/*
IGUIObject
by Gustav Larsson
gee@pyro.nu
*/

#include "precompiled.h"
#include "GUI.h"

///// janwas: again, including etiquette?
#include "ps/Parser.h"

/////

#include "gui/scripting/JSInterface_IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"

extern int g_xres, g_yres;

using namespace std;

//-------------------------------------------------------------------
//  Implementation Macros
//-------------------------------------------------------------------

//-------------------------------------------------------------------
//  Constructor / Destructor
//-------------------------------------------------------------------
IGUIObject::IGUIObject() : 
	m_pGUI(NULL), 
	m_pParent(NULL),
	m_MouseHovering(false)
{
	AddSetting(GUIST_bool,			"enabled");
	AddSetting(GUIST_bool,			"hidden");
	AddSetting(GUIST_CClientArea,	"size");
	AddSetting(GUIST_CStr,			"style");
	AddSetting(GUIST_CStr,			"hotkey" );
	AddSetting(GUIST_float,			"z");
	AddSetting(GUIST_bool,			"absolute");
	AddSetting(GUIST_bool,			"ghost");

	// Setup important defaults
	GUI<bool>::SetSetting(this, "hidden", false);
	GUI<bool>::SetSetting(this, "ghost", false);
	GUI<bool>::SetSetting(this, "enabled", true);
	GUI<bool>::SetSetting(this, "absolute", true);


	for (int i=0; i<6; i++)
		m_LastClickTime[i]=0;
	bool hidden=true;

	GUI<bool>::GetSetting(this, "hidden", hidden);
}

IGUIObject::~IGUIObject()
{
	{
		map<CStr, SGUISetting>::iterator it;
		for (it = m_Settings.begin(); it != m_Settings.end(); ++it)
		{
			switch (it->second.m_Type)
			{
				// delete() needs to know the type of the variable - never delete a void*
#define TYPE(t) case GUIST_##t: delete (t*)it->second.m_pSetting; break;
#include "GUItypes.h"
#undef TYPE
		default:
			debug_warn("Invalid setting type");
			}
		}
	}

	{
		std::map<CStr, JSObject**>::iterator it;
		for (it = m_ScriptHandlers.begin(); it != m_ScriptHandlers.end(); ++it)
		{
			JS_RemoveRoot(g_ScriptingHost.getContext(), it->second);
			delete it->second;
		}
	}
}

//-------------------------------------------------------------------
//  Functions
//-------------------------------------------------------------------
void IGUIObject::AddChild(IGUIObject *pChild)
{
	// 
//	debug_assert(pChild);

	pChild->SetParent(this);

	m_Children.push_back(pChild);

	// If this (not the child) object is already attached
	//  to a CGUI, it pGUI pointer will be non-null.
	//  This will mean we'll have to check if we're using
	//  names already used.
	if (pChild->GetGUI())
	{
		try
		{
			// Atomic function, if it fails it won't
			//  have changed anything
			//UpdateObjects();
			pChild->GetGUI()->UpdateObjects();
		}
		catch (PS_RESULT e)
		{
			// If anything went wrong, reverse what we did and throw
			//  an exception telling it never added a child
			m_Children.erase( m_Children.end()-1 );

			// We'll throw the same exception for easier
			//  error handling
			throw e;
		}
	}
	// else do nothing
}

void IGUIObject::AddToPointersMap(map_pObjects &ObjectMap)
{
	// Just don't do anything about the top node
	if (m_pParent == NULL)
		return;

	// Now actually add this one
	//  notice we won't add it if it's doesn't have any parent
	//  (i.e. being the base object)
	if (m_Name.Length() == 0)
	{
		throw PS_NEEDS_NAME;
	}
	if (ObjectMap.count(m_Name) > 0)
	{
		throw PS_NAME_AMBIGUITY;
	}
	else
	{
		ObjectMap[m_Name] = this;
	}
}

void IGUIObject::Destroy()
{
	// Is there anything besides the children to destroy?
}

// Notice if using this, the naming convention of GUIST_ should be strict.
#define TYPE(type)									\
	case GUIST_##type:								\
		m_Settings[Name].m_pSetting = new type();	\
		break;

void IGUIObject::AddSetting(const EGUISettingType &Type, const CStr& Name)
{
	// Is name already taken?
	if (m_Settings.count(Name) >= 1)
		return;

	// Construct, and set type
	m_Settings[Name].m_Type = Type;

	switch (Type)
	{
		// Construct the setting.
		#include "GUItypes.h"

	default:
		debug_warn("IGUIObject::AddSetting failed, type not recognized!");
		break;
	}
}
#undef TYPE


bool IGUIObject::MouseOver()
{
	if(!GetGUI())
		throw PS_NEEDS_PGUI;

	return m_CachedActualSize.PointInside(GetMousePos());
}

CPos IGUIObject::GetMousePos() const
{ 
	return ((GetGUI())?(GetGUI()->m_MousePos):CPos()); 
}

void IGUIObject::UpdateMouseOver(IGUIObject * const &pMouseOver)
{
	// Check if this is the object being hovered.
	if (pMouseOver == this)
	{
		if (!m_MouseHovering)
		{
			// It wasn't hovering, so that must mean it just entered
			HandleMessage(GUIM_MOUSE_ENTER);
			ScriptEvent("mouseenter");
		}

		// Either way, set to true
		m_MouseHovering = true;

		// call mouse over
		HandleMessage(GUIM_MOUSE_OVER);
		ScriptEvent("mousemove");
	}
	else // Some other object (or none) is hovered
	{
		if (m_MouseHovering)
		{
			m_MouseHovering = false;
			HandleMessage(GUIM_MOUSE_LEAVE);
			ScriptEvent("mouseleave");
		}
	}
}

bool IGUIObject::SettingExists(const CStr& Setting) const
{
	// Because GetOffsets will direct dynamically defined
	//  classes with polymorphism to respective ms_SettingsInfo
	//  we need to make no further updates on this function
	//  in derived classes.
	//return (GetSettingsInfo().count(Setting) >= 1);
	return (m_Settings.count(Setting) >= 1);
}

#define TYPE(type)										\
	else												\
	if (set.m_Type == GUIST_##type)						\
	{													\
		type _Value;									\
		if (!GUI<type>::ParseString(Value, _Value))		\
			return PS_FAIL;								\
														\
		GUI<type>::SetSetting(this, Setting, _Value, SkipMessage);	\
	}

PS_RESULT IGUIObject::SetSetting(const CStr& Setting, const CStr& Value, const bool& SkipMessage)
{
	if (!SettingExists(Setting))
	{
		return PS_FAIL;
	}

	// Get setting
	SGUISetting set = m_Settings[Setting];

	if (0);
	// else...
#include "GUItypes.h"
	else
	{
		return PS_FAIL;
	}
	return PS_OK;
}

#undef TYPE


PS_RESULT IGUIObject::GetSettingType(const CStr& Setting, EGUISettingType &Type) const
{
	if (!SettingExists(Setting))
		return PS_SETTING_FAIL;

	if (m_Settings.find(Setting) == m_Settings.end())
		return PS_FAIL;

	Type = m_Settings.find(Setting)->second.m_Type;

	return PS_OK;
}


void IGUIObject::ChooseMouseOverAndClosest(IGUIObject* &pObject)
{
	if (MouseOver())
	{
		// Check if we've got competition at all
		if (pObject == NULL)
		{
			pObject = this;
			return;
		}

		// Or if it's closer
		if (GetBufferedZ() >= pObject->GetBufferedZ())
		{
			pObject = this;
			return;
		}
	}
}

IGUIObject *IGUIObject::GetParent() const
{
	// Important, we're not using GetParent() for these
	//  checks, that could screw it up
	if (m_pParent)
	{
		if (m_pParent->m_pParent == NULL)
			return NULL;
	}

	return m_pParent;
}

void IGUIObject::UpdateCachedSize()
{
	bool absolute;
	GUI<bool>::GetSetting(this, "absolute", absolute);

	CClientArea ca;
	GUI<CClientArea>::GetSetting(this, "size", ca);
	
	// If absolute="false" and the object has got a parent,
	//  use its cached size instead of the screen. Notice
	//  it must have just been cached for it to work.
	if (absolute == false && m_pParent && !IsRootObject())
		m_CachedActualSize = ca.GetClientArea(m_pParent->m_CachedActualSize);
	else
		m_CachedActualSize = ca.GetClientArea(CRect(0.f, 0.f, (float)g_xres, (float)g_yres));

}

void IGUIObject::LoadStyle(CGUI &GUIinstance, const CStr& StyleName)
{
	// Fetch style
	if (GUIinstance.m_Styles.count(StyleName)==1)
	{
		LoadStyle(GUIinstance.m_Styles[StyleName]);
	}
	else
	{
		debug_warn("IGUIObject::LoadStyle failed");
	}
}

void IGUIObject::LoadStyle(const SGUIStyle &Style)
{
	// Iterate settings, it won't be able to set them all probably, but that doesn't matter
	std::map<CStr, CStr>::const_iterator cit;
	for (cit = Style.m_SettingsDefaults.begin(); cit != Style.m_SettingsDefaults.end(); ++cit)
	{
		// Try set setting in object
		SetSetting(cit->first, cit->second);

		// It doesn't matter if it fail, it's not suppose to be able to set every setting.
		//  since it's generic.

		// The beauty with styles is that it can contain more settings
		//  than exists for the objects using it. So if the SetSetting
		//  fails, don't care.
	}
}

float IGUIObject::GetBufferedZ() const
{
	bool absolute;
	GUI<bool>::GetSetting(this, "absolute", absolute);

	float Z;
	GUI<float>::GetSetting(this, "z", Z);

	if (absolute)
		return Z;
	else
	{
		if (GetParent())
			return GetParent()->GetBufferedZ() + Z;
		else
		{
			// In philosophy, a parentless object shouldn't be able to have a relative sizing,
			//  but we'll accept it so that absolute can be used as default without a complaint. 
			//  Also, you could consider those objects children to the screen resolution.
			return Z;
		}
	}
}

// TODO Gee: keep this function and all???
void IGUIObject::CheckSettingsValidity()
{
	bool hidden;
	GUI<bool>::GetSetting(this, "hidden", hidden);

	// If we hide an object, reset many of its parts
	if (hidden)
	{
		// Simulate that no object is hovered for this object and all its children
		//  why? because it's 
		try
		{
			GUI<IGUIObject*>::RecurseObject(0, this, &IGUIObject::UpdateMouseOver, NULL);
		}
		catch (PS_RESULT)
		{
		}
	}

	try
	{
		// Send message to itself
		HandleMessage(GUIM_SETTINGS_UPDATED);
		ScriptEvent("update");
	}
	catch (PS_RESULT)
	{
	}
}

void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI)
{
	const int paramCount = 1;
	const char* paramNames[paramCount] = { "mouse" };

	// Location to report errors from
	CStr CodeName = GetName()+" "+Action;

	// Generate a unique name
	static int x=0;
	char buf[64];
	sprintf(buf, "__eventhandler%d", x++);

	JSFunction* func = JS_CompileFunction(g_ScriptingHost.getContext(), pGUI->m_ScriptObject, buf, paramCount, paramNames, (const char*)Code, Code.Length(), CodeName, 0);
	debug_assert(func); // TODO: Handle errors
	if (func)
		SetScriptHandler(Action, JS_GetFunctionObject(func));
}

void IGUIObject::SetScriptHandler(const CStr& Action, JSObject* Function)
{
	JSObject** obj = new JSObject*;
	*obj = Function;
	JS_AddRoot(g_ScriptingHost.getContext(), obj);

	if (m_ScriptHandlers[Action])
	{
		JS_RemoveRoot(g_ScriptingHost.getContext(), m_ScriptHandlers[Action]);
		delete m_ScriptHandlers[Action];
	}
	m_ScriptHandlers[Action] = obj;
}

void IGUIObject::ScriptEvent(const CStr& Action)
{
	map<CStr, JSObject**>::iterator it = m_ScriptHandlers.find(Action);
	if (it == m_ScriptHandlers.end())
		return;

	// PRIVATE_TO_JSVAL assumes two-byte alignment,
	// so make sure that's always true
	debug_assert(! ((jsval)this & JSVAL_INT));

	// The IGUIObject needs to be stored inside the script's object
	jsval guiObject = PRIVATE_TO_JSVAL(this);

	// Make a 'this', allowing access to the IGUIObject
	JSObject* jsGuiObject = JS_ConstructObjectWithArguments(g_ScriptingHost.getContext(), &JSI_IGUIObject::JSI_class, NULL, m_pGUI->m_ScriptObject, 1, &guiObject);
	debug_assert(jsGuiObject); // TODO: Handle errors
	
	// Prevent it from being garbage-collected before it's passed into the function
	JS_AddRoot(g_ScriptingHost.getContext(), &jsGuiObject);

	// Set up the 'mouse' parameter
	jsval mouseParams[3];
	mouseParams[0] = INT_TO_JSVAL(m_pGUI->m_MousePos.x);
	mouseParams[1] = INT_TO_JSVAL(m_pGUI->m_MousePos.y);
	mouseParams[2] = INT_TO_JSVAL(m_pGUI->m_MouseButtons);
	JSObject* mouseObj = JS_ConstructObjectWithArguments(g_ScriptingHost.getContext(), &JSI_GUIMouse::JSI_class, NULL, m_pGUI->m_ScriptObject, 3, mouseParams);
	debug_assert(mouseObj); // TODO: Handle errors

	// Don't garbage collect the mouse
	JS_AddRoot(g_ScriptingHost.getContext(), &mouseObj);

	const int paramCount = 1;
	jsval paramData[paramCount];
	paramData[0] = OBJECT_TO_JSVAL(mouseObj);

	jsval result;

	JSBool ok = JS_CallFunctionValue(g_ScriptingHost.getContext(), jsGuiObject, OBJECT_TO_JSVAL(*it->second), 1, paramData, &result);
	if (!ok)
	{
		JS_ReportError(g_ScriptingHost.getContext(), "Errors executing script action \"%s\"", Action.c_str());
	}

	// Allow the temporary parameters to be garbage-collected
	JS_RemoveRoot(g_ScriptingHost.getContext(), &mouseObj);
	JS_RemoveRoot(g_ScriptingHost.getContext(), &jsGuiObject);
}

CStr IGUIObject::GetPresentableName() const
{
	// __internal(), must be at least 13 letters to be able to be
	//  an internal name
	if (m_Name.Length() <= 12)
		return m_Name;

	if (m_Name.GetSubstring(0, 10) == "__internal")
		return CStr("[unnamed object]");
	else
		return m_Name;
}

void IGUIObject::SetFocus()
{
	GetGUI()->m_FocusedObject = this; 
}

bool IGUIObject::IsFocused() const
{
	return GetGUI()->m_FocusedObject == this; 
}

bool IGUIObject::IsRootObject() const
{
	return (GetGUI() != 0 && m_pParent == GetGUI()->m_BaseObject);
}
