#include "interface.h"
#include "SimSetLooper.cpp"

static PyObject*
BLPiface_TSeval(PyObject *self, PyObject *args)
{
	char* str;
	if(!PythonABI::PyArg_ParseTuple(args, "s:TSeval", &str))
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (string)eval string");
		return NULL;
	}

	return PythonABI::PyUnicode_FromString(tsf_Eval(str));
}

bool BLPiface_TScall_helper(char *argsC[], PyObject *args, int arg_start, int *arg_end)
{
	for(int i = arg_start; i < 21; i++)
	{
		if(i >= PythonABI::PyTuple_Size(args))
		{
			*arg_end = i - arg_start;
			return true;
		}

		//Borrowed reference, don't change reference counts
		PyObject* item = PythonABI::PyTuple_GetItem(args, i);

		PyObject* item_ascii = PythonABI::PyUnicode_AsASCIIString(item);
		char* str = (char*)PythonABI::PyBytes_AsString(item_ascii);

		//Immediately discard reference
		PythonABI::Py_DecRef(item_ascii);

		if(str == NULL)
		{
			PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
			"Usage: string decode error");
			return false;
		}

		argsC[i-arg_start] = str;
	}

	return true;
}

static PyObject*
BLPiface_TScall(PyObject *self, PyObject *args)
{
	char* argsC[20];

	int arg_end;
	if(!BLPiface_TScall_helper(argsC, args, 0, &arg_end))
		return NULL;

	//todo: clean up this macro, making use of argcount to ignore args
	const char* out;
	MACRO__tsf_BlCon__executef(out, MACRO_A__executef, , arg_end, argsC);

	return PythonABI::PyUnicode_FromString(out);
}

static PyObject*
BLPiface_TScallSimObj(PyObject *self, PyObject *args)
{
	int objID;
	char* argsC[20];

	if(PythonABI::PyTuple_Size(args) < 2)
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (int)object ID, (string)func, ... 19 args");
		return NULL;
	}

	//Borrowed reference, don't change reference counts
	PyObject* objID_item = PythonABI::PyTuple_GetItem(args, 0);

	objID = PythonABI::PyLong_AsSsize_t(objID_item);

	if(PythonABI::PyErr_Occurred() != NULL)
		return NULL;

	SimObject* obj = (SimObject*)FIXEDtsf_FindObject(objID);
	if(obj == NULL)
	{
		PythonABI::PyErr_SetString(TorqueObjectNotFoundError, "Object ID not found");
		return NULL;
	}

	int arg_end;
	if(!BLPiface_TScall_helper(argsC, args, 1, &arg_end))
		return NULL;

	//todo: clean up this macro, making use of argcount to ignore args
	const char* out;
	MACRO__tsf_BlCon__executef(out, MACRO_B__executefSimObj, (ADDR*)obj, arg_end, argsC);

	return PythonABI::PyUnicode_FromString(out);
}

static PyObject*
BLPiface_TSgetDataField(PyObject *self, PyObject *args)
{
	int objID;
	char* slot;
	char* array;
	if(!PythonABI::PyArg_ParseTuple(args, "izz:TSgetDataField", &objID, &slot, &array))
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (int)object ID, (string)slot, (string)array");
		return NULL;
	}

	SimObject* obj = (SimObject*)FIXEDtsf_FindObject(objID);
	if(obj == NULL)
	{
		PythonABI::PyErr_SetString(TorqueObjectNotFoundError, "Object ID not found");
		return NULL;
	}

	return PythonABI::PyUnicode_FromString(tsf_GetDataField((ADDR)obj, slot, array));
}

static PyObject*
BLPiface_TSgetDataField_int(PyObject *self, PyObject *args)
{
	int objID;
	char* slot;
	char* array;
	if(!PythonABI::PyArg_ParseTuple(args, "izz:TSgetDataField_int", &objID, &slot, &array))
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (int)object ID, (string)slot, (string)array");
		return NULL;
	}

	SimObject* obj = (SimObject*)FIXEDtsf_FindObject(objID);
	if(obj == NULL)
	{
		PythonABI::PyErr_SetString(TorqueObjectNotFoundError, "Object ID not found");
		return NULL;
	}

	return PythonABI::PyLong_FromLong(atoi(tsf_GetDataField((ADDR)obj, slot, array)));
}

static PyObject*
BLPiface_TSsetDataField(PyObject *self, PyObject *args)
{
	int objID;
	char* slot;
	char* array;
	char* val;
	if(!PythonABI::PyArg_ParseTuple(args, "izzz:TSsetDataField", &objID, &slot, &array, &val))
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (int)object ID, (string)slot, (string)array, (string)value");
		return NULL;
	}

	SimObject* obj = (SimObject*)FIXEDtsf_FindObject(objID);
	if(obj == NULL)
	{
		PythonABI::PyErr_SetString(TorqueObjectNotFoundError, "Object ID not found");
		return NULL;
	}

	tsf_SetDataField((ADDR)obj, slot, array, val);
	return PythonABI::Py_BuildValue("");
}

static PyObject*
BLPiface_TSgetvar(PyObject *self, PyObject *args)
{
	char* var_name;
	if(!PythonABI::PyArg_ParseTuple(args, "s:TSgetvar", &var_name))
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (string)variable name");
		return NULL;
	}

	return PythonABI::PyUnicode_FromString(tsf_BlCon__getVariable(var_name));
}

static PyObject*
BLPiface_TSgetvar_int(PyObject *self, PyObject *args)
{
	char* var_name;
	if(!PythonABI::PyArg_ParseTuple(args, "s:TSgetvar_int", &var_name))
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (string)variable name");
		return NULL;
	}

	return PythonABI::PyLong_FromLong(atoi(tsf_BlCon__getVariable(var_name)));
}

//TODO: not implemented
/*static PyObject*
BLPiface_TSsetvar(PyObject *self, PyObject *args)
{

}*/

static PyObject*
BLPiface_TSfindObj(PyObject *self, PyObject *args)
{
	char* obj_name;
	if(!PythonABI::PyArg_ParseTuple(args, "s:TSfindObj", &obj_name))
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (string)object name");
		return NULL;
	}

	SimObject* obj = (SimObject*)tsf_FindObject(obj_name);

	int id = 0;
	if(obj != NULL)
		id = obj->id;

	return PythonABI::PyLong_FromSsize_t(id);
}


//TODO: needs to concatenate and auto-convert all arguments to string
static PyObject*
BLPiface_BlPrint(PyObject *self, PyObject *args)
{
	char* str;
	if(!PythonABI::PyArg_ParseTuple(args, "s:BlPrintf", &str))
	{
		PythonABI::PyErr_SetString(*PythonABI::PyExc_TypeError,
		"Usage: (string)text");
		return NULL;
	}

	BlPrintf(str);

	return PythonABI::Py_BuildValue("");
}

PyObject* CommonTS_callPython(ADDR objADDR, int argc, const char *argv[])
{
	if(argc < 1)
		return PythonABI::Py_BuildValue("");


	//TODO: check change here
	SimObject* obj = (SimObject*)objADDR;

	int id = 0;
	if(obj != NULL)
		id = obj->id;

	const char *tsFunc = argv[0];

	if(tsFunc == "\0")
		return PythonABI::Py_BuildValue("");


	//TODO: warn about call to missing function
	std::string name_str(tsFunc);
	if(pyfunc_map[name_str] == NULL)
		return PythonABI::Py_BuildValue("");

	PyObject *result;

	PyObject *call_args = PythonABI::Py_BuildValue("()");
	PyObject *call_kwargs = PythonABI::PyDict_New();
	PythonABI::PyDict_SetItemString(call_kwargs, "obj", PythonABI::Py_BuildValue("i", id));

	PyObject *arglist = PythonABI::PyList_New(0);

	//TODO: Reference checking here:
	for(int i = 0; i < argc; i++)
		PythonABI::PyList_Append(arglist, PythonABI::Py_BuildValue("z", argv[i]));

	//TODO: Reference checking here:
	PythonABI::PyDict_SetItemString(call_kwargs, "args", arglist);

	//TODO: Reference checking here:
	result = PythonABI::PyObject_Call(pyfunc_map[name_str]->callback, call_args, call_kwargs);
	//result can be NULL if call doesn't have correct keywords

	PythonABI::Py_DecRef(arglist);
	PythonABI::Py_DecRef(call_kwargs);
	PythonABI::Py_DecRef(call_args);

	return result;
}

void TS_callPythonVoid(ADDR objADDR, int argc, const char *argv[])
{
	PyObject* ret = CommonTS_callPython(objADDR, argc, argv);	
	PythonABI::Py_DecRef(ret);
}

const char* TS_callPythonString(ADDR objADDR, int argc, const char *argv[])
{
	PyObject* ret = CommonTS_callPython(objADDR, argc, argv);
	if(PythonABI::Py_IsNone(ret))
	{
		PythonABI::Py_DecRef(ret);
		return "";
	}

	PyObject* item_ascii = PythonABI::PyUnicode_AsASCIIString(ret);
	char* str = (char*)PythonABI::PyBytes_AsString(item_ascii);
	PythonABI::Py_DecRef(item_ascii);


	PythonABI::Py_DecRef(ret);

	return str;
}

int TS_callPythonInt(ADDR objADDR, int argc, const char *argv[])
{
	PyObject* ret = CommonTS_callPython(objADDR, argc, argv);
	if(PythonABI::Py_IsNone(ret))
	{
		PythonABI::Py_DecRef(ret);
		return 0;
	}

	int out = PythonABI::PyLong_AsSsize_t(ret);
	PythonABI::Py_DecRef(ret);
	return out;
}

float TS_callPythonFloat(ADDR objADDR, int argc, const char *argv[])
{
	PyObject* ret = CommonTS_callPython(objADDR, argc, argv);
	if(PythonABI::Py_IsNone(ret))
	{
		PythonABI::Py_DecRef(ret);
		return 0.f;
	}

	float out = (float)PythonABI::PyFloat_AsDouble(ret);
	PythonABI::Py_DecRef(ret);
	return out;
}

bool TS_callPythonBool(ADDR objADDR, int argc, const char *argv[])
{
	PyObject* ret = CommonTS_callPython(objADDR, argc, argv);
	if(PythonABI::Py_IsNone(ret))
	{
		PythonABI::Py_DecRef(ret);
		return false;
	}

	bool out = false;
	if(PythonABI::PyObject_IsTrue(ret))
		out = true;

	PythonABI::Py_DecRef(ret);
	return out;
}


//TODO: return something if overwriting function
static PyObject*
BLPiface_TSregisterFunc(PyObject *self, PyObject *args)
{
	char *pname, *cname, *tsFunc;
	PyObject *pyFunc;
	void *type;
	char *usage;
	unsigned int minA, maxA;

	if(!PythonABI::PyArg_ParseTuple(args, "zzsOOzII:TSregisterFunc", &pname, &cname, &tsFunc, &pyFunc, &type, &usage, &minA, &maxA))
	{
		PythonABI::PyErr_SetString(TorqueRegisterFunctionError,
		"Usage: (string)pname, (string)cname, (string)tsFunc, (callable)pyFunc, None|str|int|float|bool, min args, max args");
		return NULL;
	}

	if(!PythonABI::PyCallable_Check(pyFunc))
	{
		PythonABI::PyErr_SetString(TorqueRegisterFunctionError, "Second parameter must be callable");
		return NULL;
        }


	TSRETURN_TYPES return_type;

	if(PythonABI::Py_IsNone((PyObject*)type))
		return_type = TSRETURN_VOID;

	else if(type == PythonABI::PyUnicode_Type)
		return_type = TSRETURN_STRING;

	else if(type == PythonABI::PyLong_Type)
		return_type = TSRETURN_INT;

	else if(type == PythonABI::PyFloat_Type)
		return_type = TSRETURN_FLOAT;

	else if(type == PythonABI::PyBool_Type)
		return_type = TSRETURN_BOOL;

	else
	{
		PythonABI::PyErr_SetString(TorqueRegisterFunctionError,
		"Unrecognized return type. Usage: (string)pname, (string)cname, (string)tsFunc, (callable)pyFunc, None|str|int|float|bool, min args, max args");
		return NULL;
	}

	if(minA < 1)
	{
		PythonABI::PyErr_SetString(TorqueRegisterFunctionError, "Minimum argument must be non-zero");
		return NULL;
        }


	std::string name_str(tsFunc);

	if(pyfunc_map[name_str] != NULL)
		PythonABI::Py_DecRef(pyfunc_map[name_str]->callback); //decrease ref count to prev callback

	delete pyfunc_map[name_str];
	pyfunc_map.erase(name_str);

	PythonABI::Py_IncRef(pyFunc); //Add a reference to new callback

	//TODO: throw error if pyFunc does not accept obj and args keywords
	pyfunc_map[name_str] = new pyfunc_struct { pyFunc, return_type };
	

	if(return_type == TSRETURN_VOID)
		tsf_AddConsoleFunc(pname, cname, tsFunc, TS_callPythonVoid, usage, minA, maxA);

	else if(return_type == TSRETURN_STRING)
		tsf_AddConsoleFunc(pname, cname, tsFunc, TS_callPythonString, usage, minA, maxA);

	else if(return_type == TSRETURN_INT)
		tsf_AddConsoleFunc(pname, cname, tsFunc, TS_callPythonInt, usage, minA, maxA);

	else if(return_type == TSRETURN_FLOAT)
		tsf_AddConsoleFunc(pname, cname, tsFunc, TS_callPythonFloat, usage, minA, maxA);

	else if(return_type == TSRETURN_BOOL)
		tsf_AddConsoleFunc(pname, cname, tsFunc, TS_callPythonBool, usage, minA, maxA);

	return PythonABI::Py_BuildValue("");
}

static PyMethodDef BLPifaceMethods[] = {
    {"TSgetvar", BLPiface_TSgetvar, METH_VARARGS,
     "Get a TorqueScript global variable"},

    {"TSgetvar_int", BLPiface_TSgetvar_int, METH_VARARGS,
     "Get a TorqueScript global variable as an integer"},

    //{"TSsetvar", BLPiface_TSsetvar, METH_VARARGS,
    // "Set a TorqueScript global variable"},

    {"TSfindObj", BLPiface_TSfindObj, METH_VARARGS,
     "Find an object ID by its name"},

    {"BlPrint", BLPiface_BlPrint, METH_VARARGS,
     "Print through Blockland's streams. Accepts one string argument."},

    {"TSregisterFunc", BLPiface_TSregisterFunc, METH_VARARGS,
     "Register a function. \n"
	"Usage: (string)pname, (string)cname, (string)tsFunc, (callable)pyFunc, "
	"None|str|int|float|bool, min args, max args"},

    {"TSeval", BLPiface_TSeval, METH_VARARGS,
     "Eval a string"},

    {"TScall", BLPiface_TScall, METH_VARARGS,
     "Call a function"},

    {"TScallSimObj", BLPiface_TScallSimObj, METH_VARARGS,
     "Call a function on an object"},

    {"TSgetDataField", BLPiface_TSgetDataField, METH_VARARGS,
     "Get a field from an object"},

    {"TSgetDataField_int", BLPiface_TSgetDataField_int, METH_VARARGS,
     "Get a field from an object as an integer"},

    {"TSsetDataField", BLPiface_TSsetDataField, METH_VARARGS,
     "Set a field on an object"},

//alternate eval?
//const char *tsf_Evalf(const char *fmt, ...)

//tsf_AddVar string|int|float|bool #save for later (adds a var that points to a C++ var)

    {NULL, NULL, 0, NULL} //sentinel
};

static PyModuleDef BLPifaceModule = {
    PyModuleDef_HEAD_INIT, "BLpython", NULL, -1, BLPifaceMethods,
    NULL, NULL, NULL, NULL
};


static PyObject*
PyInit_BLPiface(void)
{
	SimSetLooper_SSIType.tp_new = PythonABI::PyType_GenericNew;

	if(PythonABI::PyType_Ready(&SimSetLooper_SSIType) < 0)
		return NULL;

	PythonABI::Py_IncRef((PyObject*)&SimSetLooper_SSIType);

	//Create() is a wrapper for Create2 with the ABI version
	PyObject* module = PythonABI::PyModule_Create2(&BLPifaceModule, PYTHON_ABI_VERSION);
	PythonABI::PyModule_AddObject(module, "SimSetLooper", (PyObject *)&SimSetLooper_SSIType);

	TorqueObjectNotFoundError = PythonABI::PyErr_NewException("BLpython.TorqueObjectNotFoundError", NULL, NULL);
	PythonABI::PyModule_AddObject(module, "TorqueObjectNotFoundError", TorqueObjectNotFoundError);

	TorqueRegisterFunctionError = PythonABI::PyErr_NewException("BLpython.TorqueRegisterFunctionError", NULL, NULL);
	PythonABI::PyModule_AddObject(module, "TorqueRegisterFunctionError", TorqueRegisterFunctionError);

	return module;
}

