// This software is in the public domain.
// There are no restrictions on any sort of usage of this software.

#include <gui/System/Drawing/Rectangle.h>
#if __GNUC_MINOR__ < 4
#define _R ::System::Drawing::Rectangle
#else
#define _R System::Drawing::Rectangle
#endif

#include <monapi/messages.h>
#include "window.h"
#include "screen.h"
#include "image.h"
#include "effect.h"
#include "Overlap.h"

#define DEFAULT_WIDTH  240
#define DEFAULT_HEIGHT 160

using namespace MonAPI;

extern CommonParameters* commonParams;
extern guiserver_bitmap* screen_buffer, * wallpaper;

static guiserver_window* activeWindow = NULL;
static HList<guiserver_window*> windows;
static int start_pos = 0;
static HList<guiserver_window*> captures;
static HList<Overlap*> overlaps;
static int prevButton = 0;

guiserver_window* CreateWindow()
{
	dword handle = MemoryMap::create(sizeof(guiserver_window));
	if (handle == 0) return NULL;
	
	guiserver_window* ret = (guiserver_window*)MemoryMap::map(handle);
	if (ret == NULL) return NULL;
	
	ret->Handle   = handle;
	ret->Parent   = 0;
	ret->Owner    = 0;
	ret->ThreadID = 0;
	ret->X        = start_pos;
	ret->Y        = start_pos;
	ret->Width    = DEFAULT_WIDTH;
	ret->Height   = DEFAULT_HEIGHT;
	ret->OffsetX  = 0;
	ret->OffsetY  = 0;
	ret->Opacity  = 255;
	ret->Visible  = false;
	ret->Focused  = false;
	ret->Flags    = 0;
	ret->TransparencyKey = 0;
	ret->BufferHandle = ret->FormBufferHandle = 0;
	ret->__internal1 = NULL;
	ret->__internal2 = false;
	windows.add(ret);
	
	start_pos += 32;
	Screen* scr = GetDefaultScreen();
	if (start_pos + DEFAULT_WIDTH >= scr->getWidth()
		&& start_pos + DEFAULT_HEIGHT >= scr->getHeight())
	{
		start_pos = 0;
	}

	return ret;
}

guiserver_window* GetWindowPointer(dword handle)
{
	int size = windows.size();
	for (int i = 0; i < size; i++)
	{
		if (windows[i]->Handle == handle) return windows[i];
	}
	return NULL;
}

bool DisposeWindow(dword handle)
{
	int size_c = captures.size();
	for (int i = 0; i < size_c; i++)
	{
		if (captures[i]->Handle == handle)
		{
			captures.removeAt(i);
			break;
		}
	}
	int size_w = windows.size();
	for (int i = 0; i < size_w; i++)
	{
		guiserver_window* w = windows[i];
		if (w->Handle == handle)
		{
			if (w->__internal2) DestructionEffect(w);
			windows.removeAt(i);
			MemoryMap::unmap(handle);
			return true;
		}
	}
	return false;
}

void DisposeWindowFromThreadID(dword tid)
{
	for (int i = 0; i < captures.size(); i++)
	{
		if (captures[i]->ThreadID == tid)
		{
			captures.removeAt(i);
			i--;
		}
	}
	for (int i = 0; i < windows.size(); i++)
	{
		guiserver_window* w = windows[i];
		if (w->ThreadID == tid)
		{
			if (w->FormBufferHandle != 0)
			{
				w->Visible = false;
				DrawWindow(w);
			}
			windows.removeAt(i);
			MemoryMap::unmap(w->Handle);
			i--;
		}
	}
}

static void DrawWindowInternal(guiserver_window* w, const _R& r)
{
	if (!w->Visible || w->FormBufferHandle == 0) return;
	
	_R rr(w->X, w->Y, w->Width, w->Height);
	rr.Intersect(r);
	if (r.Width == 0 || rr.Height == 0) return;
	
	if (w->__internal1 == NULL)
	{
		w->__internal1 = GetBitmapPointer(w->FormBufferHandle);
		if (w->__internal1 == NULL) return;
	}
	DrawImage(screen_buffer, w->__internal1, rr.X, rr.Y, rr.X - w->X, rr.Y - w->Y, rr.Width, rr.Height, w->TransparencyKey, w->Opacity);
}

void DrawWindow(guiserver_window* w, bool draw_screen /*= true*/)
{
	if (w == NULL || w->FormBufferHandle == 0) return;
	
	if (!w->__internal2) CreationEffect(w);
	
	DrawImage(screen_buffer, wallpaper, w->X, w->Y, w->X, w->Y, w->Width, w->Height);
	_R r(w->X, w->Y, w->Width, w->Height);
	int size_w = windows.size();
	for (int i = 0; i < size_w; i++)
	{
		guiserver_window* w = windows[i];
		if (w->Parent != 0 || (w->Flags & WINDOWFLAGS_TOPMOST) != 0 || (w->Flags & WINDOWFLAGS_BOTTOMMOST) == 0) continue;
		
		DrawWindowInternal(w, r);
	}
	for (int i = 0; i < size_w; i++)
	{
		guiserver_window* w = windows[i];
		if (w->Parent != 0 || (w->Flags & WINDOWFLAGS_TOPMOST) != 0 || (w->Flags & WINDOWFLAGS_BOTTOMMOST) != 0) continue;
		
		DrawWindowInternal(w, r);
	}
	for (int i = 0; i < size_w; i++)
	{
		guiserver_window* w = windows[i];
		if (w->Parent != 0 || (w->Flags & WINDOWFLAGS_TOPMOST) == 0) continue;
		
		DrawWindowInternal(w, r);
	}
	int size_ov = overlaps.size();
	for (int i = 0; i < size_ov; i++)
	{
		overlaps[i]->Draw(w->X, w->Y, w->Width, w->Height);
	}
	if (!draw_screen) return;
	
	DrawScreen(w->X, w->Y, w->Width, w->Height);
}

void MoveWindow(guiserver_window* w, int x, int y)
{
	if (w == NULL || !w->Visible || w->FormBufferHandle == 0 || (w->X == x && w->Y == y)) return;
	
	w->Visible = false;
	DrawWindow(w, false);
	int xx = w->X, yy = w->Y;
	w->Visible = true;
	w->X = x;
	w->Y = y;
	DrawWindow(w);
	DrawScreen(xx, yy, w->Width, w->Height);
}

static guiserver_window* GetTargetWindowInternal(guiserver_window* w, int x, int y)
{
	if (w->FormBufferHandle == 0) return NULL;
	
	_R r(w->X, w->Y, w->Width, w->Height);
	if (!r.Contains(x, y)) return NULL;
	
	if (w->__internal1 == NULL)
	{
		w->__internal1 = GetBitmapPointer(w->FormBufferHandle);
		if (w->__internal1 == NULL) return NULL;
	}
	unsigned int c = w->__internal1->Data[(x - w->X) + (y - w->Y) * w->Width];
	if (c == 0 || c == w->TransparencyKey) return NULL;
	
	return w;
}

guiserver_window* GetTargetWindow(int x, int y)
{
	int size = captures.size();
	if (size > 0)
	{
		return captures[size - 1];
	}
	
	for (int i = windows.size() - 1; i >= 0; i--)
	{
		guiserver_window* w = windows[i];
		if (w->Parent != 0 || (w->Flags & WINDOWFLAGS_TOPMOST) == 0) continue;
		
		guiserver_window* ww = GetTargetWindowInternal(w, x, y);
		if (ww != NULL) return ww;
	}
	for (int i = windows.size() - 1; i >= 0; i--)
	{
		guiserver_window* w = windows[i];
		if (w->Parent != 0 || (w->Flags & WINDOWFLAGS_TOPMOST) != 0 || (w->Flags & WINDOWFLAGS_BOTTOMMOST) != 0) continue;
		
		guiserver_window* ww = GetTargetWindowInternal(w, x, y);
		if (ww != NULL) return ww;
	}
	for (int i = windows.size() - 1; i >= 0; i--)
	{
		guiserver_window* w = windows[i];
		if (w->Parent != 0 || (w->Flags & WINDOWFLAGS_TOPMOST) != 0 || (w->Flags & WINDOWFLAGS_BOTTOMMOST) == 0) continue;
		
		guiserver_window* ww = GetTargetWindowInternal(w, x, y);
		if (ww != NULL) return ww;
	}
	
	return NULL;
}

static void ProcessMouseInfo(MessageInfo* msg)
{
	guiserver_window* target = GetTargetWindow(msg->arg1, msg->arg2);
	if (target == NULL) return;
	
	if (prevButton != (int)msg->arg3 && windows[windows.size() - 1] != target)
	{
		windows.remove(target);
		windows.add(target);
		DrawWindow(target);
	}
	prevButton = (int)msg->arg3;
	if (Message::send(target->ThreadID, msg) != 0)
	{
		DisposeWindowFromThreadID(target->ThreadID);
	}
}

bool WindowHandler(MessageInfo* msg)
{
	switch (msg->header)
	{
		case MSG_GUISERVER_CREATEWINDOW:
		{
			guiserver_window* w = CreateWindow();
			w->ThreadID = msg->from;
			Message::reply(msg, w->Handle);
			break;
		}
		case MSG_GUISERVER_DISPOSEWINDOW:
			DisposeWindow(msg->arg1);
			Message::reply(msg);
			break;
		case MSG_GUISERVER_DRAWWINDOW:
			DrawWindow(GetWindowPointer(msg->arg1));
			Message::reply(msg);
			break;
		case MSG_GUISERVER_MOVEWINDOW:
			MoveWindow(GetWindowPointer(msg->arg1), (int)msg->arg2, (int)msg->arg3);
			Message::reply(msg);
			break;
		case MSG_GUISERVER_WINDOWTOFRONTMOST:
		{
			guiserver_window* w = GetWindowPointer(msg->arg1);
			windows.remove(w);
			windows.add(w);
			if (msg->arg2 != 0) DrawWindow(w);
			Message::reply(msg);
			break;
		}
		case MSG_MOUSE_INFO:
			ProcessMouseInfo(msg);
			break;
		case MSG_GUISERVER_MOUSECAPTURE:
		{
			guiserver_window* w = GetWindowPointer(msg->arg1);
			if (w != NULL)
			{
				if (msg->arg2 == 0)
				{
					captures.remove(w);
				}
				else
				{
					captures.add(w);
				}
			}
			Message::reply(msg);
			break;
		}
		case MSG_GUISERVER_CREATEOVERLAP:
		{
			Overlap* ov = new Overlap(
				(int)msg->arg1, (int)msg->arg2,
				GET_X_DWORD(msg->arg3), GET_Y_DWORD(msg->arg3));
			overlaps.add(ov);
			Message::reply(msg, (dword)ov);
			break;
		}
		case MSG_GUISERVER_DISPOSEOVERLAP:
		{
			Overlap* ov = (Overlap*)msg->arg1;
			overlaps.remove(ov);
			delete ov;
			Message::reply(msg);
			break;
		}
		case MSG_GUISERVER_MOVEOVERLAP:
			((Overlap*)msg->arg1)->Move(
				GET_X_DWORD(msg->arg2), GET_Y_DWORD(msg->arg2),
				GET_X_DWORD(msg->arg3), GET_Y_DWORD(msg->arg3));
			Message::reply(msg);
			break;
		case MSG_GUISERVER_EXPANSIONEFFECT:
			ExpansionEffect(
				GET_X_DWORD(msg->arg1), GET_Y_DWORD(msg->arg1),
				GET_X_DWORD(msg->arg2), GET_Y_DWORD(msg->arg2),
				GET_X_DWORD(msg->arg3), GET_Y_DWORD(msg->arg3));
			Message::reply(msg);
			break;
		case MSG_GUISERVER_REDUCTIONEFFECT:
			ReductionEffect(
				GET_X_DWORD(msg->arg1), GET_Y_DWORD(msg->arg1),
				GET_X_DWORD(msg->arg2), GET_Y_DWORD(msg->arg2),
				GET_X_DWORD(msg->arg3), GET_Y_DWORD(msg->arg3));
			Message::reply(msg);
			break;
		default:
			return false;
	}
	return true;
}
