/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include <float.h>
		#include "ui/ui_cursor.h"
		#include "ui/ui.h"
		#include "ui/ui_window.h"
		#include "script/script_engine_types.h"
		#include "script/script_variant.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::S2D;


//------------------------------------------------------------------------------
bool				Scene::CallEventHandler(Cursor *cursor, EventCode event_code, EventTable *event_table)
{
	EventHandler *handler = event_table ? event_table->GetHandler(event_code) : NULL;
	if	(!handler)
		return false;

	if	(vm->SetupFunctionCall(NULL, handler->GetHandler(), handler->GetContext()))
	{
		AutoPtr <Script::Object> table_object(vm->CreateTable());

		table_object->Set("sprite", Script::Variant(cursor->current_state.item, Script::typetag_Window));
		table_object->Set("window", Script::Variant(cursor->current_state.item, Script::typetag_Window));

		table_object->Set("widget", Script::Variant(cursor->current_state.widget, Script::typetag_Widget));

		if	(cursor->current_state.widget)
			table_object->Set("id", Script::Variant(cursor->current_state.widget->GetId()));

		table_object->Set("cursor_id", cursor->id);
		table_object->Set("x", cursor->current_state.x);
		table_object->Set("y", cursor->current_state.y);
		table_object->Set("dx", cursor->current_state.x - cursor->previous_state.x);
		table_object->Set("dy", cursor->current_state.y - cursor->previous_state.y);
		table_object->Set("down", cursor->current_state.down);

		// Cursor position in sprite.
		if	(cursor->current_state.item)
		{
			Vector2 c_pos = cursor->current_state.item->ScreenToLocal(Vector2(cursor->current_state.x, cursor->current_state.y));
			table_object->Set("local_x", c_pos.x);
			table_object->Set("local_y", c_pos.y);

			if	(cursor->previous_state.item == cursor->current_state.item)
			{
				Vector2 p_pos = cursor->previous_state.item->ScreenToLocal(Vector2(cursor->previous_state.x, cursor->previous_state.y));
				table_object->Set("local_dx", c_pos.x - p_pos.x);
				table_object->Set("local_dy", c_pos.y - p_pos.y);
			}
			else
			{
				table_object->Set("local_dx", 0);
				table_object->Set("local_dy", 0);
			}
		}

		vm->PushArgument(event_code);
		vm->PushArgument(table_object.c_ptr());

		if	(!vm->DoFunctionCall())
			__ERR__(__LOG_E__ << "Event handler (" << event_code << ") call failed. The handler parameters should be (event, table).\n", false)

		return true;
	}
	return false;
}
void				Scene::CallEventHandler(Cursor *cursor, Widget *widget, EventCode event_code, bool can_propagate)
{
	for	(Widget *base = widget; base; base = base->GetParent())
	{
		if	(CallEventHandler(cursor, event_code, base->GetEventTable()))
			return;
		if	(!can_propagate)
			break;
	}

	// Try the sprite event handler.
	if	(can_propagate && cursor->current_state.item.IsValid())
		CallEventHandler(cursor, event_code, cursor->current_state.item->GetEventTable());
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Widget				*Scene::RecurseWidgetBelowCursor(Window *window, Widget *widget, int mx, int my)
{
	if	(!window || !widget)
		return NULL;

	ListForeachPtr(Widget *, c, widget->GetChildren())
	{
		Rect <int> rect(c->GetRect());
		if	(!c->IsHidden() && window->LocalToScreen(rect).Inside(mx, my))
		{
			Widget	*sub_w = RecurseWidgetBelowCursor(window, c, mx, my);
			return sub_w ? sub_w : c;
		}
	}
	return widget;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::ProcessCursorEnterLeaveWidget(Cursor *cursor, Window *window, Widget *widget)
{
	if	(widget && !widget->IsHidden())
	{
		// Process on widget.
		iRect screen_rect = window->LocalToScreen(widget->GetRect());

		if	(screen_rect.Inside((int)cursor->current_state.x, (int)cursor->current_state.y))
		{
			if	(!screen_rect.Inside((int)cursor->previous_state.x, (int)cursor->previous_state.y))
				CallEventHandler(cursor, widget, Event_CursorEnter, false);
		}
		else
			if	(screen_rect.Inside((int)cursor->previous_state.x, (int)cursor->previous_state.y))
				CallEventHandler(cursor, widget, Event_CursorLeave, false);

		// Process on widget children.
		ListForeachPtr(Widget *, c, widget->GetChildren())
			ProcessCursorEnterLeaveWidget(cursor, window, c);
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::UpdateCursor(Cursor *cursor)
{
	Cursor::State &p_state = cursor->previous_state,
				  &c_state = cursor->current_state,
				  &down_state = cursor->down_state;

	// Assert lock sprite validity.
	if	(lock && !item_list.Find(lock))
		lock = NULL;

	// Move cursor position in system reference.
	Vector4 c_ui = screen_to_ui * Vector4(c_state.x, c_state.y, 1);
//	__LOG__ << "x: " << c_ui.x << ", y: " << c_ui.y << "\n";

	c_state.x = c_ui.x;
	c_state.y = c_ui.y;

	// Determine the current sprite under the cursor.
	if	(lock)
		c_state.item = lock;

	else
	{
		float min_z_order = FLT_MAX;

		c_state.item = NULL;
		for	(List <Item *> ::Item *e = item_list.GetLast(); e; e = e->Previous())
		{
			Item *i = e->Object();

			switch (i->GetItemType())
			{
				case Item::Type_Sprite:
				case Item::Type_Window:
				{
					Sprite *s = (Sprite *)i;

					if	(s->opacity == 0.0)
						break;
					if	(s->sprite_flags.IsSet(Sprite::FlagNonSensitive))
						break;

					Rect <int>	wrect(s->GetRect());

					if	(s->LocalToScreen(wrect).Inside((int)c_state.x, (int)c_state.y) && (min_z_order > s->GetZOrder()))
					{
						min_z_order = s->GetZOrder();
						c_state.item = s;
					}
				}
				break;
			}
		}
	}

	// Determine the current widget under the cursor.
	c_state.widget = NULL;
	if	(c_state.item && (c_state.item->GetItemType() == Item::Type_Window))
	{
		Window *w = (Window *)c_state.item.c_ptr();
		c_state.widget = RecurseWidgetBelowCursor(w, w->GetBaseWidget(), (int)c_state.x, (int)c_state.y);
	}

// State must be complete from here on.

	// Store down state.
	if	(c_state.down && !p_state.down)
		down_state = c_state;

	// Handle cursor up event (done here so that the down_state widget/sprite will always catch the event).
	if	(p_state.down && !c_state.down)
	{
		if	(down_state.widget)
			CallEventHandler(cursor, down_state.widget, Event_CursorUp);
		else if	(down_state.item)
			CallEventHandler(cursor, Event_CursorUp, down_state.item->GetEventTable());
	}
	
	// Handle hit/down/move cursor events.
	if	(c_state.widget)
	{
		if	(p_state.down && !c_state.down)
		{
			if	(c_state.widget == down_state.widget)
				CallEventHandler(cursor, c_state.widget, Event_CursorHit);
		}

		if	(!p_state.down && c_state.down)
			CallEventHandler(cursor, c_state.widget, Event_CursorDown);

		if	((c_state.widget == p_state.widget) && ((c_state.x != p_state.x) || (c_state.y != p_state.y)))
			CallEventHandler(cursor, c_state.widget, Event_CursorMove);
	}
	else
		if	(c_state.item)
		{
			if	(p_state.down && !c_state.down)
			{
				if	(c_state.item == down_state.item)
					CallEventHandler(cursor, Event_CursorHit, c_state.item->GetEventTable());
			}

			if	(!p_state.down && c_state.down)
				CallEventHandler(cursor, Event_CursorDown, c_state.item->GetEventTable());

			if	((c_state.item == p_state.item) && ((c_state.x != p_state.x) || (c_state.y != p_state.y)))
				CallEventHandler(cursor, Event_CursorMove, c_state.item->GetEventTable());
		}

	// Handle enter/leave cursor events.
	if	(c_state.item)
	{
		if	(c_state.item->GetItemType() == Item::Type_Window)
		{
			Window *w = (Window *)c_state.item.c_ptr();
			ProcessCursorEnterLeaveWidget(cursor, w, w->GetBaseWidget());
		}
		if	(c_state.item != p_state.item)
			CallEventHandler(cursor, Event_CursorEnter, c_state.item->GetEventTable());
	}
	if	(p_state.item)
	{
		if	(p_state.item->GetItemType() == Item::Type_Window)
		{
			Window *w = (Window *)p_state.item.c_ptr();
			ProcessCursorEnterLeaveWidget(cursor, w, w->GetBaseWidget());
		}
		if	(p_state.item != c_state.item)
			CallEventHandler(cursor, Event_CursorLeave, p_state.item->GetEventTable());
	}

#if		0
	if	((c_state.x != p_state.x) || (c_state.y != p_state.y))
	{
		__LOG__ << "CURSOR ID = " << cursor->id << "\n";
		__LOG__ << "\n";
		__LOG__ << "X = " << c_state.x << ", Y = " << c_state.y << "\n";

		if	(c_state.item)
			__LOG__ << "PX = " << c_state.item->GetPosition().x << ", PY = " << c_state.item->GetPosition().y << "\n";

		if	(Sprite *sprite = (Sprite *)c_state.item)
		{
			__LOG__ << "SPRITE = ";
			if	(sprite)
				__LOG__ << (int)sprite;
			else
				__LOG__ << "None";
		}

		__LOG__ << ", WIDGET = ";
		if	(c_state.widget)
		{
			__LOG__ << (int)c_state.widget << " (Id = " << c_state.widget->GetId() << "), type = " << c_state.widget->GetType() << "\n";
			nRect <int>	rect = c_state.widget->GetRect();
			__LOG__ << "SX = " << rect.sx << ", SY = " << rect.sy << ", W = " << rect.GetWidth() << ", EY = " << rect.GetHeight() << "\n";
		}
		else
			__LOG__ << "None";

		__LOG__ << "\n";
	}
#endif

	p_state = c_state;
}
//------------------------------------------------------------------------------
