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


		#include "script_squirrel/squirrel_debugger.h"
		#include "script_squirrel/cobject/cobject.h"
		#include "script_squirrel/cobject/vector_decl.h"
		#include "math/vector.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Script;


//------------------------------------------------------------------------------
String				SquirrelDebugger::FormatParameter(DebuggerVariable *var)
{
	switch (var->type)
	{
		case OT_STRING:
			return String::Format("%s=%s", var->id.c_str(), var->v_string.c_str());
		case OT_FLOAT:
			return String::Format("%s=%.4f", var->id.c_str(), var->v_float);
		case OT_INTEGER:
			return String::Format("%s=%d", var->id.c_str(), var->v_int);
		case OT_BOOL:
			return String::Format("%s=%s", var->id.c_str(), var->v_bool ? "True" : "False");
	}
	return var->id;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static int DebuggerCompareVariable(DebuggerVariable *&v, const char *s) { return !String::Compare(v->id, s); }
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
DebuggerVariable	*SquirrelDebugger::InsertVariable(const char *name, AutoList <DebuggerVariable *> &debug_var, int level)
{
	// Catch nesting limit.
	if	(level == 3)
		return NULL;

	// Try to locate variable in the current watch.
	DebuggerVariable *var = ListFindEx(debug_var, DebuggerCompareVariable, name);

	// Retrieve stack variable type.
	bool check_change = false, has_changed = false;

	SQObjectType sq_type = sq_gettype(vm, -1);

	if	(var)
	{
		// If type has changed, drop the whole member watch structure.
		if	(var->type != (uint)sq_type)
			var->member_list.Clear();
		else
			check_change = true;
	}
	else
	{
		var = new DebuggerVariable;
		if	(!var)
			__ERR__(__LOG_E__ << "Failed to allocate debugger watch structure.\n", NULL);

		debug_var.Add(var);
		var->id = name;
	}

	// Update the variable.
	var->referenced = true;
	var->type = sq_type;

	String type = "Unknown", value;
	int limit = 64 / (level + 1);		// Increasingly limit array exploration the deeper in the hierarchy we get.

	switch (sq_type)
	{
		case OT_NULL:
			type = "Null";
			break;

		case OT_TABLE:
		{
			type = "Table";

			// Check item slot.
			sq_pushnull(vm);	// Iterator.
			while (SQ_SUCCEEDED(sq_next(vm, -2)))
			{
				// Here -1 is the value and -2 is the key.
				const SQChar *slot_name;
				if	(sq_getstring(vm, -2, &slot_name) == SQ_OK)
					InsertVariable(slot_name, var->member_list, level + 1);

				sq_pop(vm, 2);
			}
			sq_pop(vm, 1);
		}
		break;

		case OT_ARRAY:
		{
			type = "Array";
			SQInteger array_size = sq_getsize(vm, -1);
			value = String::Format("{ Length=%d, [...] }", array_size);

			// Check item slot.
			sq_pushnull(vm);	// Iterator.
			while (SQ_SUCCEEDED(sq_next(vm, -2)))
			{
				int idx;
				sq_getinteger(vm, -2, (SQInteger *)&idx);
				String slot_name = String::Format("[%d]", idx);
				InsertVariable(slot_name, var->member_list, level + 1);
				sq_pop(vm, 2);

				if	(--limit == 0)
					break;
			}
			sq_pop(vm, 1);
		}
		break;

		case OT_CLOSURE:
			type = "Function";
			break;

		case OT_NATIVECLOSURE:
			type = "C/C++";
			break;

		case OT_OUTER:
			type = "Outer";
			break;

		case OT_USERDATA:
			type = "User data";
			break;

		case OT_GENERATOR:
			type = "Generator";
			break;

		case OT_USERPOINTER:
			break;

		case OT_THREAD:
			type = "Thread";
			break;

		case OT_FUNCPROTO:
			type = "Prototype";
			break;

		case OT_CLASS:
			type = "Class definition";
			break;

		case OT_INSTANCE:
		{
			type = "Class instance";

			Vector4	*v = NULL;

			CObjectType typetag;
			if	(CObject::GetType(vm, -1, typetag))
			{
				type = CObjectTypeToString(typetag);
				value = FormatUserObjectParameter(typetag);
			}
			else if	(SQ_SUCCEEDED(sq_getinstanceup(vm, -1, (SQUserPointer*)&v, (SQUserPointer)&__Vector_decl)))
			{
				type = "Vector";
				value = String::Format("{%.2f, %.2f, %.2f, %.2f}", v->x, v->y, v->z, v->w);
			}
			else
			{
				// Trying iterating over instance members.
				AutoList <String *> member_list;

				sq_getclass(vm, -1);
				sq_pushnull(vm);	// Iterator.
				while (SQ_SUCCEEDED(sq_next(vm, -2)))
				{
					switch (sq_gettype(vm, -1))
					{
						case OT_CLOSURE:
						case OT_NATIVECLOSURE:
							break;

						default:
						{
							const SQChar *slot_name;
							if	(sq_getstring(vm, -2, &slot_name) == SQ_OK)
								member_list.Add(new String(slot_name));
						}
						break;
					}
					sq_pop(vm, 2);
				}
				sq_pop(vm, 2);

				// Get actual instance values.
				ListForeachPtr(String *, m, member_list)
				{
					sq_pushstring(vm, m->c_str(), m->Len());
					if	(sq_get(vm, -2) == SQ_OK)
						InsertVariable(m->c_str(), var->member_list, level + 1);
					sq_pop(vm, 1);
				}
			}
		}
		break;

		case OT_WEAKREF:
			type = "Weakref";
			break;

		case OT_BOOL:
		{
			type = "Bool";
			SQBool b;
			sq_getbool(vm, -1, &b);
			value = b ? "True" : "False";
			has_changed = check_change ? var->v_bool != asbool(b) : false;
			var->v_bool = b ? true : false;
		}
		break;

		case OT_STRING:
		{
			type = "String";
			const SQChar *s;
			sq_getstring(vm, -1, &s);
			value = String::Format("%s", s);
			has_changed = check_change ? var->v_string != s : false;
			var->v_string = s;
		}
		break;

		case OT_INTEGER:
		{
			type = "Integer";
			SQInteger i;
			sq_getinteger(vm, -1, &i);
			value = String::Format("%d", i);
			has_changed = check_change ? var->v_int != i : false;
			var->v_int = (int)i;
		}
		break;

		case OT_FLOAT:
		{
			type = "Float";
			SQFloat	f;
			sq_getfloat(vm, -1, &f);
			value = String::Format("%.4f", f);
			has_changed = check_change ? var->v_float != f : false;
			var->v_float = f;
		}
		break;
	}

	// Value for item with sub-items.
	if	(var->member_list.GetCount() && value.IsEmpty())
	{
		value = "{ ";

		int	max = 4;
		ListForeachPtr(DebuggerVariable *, v, var->member_list)
		{
			value += max > 0 ? FormatParameter(v).c_str() : "...";
			value += (iterator.Next() == NULL) || (max == 0) ? " }" : ", ";

			if	(!max--)
				break;
		}
	}

	var->type_string = type;
	var->modified = has_changed;
	var->value = value;

	return var;
}
void				SquirrelDebugger::RefreshStackFrameLocalsCache()
{
	// Mark all variables as unreferenced.
	DereferenceVarTree(local_var_tree);

	// Update local variables.
	const char *var_name;
	for (int n = 0; (var_name = sq_getlocal(vm, debug_stack_frame, n)) != NULL; ++n)
		InsertVariable(var_name, local_var_tree, 0);

	// Drop unreferenced variables.
	ListForeachPtr(DebuggerVariable *, v, local_var_tree)
		if	(!v->referenced)
			local_var_tree.Remove(v);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				SquirrelDebugger::VariableToMetatagString(DebuggerVariable *v, String &s)
{
	s += String::Format("<Variable=<Name=\"%s\">\n", v->id.c_str());

	switch (v->type)
	{
		case OT_NULL:			s += "<ScriptType=\"Null\">\n";				break;
		case OT_TABLE:			s += "<ScriptType=\"Table\">\n";			break;
		case OT_ARRAY:			s += "<ScriptType=\"Array\">\n";			break;
		case OT_USERDATA:		s += "<ScriptType=\"UserData\">\n";			break;
		case OT_CLOSURE:		s += "<ScriptType=\"Closure\">\n";			break;
		case OT_NATIVECLOSURE:	s += "<ScriptType=\"NativeClosure\">\n";	break;
		case OT_GENERATOR:		s += "<ScriptType=\"Generator\">\n";		break;
		case OT_USERPOINTER:	s += "<ScriptType=\"UserPointer\">\n";		break;
		case OT_THREAD:			s += "<ScriptType=\"Thread\">\n";			break;
		case OT_FUNCPROTO:		s += "<ScriptType=\"FuncProto\">\n";		break;
		case OT_CLASS:			s += "<ScriptType=\"Class\">\n";			break;
		case OT_INSTANCE:		s += "<ScriptType=\"Instance\">\n";			break;
		case OT_WEAKREF:		s += "<ScriptType=\"Weakref\">\n";			break;
		case OT_BOOL:			s += "<ScriptType=\"Bool\">\n";				break;
		case OT_STRING:			s += "<ScriptType=\"String\">\n";			break;
		case OT_INTEGER:		s += "<ScriptType=\"Integer\">\n";			break;
		case OT_FLOAT:			s += "<ScriptType=\"Float\">\n";			break;
	}

	// Display type.
	s += String::Format("<Type=\"%s\">\n", v->type_string.c_str());

	// Value.
	if	(!v->value.IsEmpty())
		s += String::Format("<ValueString=\"%s\">\n", v->value.c_str());

	if	(v->modified)
		s += "<Modified>";

	// Members.
	if	(v->member_list.GetCount())
	{
		s += "<Members=\n";
		ListForeachPtr(DebuggerVariable *, m, v->member_list)
			VariableToMetatagString(m, s);
		s += ">\n";
	}

	s += ">\n";
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				SquirrelDebugger::GetStackFrameSource(const char *&source, int &line)
{
	SQStackInfos si;

	if	(SQ_SUCCEEDED(sq_stackinfos(vm, debug_stack_frame, &si)))
	{
		source = (const char *)si.source;
		line = si.line;
	}
	else
	{
		source = NULL;
		line = -1;
	}
}
int					SquirrelDebugger::GetStackFrameIndex()
{
	int depth = 0;

	SQStackInfos si;
	for	(int c = 0; SQ_SUCCEEDED(sq_stackinfos(vm, c, &si)); ++c)
		if	(String(si.source) != "NATIVE")	// Skip native C frame.
		{
			depth = c;
			break;
		}

	return depth;
}
int					SquirrelDebugger::GetCallstackDepth()
{
	int depth = 0;
	SQStackInfos si;
	while (SQ_SUCCEEDED(sq_stackinfos(vm, depth, &si)))
		depth++;
	return depth;
}
//------------------------------------------------------------------------------

SquirrelDebugger::SquirrelDebugger(SquirrelVM &svm)
{
	vm = svm.VM();
}
