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


		#include <cstdio>
		#include <cmath>

		#include "squirrel.h"
		#include "script_squirrel/cobject/uc_binding.h"
		#include "script_squirrel/cobject/cobject.h"
		#include "script_squirrel/cobject/vector_decl.h"
		#include "script_squirrel/cobject/uv_decl.h"

		#include "squirrel_binding.h"
		#include "binding_helpers.h"

	#if	__PLATFORM_NINTENDO_WII__
		#include "platform/wii/script/wii_binding.h"
	#endif
		#include "geometry/bounding_box.h"
		#include "color/color.h"
		#include "geometry/rect.h"
		#include "filesystem/filesystem.h"
		#include "platform.h"


namespace GS {
	namespace Script {

//------------------------------------------------------------------------------
static bool			BindingError(HSQUIRRELVM vm, const char *msg)
{
//	if	(sq_getforeignptr(vm))
//		((Script::VM *)sq_getforeignptr(vm))->Kill();
	SquirrelVM::DumpCallStack(vm, String::Format("Binding error: %s", msg).c_str());
	return false;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				CreateClassInstance(HSQUIRRELVM vm, const char *class_name, bool call)
{
	sq_pushroottable(vm);
	sq_pushstring(vm, class_name, -1);
	if	(SQ_FAILED(sq_get(vm, -2)))
	{
		sq_pop(vm, 1);
		return BindingError(vm, String::Format("Class '%s' is not declared.", class_name).c_str());
	}
	if	(call)
	{
		sq_pushroottable(vm);
		if	(SQ_FAILED(sq_call(vm, 1, SQTrue, SQTrue)))
		{
			sq_pop(vm, 1);
			return BindingError(vm, String::Format("Failed to instantiate class '%s'.", class_name).c_str());
		}
	}
	else
		sq_createinstance(vm, -1);
	sq_remove(vm, -2);		// Remove root table.
	sq_remove(vm, -2);		// Remove class.
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				PushColor(HSQUIRRELVM vm, const Color &c)
{
	if	(!CreateClassInstance(vm, "Vector", true))
		BindingError(vm, "Failed to create Vector instance.");
	SetTableKey("x", sq_pushfloat, -1, c.x);
	SetTableKey("y", sq_pushfloat, -1, c.y);
	SetTableKey("z", sq_pushfloat, -1, c.z);
	SetTableKey("w", sq_pushfloat, -1, c.w);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				PushVector2(HSQUIRRELVM vm, const Vector2 &v)
{
	if	(!CreateClassInstance(vm, "Vector2", true))
		BindingError(vm, "Failed to create Vector2 instance.");
	SetTableKey("x", sq_pushfloat, -1, v.x);
	SetTableKey("y", sq_pushfloat, -1, v.y);
}
void				GetVector2(HSQUIRRELVM vm, SQInteger idx, Vector2 &v)
{
	SQFloat x, y;
	GetTableKey("x", sq_getfloat, idx, x)
	GetTableKey("y", sq_getfloat, idx, y)
	v.Set(x, y);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				PushVector(HSQUIRRELVM vm, const Vector4 &v, bool push_w)
{
	push_Vector(vm, v);
}
void				GetVector(HSQUIRRELVM v, SQInteger idx, Vector4 &vo, bool get_w)
{
	_CHECK_INST_PARAM_RAW(pv, idx, Vector4, Vector);
	vo = *pv;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				PushUV(HSQUIRRELVM vm, const Vector2 &uv)
{
	if	(!CreateClassInstance(vm, "UV", true))
		BindingError(vm, "Failed to create UV instance.");
	SetTableKey("u", sq_pushfloat, -1, uv.x);
	SetTableKey("v", sq_pushfloat, -1, uv.y);
}
void				GetUV(HSQUIRRELVM v, SQInteger idx, Vector2 &uv)
{
	_CHECK_INST_PARAM_RAW(pv, idx, Vector2, UV);
	uv = *pv;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				PushMinMax(HSQUIRRELVM vm, const MinMax &v)
{
	if	(!CreateClassInstance(vm, "MinMax", true))
		BindingError(vm, "Failed to create MinMax instance.");

	sq_pushstring(vm, "min", -1);
	sq_get(vm, -2);
	SetTableKey("x", sq_pushfloat, -1, v.mn.x)
	SetTableKey("y", sq_pushfloat, -1, v.mn.y)
	SetTableKey("z", sq_pushfloat, -1, v.mn.z)
	sq_pop(vm, 1);

	sq_pushstring(vm, "max", -1);
	sq_get(vm, -2);
	SetTableKey("x", sq_pushfloat, -1, v.mx.x)
	SetTableKey("y", sq_pushfloat, -1, v.mx.y)
	SetTableKey("z", sq_pushfloat, -1, v.mx.z)
	sq_pop(vm, 1);
}
void				GetMinMax(HSQUIRRELVM vm, SQInteger idx, MinMax &v)
{
	sq_pushstring(vm, "min", -1);
	sq_get(vm, -2);
	GetVector(vm, -1, v.mn);
	sq_pop(vm, 1);

	sq_pushstring(vm, "max", -1);
	sq_get(vm, -2);
	GetVector(vm, -1, v.mx);
	sq_pop(vm, 1);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				PushRect(HSQUIRRELVM vm, const fRect &r)
{
	if	(!CreateClassInstance(vm, "Rect"))
		BindingError(vm, "Failed to create Rect instance.");
	SetTableKey("sx", sq_pushfloat, -1, r.sx);
	SetTableKey("sy", sq_pushfloat, -1, r.sy);
	SetTableKey("ex", sq_pushfloat, -1, r.ex);
	SetTableKey("ey", sq_pushfloat, -1, r.ey);
}
void				GetRect(HSQUIRRELVM vm, SQInteger idx, fRect &r)
{
	SQFloat sx, sy, ex, ey;
	GetTableKey("sx", sq_getfloat, idx, sx)
	GetTableKey("sy", sq_getfloat, idx, sy)
	GetTableKey("ex", sq_getfloat, idx, ex)
	GetTableKey("ey", sq_getfloat, idx, ey)
	r.Set(sx, sy, ex, ey);
}
void				PushRect(HSQUIRRELVM vm, const iRect &r)
{	PushRect(vm, r.AsFloat());		}
void				GetRect(HSQUIRRELVM vm, SQInteger idx, iRect &r)
{
	fRect t;
	GetRect(vm, idx, t);
	r.Set(int(t.sx), int(t.sy), int(t.ex), int(t.ey));
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				sq_register(HSQUIRRELVM v, SQFUNCTION f, const char *fname, const SQChar *mask)
{
	sq_pushroottable(v);
	sq_pushstring(v, (const SQChar *)fname, -1);
	sq_newclosure(v, f, 0);	// Create a new function.
	sq_setnativeclosurename(v, -1, fname);
	sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, mask);
	sq_newslot(v, -3, false);
	sq_pop(v, 1);			// Pop the root table.
}
//------------------------------------------------------------------------------

	} // Script
} // GS

		using namespace GS;
		using namespace GS::Script;

//------------------------------------------
SQInteger			SQAssert(HSQUIRRELVM vm)
//------------------------------------------
{
	__SQ_GETSTART(2)
	__SQ_GETBOOL(c)
	__SQ_GETSTRING(d)
	if	(!c)
		return sq_throwerror(vm, d);
	__SQ_GETEND
	__SQ_RETURN
}

//---------------------------------------
SQInteger			SQPow(HSQUIRRELVM vm)
//---------------------------------------
{
	__SQ_GETSTART(2)
	__SQ_GETFLOAT(v)
	__SQ_GETFLOAT(p)
	__SQ_GETEND
	__SQ_RETURNFLOAT(pow(v, p))
}

//---------------------------------------
SQInteger			SQExp(HSQUIRRELVM vm)
//---------------------------------------
{
	__SQ_GETSTART(1)
		__SQ_GETFLOAT(v)
		__SQ_GETEND
		__SQ_RETURNFLOAT(exp(v))
}

//---------------------------------------
SQInteger			SQMod(HSQUIRRELVM vm)
//---------------------------------------
{
	__SQ_GETSTART(2)
	__SQ_GETINT(v)
	__SQ_GETINT(m)
	__SQ_GETEND
	__SQ_RETURNINT(v % m)
}

//------------------------------------------
SQInteger			SQAbsMod(HSQUIRRELVM vm)
//------------------------------------------
{
	__SQ_GETSTART(2)
	__SQ_GETINT(v)
	__SQ_GETINT(m)
	__SQ_GETEND
	v %= m;
	if	(v < 0)
		v = m + v;
	__SQ_RETURNINT(v)
}

//-----------------------------------------------
SQInteger			ObjectIsValid(HSQUIRRELVM vm)
//-----------------------------------------------
{
	void *p;
	if	(!CObject::Get(vm, -1, &p))
			return -1;
	sq_pop(vm, 1);
	__SQ_RETURNBOOL(asbool(p));
}

//----------------------------------------------
SQInteger			ObjectIsSame(HSQUIRRELVM vm)
//----------------------------------------------
{
	void *a, *b;
	if	(!CObject::Get(vm, -2, &a) || !CObject::Get(vm, -1, &b))
		return -1;
	sq_pop(vm, 2);
	__SQ_RETURNBOOL(a == b);
}

//----------------------------------------------
SQInteger			ShellExecute(HSQUIRRELVM vm)
//----------------------------------------------
{
	__SQ_GETSTART(2)
	__SQ_GETSTRING(path)
	__SQ_GETSTRING(parm)

	int r = -1;

#if	__PLATFORM_WINDOWS__
	if	(path)
	{
		String cmd = parm ? String::Format("%s %s", path, parm).c_str() : path;
		__LOG_H__ << "Execute command '" << cmd << "'\n\n";

#if		0
		char				szBuffer[_MAX_PATH * 10 + 1];

		DWORD				dw;
		HANDLE				hIn, hOut;
		PROCESS_INFORMATION	pi;
		SECURITY_ATTRIBUTES	sa;
		STARTUPINFO			si;

		if	(CreatePipe(&hIn, &hOut, NULL, sizeof(szBuffer) * 2))
		{
			memset(&si, 0, sizeof(si));
			si.cb = sizeof(si);
			si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
			si.wShowWindow = SW_HIDE;
			si.hStdOutput = hOut;
			si.hStdError = hOut;

			//if	(!CreateProcess("C:\\WINDOWS\\system32\\cmd.exe", (LPSTR)cmd.c_str(), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
			if	(!CreateProcess(NULL, (LPSTR)cmd.c_str(), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
				__LOG_N__ << "Couldn't create process: " << GetLastError() << "\n";
			else
			{
				CloseHandle(hOut);
				__LOG_N__ << "Reading ShellExecute...\n";

				forever
				{
					if	(ReadFile(hIn, szBuffer, sizeof(szBuffer) - 1, &dw, NULL) == 0)
					{
						if	(GetLastError() == ERROR_NO_DATA)
							Sleep(1);

						else	if	(GetLastError() == ERROR_BROKEN_PIPE)
						{
							GetExitCodeProcess(pi.hProcess, &dw);
							r = dw;
							break;
						}
						else
							break;
					}
					else
						if (dw > 0)
						{
							szBuffer[dw] = 0;
							__LOG__ << szBuffer << "\n";
						}

					if	((GetExitCodeProcess(pi.hProcess, &dw) == 0) || (dw != STILL_ACTIVE))
					{
						r = dw;
						break;
					}
				}
			}
			CloseHandle(hIn);
		}
		else {
			__LOG_N__ << "Couldn't create pipe: " << GetLastError() << "\n";
		}
#else
	    char		psBuffer[256];
		FILE		*iopipe;

		if	((iopipe = _popen(cmd.c_str(), "r" )) != 0)
			while (!feof(iopipe))
				if	(fgets(psBuffer, 256, iopipe))
					__LOG__ << psBuffer;

		r = _pclose(iopipe);
#endif
	}
#elif	__PLATFORM_OSX__
	__LOG_E__ << "ShellExecute(): STUB\n";
#elif	__PLATFORM_LINUX__
	r = path ? system(parm ? String::Format("%s %s", path, parm).c_str() : path) : -1;
#endif

	__SQ_GETEND
	__SQ_RETURNINT(r)
}


//---------------------------------------
SQInteger			Sleep(HSQUIRRELVM vm)
//---------------------------------------
{
	__SQ_GETSTART(1)
	__SQ_GETINT(ms)
	__SQ_GETEND
	Platform::Get().Sleep(ms);
	__SQ_RETURN
}

//------------------------------------------------------------
void				RegisterAllSquirrelBinding(HSQUIRRELVM vm)
//------------------------------------------------------------
{
/*#
	Topic: Script
#*/

/*#
	Section: ScriptDebug
	Desc: Debugging
#*/
	/*#
		Func: __Assert
		Proto: void:bool,string
		Desc: Assert an expression, throw an engine exception and suspend the VM if not verified.
	#*/
	sq_register(vm, SQAssert, "__Assert", _SC(".bs"));

/*#
	Section: ScriptGeneric
	Desc: Generic
#*/
	/*#
		Func: SQPow
		Proto: float:float value,float pow
		Desc: Returns value raised to power.
	#*/
	sq_register(vm, SQPow, "Pow", _SC(".ff"));
	/*#
	Func: SQExp
	Proto: float:float value
	Desc: Returns value in exponential .
	#*/
	sq_register(vm, SQExp, "Exp", _SC(".f"));
	/*#
		Func: SQMod
		Proto: int:int value,int divider
		Desc: Returns integer modulo of a value.
	#*/
	sq_register(vm, SQMod, "Mod", _SC(".ii"));
	/*#
		Func: SQAbsMod
		Proto: int:int value,int divider
		Desc: Returns integer modulo of the absolute of a given value.
	#*/
	sq_register(vm, SQAbsMod, "AbsMod", _SC(".ii"));
	/*#
		Func: ObjectIsValid
		Proto: bool:Object
		Desc: Returns true if the given engine object is valid, false otherwise.<br>This function can be used to test the validity of all engine types like geometry, material, item, scene and others.
	#*/
	sq_register(vm, ObjectIsValid, "ObjectIsValid", _SC(".x"));
	/*#
		Func: ObjectIsSame
		Proto: bool:Object,Object
		Desc: Returns true if two given engine object references are pointing to the same object.
	#*/
	sq_register(vm, ObjectIsSame, "ObjectIsSame", _SC(".xx"));

/*#
	Topic: System
#*/

/*#
	Section: SystemGeneric
	Desc: Generic
#*/
	/*#
		Func: ShellExecute
		Proto: int:string path,string param
		Desc: Execute an external program, returns its return code.
	#*/
	sq_register(vm, ShellExecute, "ShellExecute", _SC(".ss"));
	

	sq_register(vm, Sleep, "_Sleep", _SC(".i"));

	// Legacy support.
#if	1
	sq_register(vm, ObjectIsValid, "AIPathIsValid", _SC(".x"));
	sq_register(vm, ObjectIsValid, "GeometryIsValid", _SC(".x"));
	sq_register(vm, ObjectIsValid, "ItemIsValid", _SC(".x"));
	sq_register(vm, ObjectIsValid, "MetatagIsValid", _SC(".x"));
	sq_register(vm, ObjectIsValid, "TextureIsValid", _SC(".x"));
	sq_register(vm, ObjectIsValid, "WidgetIsValid", _SC(".x"));
	sq_register(vm, ObjectIsValid, "WindowIsValid", _SC(".x"));
#endif

	sq_pushroottable(vm);

	/*#
		Enum: SystemConstant
		Values: SystemClockFrequency
	#*/
	sq_pushstring(vm, "SystemClockFrequency", -1); sq_pushinteger(vm, Platform::Get().GetClockFrequency()); sq_newslot(vm, -3, true);

	// Register engine types.
	for (int n = 0; n < typetag_End; ++n)
	{
		String type_string = String::Format("EngineType%s", CObjectTypeToString((CObjectType)n));
		sq_pushstring(vm, type_string.TrimChar(' ').c_str(), -1); sq_pushinteger(vm, n); sq_newslot(vm, -3, true);
	}

	// Newer faster bindings.
	RegisterUCBinding(vm);

	//
	RegisterClockBinding(vm);
	RegisterMatrixBinding(vm);
	RegisterAnimationBinding(vm);
	RegisterAIBinding(vm);
	RegisterSystemBinding(vm);
	RegisterRendererBinding(vm);
	RegisterMixerBinding(vm);
	RegisterSceneBinding(vm);
	RegisterInstanceBinding(vm);
	RegisterGroupBinding(vm);
	RegisterCameraBinding(vm);
	RegisterObjectBinding(vm);
	RegisterMaterialBinding(vm);
	RegisterLightBinding(vm);
	RegisterProfilerBinding(vm);
	RegisterItemBinding(vm);
	RegisterMotionBinding(vm);
	RegisterCollisionBinding(vm);
	RegisterPhysicBinding(vm);
	RegisterPictureBinding(vm);
	RegisterTextureBinding(vm);
	RegisterSoundBinding(vm);
	RegisterGeometryBinding(vm);
	RegisterIOBinding(vm);
	RegisterUIBinding(vm);
	RegisterNMLBinding(vm);
	RegisterResourceFactoryBinding(vm);
	RegisterRaytracerBinding(vm);
	RegisterProjectBinding(vm);
	RegisterTriggerBinding(vm);
	RegisterEmitterBinding(vm);
	RegisterHashBinding(vm);
	RegisterHTTPBinding(vm);
	RegisterFontBinding(vm);
	RegisterPlatformBinding(vm);
	RegisterMaterialShaderBinding(vm);

#if	__PLATFORM_NINTENDO_WII__
	RegisterWiiBinding(vm);
#endif

	sq_pop(vm, 1);
}
