/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#pragma warning(disable:4786)

#define STRICT
#include <Windows.h>
#include <string>
#include <sstream>
#include <map>
#include <list>
#include <vector>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include "WinOnly.h"
#include "WinBe.h"
#include "NativeApplication.h"
#include "NativeThread.h"

namespace winBe {


// setTimer
int setTimer (int milliseconds)
{
	return ::SetTimer (NULL, NULL, milliseconds, NULL);
}


// deleteTimer
void deleteTimer (int id)
{
	::KillTimer (NULL, id);
}


// getMessage
BMessage* getMessage ()
{
	debugMessage ("GetBMessage waiting for message");

	BMessage* message = 0;
	MSG msg;

	while (::GetMessage (&msg, NULL, NULL, NULL)) {
		::TranslateMessage (&msg);
		::DispatchMessage (&msg);

		if (msg.message == WM_BMESSAGE) {
			message = reinterpret_cast<BMessage*>(msg.wParam);
			break;
		}

		if (msg.message == WM_TIMER) {
			sendPulse ();
		}
	}

	return message;
}


// postMessage
bool postMessage (thread_id threadId, BMessage* message, unsigned long what)
{
	NativeThread* nativeThread = reinterpret_cast<NativeThread*>(threadId);

	std::ostringstream str;
	str << "Posting message '" << getMessageChars (what) << "' to [" << nativeThread->getThreadId () << ']';
	debugMessage (str.str ());

	return nativeThread->postMessage (reinterpret_cast<long>(message));
}


// postQuitMessage
void postQuitMessage (thread_id threadId)
{
	debugMessage ("PostQuitMessage");

	NativeThread* nativeThread = reinterpret_cast<NativeThread*>(threadId);
	nativeThread->postQuitMessage ();
}


// debugMessage
void debugMessage (const std::string& str)
{
#ifdef _DEBUG
	std::ostringstream output;
	output << '[' << ::GetCurrentThreadId () << "] " << str << '\n';
	::OutputDebugString (output.str ().c_str ());
#endif // _DEBUG
}


// GetMessageChars
std::string getMessageChars (unsigned long what)
{
	std::string chars;

	chars += static_cast<char>(what >> 24);
	chars += static_cast<char>(what >> 16);
	chars += static_cast<char>(what >> 8);
	chars += static_cast<char>(what);

	return chars;
}


// createWindow
void createWindow (BWindow* window, std::string title, int left, int top, int right, int bottom)
{
	debugMessage ("Creating window '" + title + "'");

	NativeWindow* wnd = NativeApplication::instance ().addWindow (window);
	wnd->createWindow (left, top, right, bottom);
	wnd->setTitle (title);
}


// destroyWindow
void destroyWindow (BWindow* window)
{
	NativeWindow* nativeWindow = NativeApplication::instance ().findWindow (window);
	if (nativeWindow)
		nativeWindow->destroy ();
}


// updateWindow
void updateWindow (BWindow* window)
{
	NativeWindow* wnd = NativeApplication::instance ().findWindow (window);
	::UpdateWindow (wnd->getHWND ());
}


// getWindowList
std::list<BWindow*> getWindowList ()
{
	return NativeApplication::instance ().getWindowList ();
}


// getClientRect
void getClientRect (BWindow* window, int& left, int& top, int& right, int& bottom)
{
	NativeWindow* wnd = NativeApplication::instance ().findWindow (window);
	RECT rect = wnd->getClientRect ();

	left = rect.left;
	top = rect.top;
	right = rect.right;
	bottom = rect.bottom;
}


// getWindowFurnitureSize
void getWindowFurnitureSize (BWindow* window, int& width, int& height)
{
	width = 2 * GetSystemMetrics (SM_CXFRAME);
	height = GetSystemMetrics (SM_CYCAPTION) + /*GetSystemMetrics (SM_CYMENU) +*/ 2 * GetSystemMetrics (SM_CYFRAME);
}


void getWindowSize (BWindow* window, int& width, int& height)
{
	NativeWindow* wnd = NativeApplication::instance ().findWindow (window);
	if (wnd) {
		RECT rect = wnd->getRect ();
		width = rect.right - rect.left;
		height = rect.bottom - rect.top;
	}
}


void setWindowSize (BWindow* window, int width, int height)
{
	NativeWindow* wnd = NativeApplication::instance ().findWindow (window);
	if (wnd) {
		RECT rect = wnd->getRect ();
		rect.right = rect.left + width;
		rect.bottom = rect.top + height;
		wnd->setRect (rect);
	}
}


// setViewColor
void setViewColor (BView* view, int red, int green, int blue)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView)
		nativeView->setBgColor (RGB (red, green, blue));
}


// createView
void createView (BView* view, BWindow* parent, const char* name, int left, int top, int right, int bottom)
{
	NativeWindow* wnd = NativeApplication::instance ().findWindow (parent);
	if (wnd)
		wnd->createView (view, name, left, top, right, bottom, 0);
	else
		debugMessage ("createView can't find window");
}


// createView
void createView (BView* view, BView* parent, const char* name, int left, int top, int right, int bottom)
{
	NativeView* nativeParent = NativeApplication::instance ().findView (parent);
	NativeWindow* window = nativeParent->getWindow ();
	if (nativeParent && window)
		window->createView (view, name, left, top, right, bottom, nativeParent);
	else
		debugMessage ("createView can't find window");
}


// destroyView
void destroyView (BView* view)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView)
		nativeView->destroy ();
}


// drawString
void drawString (BView* view, const char* text, int length, int x, int y, int red, int green, int blue)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView) {
		HDC hDC = nativeView->getDC ();
		//::SetBkColor (hDC, nativeView->getColor ());
		::SetBkMode (hDC, TRANSPARENT);
		::SetTextColor (hDC, RGB (red, green, blue));
		::TextOut (hDC, x, y, text, length);
	}
}


// drawLine
void drawLine (BView* view, int x1, int y1, int x2, int y2)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView) {
		HDC hDC = nativeView->getDC ();
		HGDIOBJ hOldPen = ::SelectObject (hDC, nativeView->getPen ());
		::MoveToEx (hDC, x1, y1, NULL);
		::LineTo (hDC, x2, y2);
		::SelectObject (hDC, hOldPen);
	}
}


// drawLineArray
void drawLineArray (BView* view, LineArray* lineArray, int size, int penSize)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView) {
		HDC hDC = nativeView->getDC ();
		for (int i = 0; i < size; i++) {
			HPEN hPen = ::CreatePen (PS_SOLID, penSize, RGB (lineArray[i].red, lineArray[i].green, lineArray[i].blue));
			HGDIOBJ hOldPen = ::SelectObject (hDC, hPen);
			::MoveToEx (hDC, lineArray[i].pt0_x, lineArray[i].pt0_y, NULL);
			::LineTo (hDC, lineArray[i].pt1_x, lineArray[i].pt1_y);
			::SelectObject (hDC, hOldPen);
			::DeleteObject (hPen);
		}
	}
}


// setPen
void setPen (BView* view, int width, int red, int green, int blue)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView)
		nativeView->setPen (width, RGB (red, green, blue));
}


// fillRect
void fillRect (BView* view, int left, int top, int right, int bottom, int red, int green, int blue)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView) {
		HDC hDC = nativeView->getDC ();
		RECT rect;
		::SetRect (&rect, left, top, right, bottom);
		HBRUSH hBrush = ::CreateSolidBrush (RGB (red, green, blue));
		::FillRect (hDC, &rect, hBrush);
		::DeleteObject (hBrush);
	}
}


// drawEllipse
void drawEllipse (BView* view, int left, int top, int right, int bottom)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView) {
		HDC hDC = nativeView->getDC ();
		int mid = (right - left) / 2;
		::Arc (hDC, left, top, right, bottom, mid, top, mid, top);
	}
}


// fillEllipse
void fillEllipse (BView* view, int left, int top, int right, int bottom, int red, int green, int blue)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView) {
		HDC hDC = nativeView->getDC ();
		HBRUSH hBrush = ::CreateSolidBrush (RGB (red, green, blue));
		HBRUSH hOldBrush = static_cast<HBRUSH>(::SelectObject (hDC, hBrush));
		::Ellipse (hDC, left, top, right, bottom);
		::SelectObject (hDC, hOldBrush);
		::DeleteObject (hBrush);
	}
}


// invalidate
void invalidate (BView* view, int left, int top, int right, int bottom)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	if (nativeView) {
		HWND hWnd = nativeView->getHWND ();
		
		RECT rect;
		rect.left = left;
		rect.top = top;
		rect.right = right;
		rect.bottom = bottom;

		::InvalidateRect (hWnd, &rect, TRUE);
		::UpdateWindow (hWnd);
	}
}


// createFont
unsigned long createFont (std::string face, int size, bool bold, bool italic)
{
	HDC hDC = ::GetDC (::GetDesktopWindow ());
	int trueSize = (size * ::GetDeviceCaps (hDC, LOGPIXELSY)) / 72;
	::ReleaseDC (GetDesktopWindow (), hDC);

	HFONT hFont = ::CreateFont (
		-trueSize, 0, 0, 0, bold ? FW_BOLD : FW_NORMAL,
		italic ? TRUE : FALSE, FALSE, FALSE,
		ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
		DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
		face.c_str ());

	return reinterpret_cast<unsigned long>(hFont);
}


// deleteFont
void deleteFont (unsigned long font)
{
	HFONT hFont = reinterpret_cast<HFONT>(font);
	::DeleteObject (hFont);
}


// selectFont
void selectFont (BView* view, unsigned long font)
{
	HFONT hFont = reinterpret_cast<HFONT>(font);
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	::SelectObject (nativeView->getDC (), hFont);
}


// stringDimensions
void stringDimensions (unsigned long font, const char* string, int length, int& width, int& height)
{
	HFONT hFont = reinterpret_cast<HFONT>(font);
	HDC hDC = ::GetDC (::GetDesktopWindow ());
	SIZE size;
	::GetTextExtentPoint (hDC, string, length, &size);
	::ReleaseDC (::GetDesktopWindow (), hDC);

	width = size.cx;
	height = size.cy;
}


// captureMouse
void captureMouse (BView* view)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	nativeView->captureMouse ();
}


// captureKeyboard
void captureKeyboard (BView* view)
{
	NativeView* nativeView = NativeApplication::instance ().findView (view);
	nativeView->captureKeyboard ();
}


} // namespace winBe
