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


		#include <stdarg.h>
		#include <stdio.h>
		#include <ctime>
		#include "squirrel.h"
		#include "sqstdio.h"
		#include "sqstdmath.h"
		#include "sqstdstring.h"
		#include "sqstdaux.h"
		#include "sqstdblob.h"
		#include "sqstdsystem.h"
		#include "script_squirrel/squirrel_vm.h"
		#include "script_squirrel/cobject/cobject.h"
		#include "script/script_variant.h"
		#include "filesystem/io_handle.h"
		#include "filesystem/filesystem.h"	
		#include "viewer_base/viewer_base.h"
		#include "platform.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Script;


//------------------------------------------------------------------------------
void				SquirrelVM::GetCallStack(AutoList <CallStackEntry *> &callstack)
{
	callstack.Clear();

	SQStackInfos si;
	for	(SQInteger level = 0; SQ_SUCCEEDED(sq_stackinfos(vm, level, &si)); ++level)
		callstack.Add(new CallStackEntry(si.source, si.funcname, si.line));
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static void			SqPrintFunc(HSQUIRRELVM v, const SQChar *s, ...)
{
    va_list arglist;
    va_start(arglist, s);
	char lcl[4096];

#ifdef	__PLATFORM_WINDOWS__
	vsprintf_s(lcl, 4095, s, arglist);
#else
	vsprintf(lcl, s, arglist);
#endif

	__LOG__ << "[S] " << lcl << "\n";
    va_end(arglist);
}
static void			SqDebugHook(HSQUIRRELVM v, SQInteger type, const SQChar *sourcename, SQInteger line, const SQChar *funcname)
{
	if	(SquirrelVM *vm = (SquirrelVM *)sq_getforeignptr(v))
		if	(vm->GetEventHandler())
			vm->GetEventHandler()->OnStep((char)type, sourcename, line, funcname);
}
static void			SqCompilerError(HSQUIRRELVM v, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
{
	if	(SquirrelVM *vm = (SquirrelVM *)sq_getforeignptr(v))
	{
		String msg("Script runtime Compilation exception:\n");
		msg += String::Format("    - (line %d) in \"%s\"\n", line, source);
		__LOG_E__ << "Squirrel Compiler Error: '" << msg << "'\n\n";

		if	(vm->GetEventHandler())
			vm->GetEventHandler()->OnCompilerError(desc, source, line);
		
		time_t now = time(NULL);
		struct tm *timeinfo = localtime(&now);
		char timestamp[64];
		strftime(timestamp, sizeof(timestamp), "%Y-%m-%d_%H-%M-%S", timeinfo);
		
		String logFileName = String::Format("g_engine_log_%s.txt", timestamp);
		
		// Check if g_engine_log.txt exists and copy it
		if (Platform::Get().io->Exists("g_engine_log.txt"))
		{
			if (Platform::Get().io->FileCopy("g_engine_log.txt", logFileName.c_str()))
			{
				__LOG_E__ << "Engine log copied to: " << logFileName << "\n";
			}
			else
			{
				__LOG_E__ << "Failed to copy engine log to: " << logFileName << "\n";
			}
		}
		else
		{
			__LOG_E__ << "Engine log file 'g_engine_log.txt' not found for copying\n";
		}
	}
}
static SQInteger	SqRuntimeError(HSQUIRRELVM v)
{
	SquirrelVM *vm = (SquirrelVM *)sq_getforeignptr(v);

	// Get error string.
	const SQChar *error = NULL;
	if	(SQ_FAILED(sq_getstring(v, 2, &error)))
		error = "Unspecified runtime error";

	// Could be null if called from a coroutine.
	if	(vm && (vm->GetState() == IVM::StateOk))
	{
		vm->SetState(IVM::StateExceptionThrown);

		// Redirect to the signal handler.
		if	(vm->GetEventHandler())
		{
			vm->GetEventHandler()->OnRuntimeException(error);
			return sq_suspendvm(v);		// All debugging hope is lost beyond this point as the Squirrel VM will unwind all exception stack frame.
		}
		else
		{			
			String msg = String::Format("Script runtime exception:\n\n%s", error);

			AutoList <IVM::CallStackEntry *> callstack;
			vm->GetCallStack(callstack);

			msg += "\n\nCallstack:\n\n";
			ListForeachPtr(IVM::CallStackEntry *, cs, callstack)
				msg += String::Format("    - %s() (line %d) in \"%s\"\n", cs->function.c_str(), cs->line, cs->source.c_str());
	
			__LOG_E__ << "Squirrel Compiler Error: '" << msg << "'\n";

			time_t now = time(NULL);
			struct tm *timeinfo = localtime(&now);
			char timestamp[64];
			strftime(timestamp, sizeof(timestamp), "%Y-%m-%d_%H-%M-%S", timeinfo);
			
			String logFileName = String::Format("g_engine_log_%s.txt", timestamp);
			
			// Check if g_engine_log.txt exists and copy it
			if (Platform::Get().io->Exists("g_engine_log.txt"))
			{
				if (Platform::Get().io->FileCopy("g_engine_log.txt", logFileName.c_str()))
				{
					__LOG_E__ << "Engine log copied to: " << logFileName << "\n";
				}
				else
				{
					__LOG_E__ << "Failed to copy engine log to: " << logFileName << "\n";
				}
			}
			else
			{
				__LOG_E__ << "Engine log file 'g_engine_log.txt' not found for copying\n";
			}

		}
	}
	return sq_suspendvm(v);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				SquirrelVM::SetDebugInterface(IDebug *i, bool debug)
{
	sq_enabledebuginfo(vm, debug);
/*	if	(vm)
	{
		sq_enabledebuginfo(vm, debug);
		if	(debug)
			sq_setnativedebughook(vm, SqDebugHook);
	}
	IVM::SetDebugInterface(i, debug);*/
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				SquirrelVM::DumpCallStack(HSQUIRRELVM v, const char *desc, String *msg)
{
	SQStackInfos si;
//	SQInteger level = 1;		// Level 0 is the native closure we are in.

	__LOG_V__ << "\n";
	String _msg(String::Format("Call stack (%s):\n", desc ? desc : "Requested"));
	__LOG_V__ << _msg.c_str();

	for (SQInteger level = 1; SQ_SUCCEEDED(sq_stackinfos(v, level, &si)); ++level)
	{
		String _lg;

		if	(si.funcname)
		{
			if	(si.line != -1)
					_lg = String::Format("  %d: %s() %s(%d)", level, si.funcname, si.source, si.line);
			else	_lg = String::Format("  %d: %s() C/C++", level, si.funcname);
		}
		else
			_lg = String::Format("  %d: NOINFO", level);

		__LOG_V__ << _lg << "\n";
		_msg += _lg;
	}

	if	(msg)
		*msg = _msg;
	__LOG_V__ << "\n";
}
bool				SquirrelVM::Compile(const char *source, uint size, const Object *context, const char *sourcename)
{
	if	(!source)
		return false;

	if	(SQ_SUCCEEDED(sq_compilebuffer(vm, source, size, sourcename, true)))
	{
		sq_pushroottable(vm);
		sq_call(vm, 1, SQFalse, SQTrue);
		sq_pop(vm, 1);
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Object				*SquirrelVM::GetObjectFromStack(int idx)
{
	HSQOBJECT o;
	sq_getstackobj(vm, idx, &o);
	return new SquirrelObject(*this, o);
}
Object				*SquirrelVM::GetObjectFromName(const char *name, const Object *context)
{
	if	(context)
			sq_pushobject(vm, ((const SquirrelObject *)context)->object);
	else	sq_pushroottable(vm);

	sq_pushstring(vm, name, -1);
	if	(SQ_FAILED(sq_get(vm, -2)))
	{
		sq_pop(vm, 1);
		return NULL;
	}

	Object *o = GetObjectFromStack(-1);
	sq_pop(vm, 2);
	return o;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				SquirrelVM::SetupFunctionCall(const char *func, const Object *cache, const Object *context)
{
	if	(!vm || (GetState() != StateOk))
		return false;

	if	(cache)
		sq_pushobject(vm, ((SquirrelObject *)cache)->object);
	else
	{
		if	(context)
				sq_pushobject(vm, ((SquirrelObject *)context)->object);
		else	sq_pushroottable(vm);

		sq_pushstring(vm, func, -1);
		if	(SQ_FAILED(sq_get(vm, -2)))
		{
			sq_pop(vm, 1);	// Cleanup root table.
			return false;
		}
		sq_remove(vm, -2);	// Remove root table.
	}

	// Push function environment.
	if	(context)
			sq_pushobject(vm, ((SquirrelObject *)context)->object);
	else	sq_pushroottable(vm);

	call_arg_count = 1;
	return true;
}
bool				SquirrelVM::SetFunctionCallContext(const Script::Variant &v)
{
	return PushArgument(v);
}
bool				SquirrelVM::PushNullArgument()
{
	call_arg_count++;
	return PushNull();
}
bool				SquirrelVM::PushArgument(const Script::Variant &v)
{
	call_arg_count++;
	return PushVariant(v);
}
bool				SquirrelVM::DoFunctionCall(Script::Variant *v)
{
	if	(GetState() != StateOk)
		return false;
	if	(SQ_FAILED(sq_call(vm, call_arg_count, SQTrue, SQTrue)))
		return false;

	if	(v)
		GetVariantFromStack(-1, *v);
	sq_pop(vm, 2);
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				SquirrelVM::GetVariantFromStack(int idx, Script::Variant &v)
{
	switch (sq_gettype(vm, idx))
	{
		case OT_STRING:
		{
			const SQChar *s;
			sq_getstring(vm, idx, &s);
			v.Set((const char *)s);
		}
		break;

		case OT_BOOL:
		{
			SQBool b;
			sq_getbool(vm, idx, &b);
			v.Set(asbool(b));
		}
		break;

		case OT_INTEGER:
		{
			SQInteger i;
			sq_getinteger(vm, idx, &i);
			v.Set((int)i);
		}
		break;

		case OT_FLOAT:
		{
			SQFloat f;
			sq_getfloat(vm, idx, &f);
			v.Set((float)f);
		}
		break;

		default:
		{
			HSQOBJECT o;
			sq_getstackobj(vm, idx, &o);
			v.Set(new SquirrelObject(*this, o), true);
		}
		break;
	}
	return true;
}
bool				SquirrelVM::Get(const char *name, Script::Variant &prop, const Object *context)
{
	if	(context)
			sq_pushobject(vm, ((SquirrelObject *)context)->object);
	else	sq_pushroottable(vm);
	sq_pushstring(vm, name, -1);
	if	(SQ_FAILED(sq_get(vm, -2)))
	{
		sq_pop(vm, 1);
		return false;
	}
	bool r = GetVariantFromStack(-1, prop);
	sq_pop(vm, 2);
	return r;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				SquirrelVM::PushNull()
{
	sq_pushnull(vm);
	return true;
}
bool				SquirrelVM::PushVariant(const Script::Variant &v)
{
	switch (v.type)
	{
		case Variant::Type_Variant:
			switch (v.variant.GetType())
			{
				case GS::Variant::VariantString:
					sq_pushstring(vm, v.variant.s_value.c_str(), -1);
					break;
				case GS::Variant::VariantInteger:
					sq_pushinteger(vm, v.variant.i_value);
					break;
				case GS::Variant::VariantBool:
					sq_pushbool(vm, v.variant.b_value);
					break;
				case GS::Variant::VariantFloat:
					sq_pushfloat(vm, v.variant.f_value);
					break;

				default:
					__LOG_E__ << "Unsupported variant type: " << v.type << ".\n";
					return false;
			}
			break;

		case Variant::Type_ScriptObject:
			sq_pushobject(VM(), ((SquirrelObject *)v.object)->object);
			break;

		case Variant::Type_UserObject:
			return CObject::Push(VM(), v.ptr, CObjectType(v.typetag));
	}
	return true;
}
bool				SquirrelVM::Set(const char *name, const Script::Variant &v, const Object *context)
{
	if	(context)
			sq_pushobject(vm, ((SquirrelObject *)context)->object);
	else	sq_pushroottable(vm);
	sq_pushstring(vm, name, -1);
	if	(!PushVariant(v))
	{
		sq_pop(vm, 2);
		return false;
	}
	SQObjectType type = sq_gettype(vm, -3);
	if	((type == OT_CLASS) || (type == OT_TABLE))
			sq_newslot(vm, -3, false);
	else	sq_set(vm, -3);
	sq_pop(vm, 1);
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Object		*SquirrelVM::CreateArray()
{
	sq_newarray(vm, 0);
	Object *o = GetObjectFromStack(-1);
	sq_pop(vm, 1);
	return o;
}
bool				SquirrelVM::Append(const Script::Variant &prop, const Object *context)
{
	if	(context)
			sq_pushobject(vm, ((SquirrelObject *)context)->object);
	else	sq_pushroottable(vm);
	if	(!PushVariant(prop))
	{
		sq_pop(vm, 1);
		return false;
	}
	sq_arrayappend(vm, -2);
	sq_pop(vm, 1);
	return true;
}
Object		*SquirrelVM::CreateTable()
{
	sq_newtable(vm);
	Object *o = GetObjectFromStack(-1);
	sq_pop(vm, 1);
	return o;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				SquirrelVM::Open()
{
	if	(vm)
		return true;

	vm = sq_open(1024);
	if	(!vm)
		return false;

	sq_setforeignptr(vm, (SQUserPointer)this);

	sq_setcompilererrorhandler(vm, SqCompilerError);
	sq_setprintfunc(vm, SqPrintFunc, SqPrintFunc);
	sq_newclosure(vm, SqRuntimeError, 0);
	sq_seterrorhandler(vm);

	sq_pushroottable(vm);
	sqstd_register_bloblib(vm);
	sqstd_register_iolib(vm);
	sqstd_register_systemlib(vm);
	sqstd_register_mathlib(vm);
	sqstd_register_stringlib(vm);
	sq_pop(vm, 1);

	return true;
}
void				SquirrelVM::Close()
{
	//if	(vm)
	//	sq_close(vm);

	vm = NULL;
	state = StateOk;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SquirrelObject::SquirrelObject(SquirrelVM &_vm, HSQOBJECT _object) : Object(_vm)
{
	object = _object;
	sq_addref(((SquirrelVM &)vm).VM(), &object);
}
SquirrelObject::~SquirrelObject()
{
	sq_release(((SquirrelVM &)vm).VM(), &object);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SquirrelVM::SquirrelVM()
{
	vm = NULL;
	call_arg_count = 0;
}
SquirrelVM::~SquirrelVM()
{
	Close();
}
//------------------------------------------------------------------------------
