#include "cbase.h"
#include "boost/python/detail/wrap_python.hpp"
#include "hl2_python.h"
#include "hl2_pyerrprint.h"
#include "filesystem.h"

// base
#include "player.h"

// utils
#include "util.h"

// npc's
#include "basecombatcharacter.h"
#include "ai_basenpc.h"

// This will make it use boost_python.lib, so we can easily use our
// own compiled boost python library
//#define BOOST_AUTO_LINK_NOMANGLE

// hack. Without this boost is telling us it is a bad idea to mix static libraries with boost libraries
// didn't encountered issue's with it so far. Seems related to the /MT compiler option
#define _DLL

// boost
#include <boost/python.hpp>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/import.hpp>
#include <boost/throw_exception.hpp>
#include <boost/python/return_value_policy.hpp>
#include <boost/ref.hpp>

using namespace boost::python;

// globals
object main_module;
object main_namespace;

/*
 * Not sure what's up with this, but it seems we need to define it to avoid 
 * link errors
 */
namespace boost
{
	void throw_exception(std::exception const & e) 
	{
		Msg("Exception\n");
	}
}

/*
 * proxies
 */
void proxy_Msg( const char *pMsg )
{
	Msg( pMsg );
}

CBaseEntity *proxy_CreateEntityByName( const char* className )
{
	CBaseEntity *pEnt = CreateEntityByName( className );
	return pEnt;
}

void proxy_PrecacheModel ( const char* name )
{
	engine->PrecacheModel( name );
}

// except hook
PY_EXPORT
PyObject *
sys_excepthook_hl2(PyObject* type, PyObject* value, PyObject* tb)
{
	PyErr_Display_HL2(type, value, tb);
	Py_INCREF(Py_None);
	return Py_None;
}

//-----------------------------------------------------------------------------
// Purpose: All base stuff
//			Notice we need to use return_internal_reference() instead of manage_new_object
//			Otherwise a crash will occur on exit() because python tries to clean entities up.
//-----------------------------------------------------------------------------
//void (CBaseEntity::*SetParent1)( CBaseEntity *pParentEntity )        = &CBaseEntity::SetParent;
//BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(SetParent_overloads, &CBaseEntity::SetParent, 1, 3)

BOOST_PYTHON_MODULE(base)
{
	// Printing
	def("Msg", proxy_Msg);

	// Vector class, always useful for Vector math
    class_<Vector>("Vector")
		.def(init<float, float, float>())
        .def_readwrite("x", &Vector::x)
		.def_readwrite("y", &Vector::y)
		.def_readwrite("z", &Vector::z)

		// methods
		.def("Zero", &Vector::Zero)
		.def("Negate", &Vector::Negate)
		.def("IsZero", &Vector::IsZero)
		.def("NormalizeInPlace", &Vector::NormalizeInPlace)
		.def("IsLengthGreaterThan", &Vector::IsLengthGreaterThan)
		.def("IsLengthLessThan", &Vector::IsLengthLessThan)
		.def("DistTo", &Vector::DistTo)
		.def("DistToSqr", &Vector::DistToSqr)
		.def("MulAdd", &Vector::MulAdd)
		.def("Dot", &Vector::Dot)
		.def("Length2D", &Vector::Length2D)
		.def("Length2DSqr", &Vector::Length2DSqr)
		.def("Cross", &Vector::Cross)
		.def("Min", &Vector::Min)
		.def("Max", &Vector::Max)

		// operators
		.def(self + self)
		.def(self - self)
		.def(self *= self)
		.def(self *= float())
		.def(self /= self)
		.def(self /= float())
		.def(self += self)
		.def(self -= self)
		.def(self * self)
		.def(self * float())
		.def(self / self)
		.def(self / float())
    ;

	// QAngle
    class_<QAngle>("QAngle")
		.def(init<float, float, float>())
        .def_readwrite("x", &QAngle::x)
		.def_readwrite("y", &QAngle::y)
		.def_readwrite("z", &QAngle::z)

		// operators
		.def(self + self)
		.def(self - self)
		.def(self += self)
		.def(self -= self)
		.def(self *= float())
		.def(self /= float())
		.def(self * float())
		.def(self / float())
    ;

	// base handle
	class_<CBaseHandle>("CBaseHandle", init<>())
		.def(init<int, int>())
		//.def_readwrite("Set", &CBaseHandle::Set, return_internal_reference<>() )
	;

	class_<EHANDLE>("EHANDLE", init<>())
		.def(init<int, int>())
		//.def_readwrite("Set", &EHANDLE::Set)
	;

	// CBaseEntity
	class_<CBaseEntity, detail::not_specified, detail::not_specified, boost::noncopyable >("CBaseEntity", init<bool>())
	  .def("KeyValue", (bool(CBaseEntity::*)(const char*, const char*)) &CBaseEntity::KeyValue )
	  .def("SetAbsOrigin", &CBaseEntity::SetAbsOrigin )
	  .def("SetAbsAngles", &CBaseEntity::SetAbsAngles )
	  .def("GetAbsOrigin", &CBaseEntity::GetAbsOrigin, return_internal_reference<>() )
	  .def("GetAbsAngles", &CBaseEntity::GetAbsAngles, return_internal_reference<>() )
	  .def("Precache", &CBaseEntity::Precache )
	  .def("Spawn", &CBaseEntity::Spawn )
	  .def("SetModel", &CBaseEntity::SetModel )
	  .def("SetModelName", &CBaseEntity::SetModelName )
	  .def("SetSolid", &CBaseEntity::SetSolid )
	  .def("GetClassname", &CBaseEntity::GetClassname )
	  .def("entindex", &CBaseEntity::entindex )
	  .def("SetHealth", &CBaseEntity::SetHealth )
	  .def("SetMaxHealth", &CBaseEntity::SetMaxHealth )
	  //.def("SetParent", &CBaseEntity::SetParent )
	  .def("MyNPCPointer", &CBaseEntity::MyNPCPointer, return_internal_reference<>() )
	;

	// base animating
	class_<CBaseAnimating, bases<CBaseEntity>, detail::not_specified, boost::noncopyable >("CBaseAnimating", init<>())	
	;

	// base combat character
	class_<CBaseCombatCharacter, bases<CBaseAnimating>, detail::not_specified, boost::noncopyable >("CBaseCombatCharacter", init<>())	
		.def("GetTeamRTSID", &CBaseCombatCharacter::GetTeamRTSID )
		.def("SetTeamRTSID", &CBaseCombatCharacter::SetTeamRTSID )
	;

	// base player
	class_<CBasePlayer, bases<CBaseCombatCharacter>, detail::not_specified, boost::noncopyable >("CBasePlayer", init<>())
	;
	
	// Usefull methods
	def("CreateEntityByName", proxy_CreateEntityByName, return_internal_reference<>() );
	def("PrecacheModel", proxy_PrecacheModel);
	def("ToBasePlayer", ToBasePlayer, return_internal_reference<>() );

	// error catching
	def("sys_excepthook_hl2", sys_excepthook_hl2 );
}

//-----------------------------------------------------------------------------
// Purpose: Utils
//-----------------------------------------------------------------------------
BOOST_PYTHON_MODULE(utils)
{
	def("UTIL_EntityByIndex", UTIL_EntityByIndex, return_internal_reference<>());
}

//-----------------------------------------------------------------------------
// Purpose: Math lib
//-----------------------------------------------------------------------------
void     (*AngleVectors1)(const QAngle& angles, Vector *forward)        = AngleVectors;
void     (*AngleVectors2)(const QAngle& angles, Vector *forward, Vector *right, Vector *up)        = AngleVectors;
BOOST_PYTHON_MODULE(mathlib)
{
	def("AngleVectors", AngleVectors1, return_value_policy<copy_const_reference>() );
	def("AngleVectors", AngleVectors2, return_value_policy<copy_const_reference>() );
}

//-----------------------------------------------------------------------------
// Purpose: Everything related to npc's
//-----------------------------------------------------------------------------
BOOST_PYTHON_MODULE(npc)
{
	class_<CAI_BaseNPC, bases<CBaseCombatCharacter>, detail::not_specified, boost::noncopyable >("CAI_BaseNPC", init<>())	
		.def("SetDistLook", &CAI_BaseNPC::SetDistLook )
	;
}

//-----------------------------------------------------------------------------
// Purpose: Initialize python
//-----------------------------------------------------------------------------
bool InitHL2Python()
{
    Py_Initialize();

	// Register the module with the interpreter
	if (PyImport_AppendInittab("base", initbase) == -1)
	{
		cvar->ConsoleColorPrintf( PYTHON_ERROR_PRINT_COLOR, "[Python]Failed to init base module\n");
	}

	if (PyImport_AppendInittab("utils", initutils) == -1)
	{
		cvar->ConsoleColorPrintf( PYTHON_ERROR_PRINT_COLOR, "[Python]Failed to init utils module\n");
	}

	if (PyImport_AppendInittab("npc", initnpc) == -1)
	{
		cvar->ConsoleColorPrintf( PYTHON_ERROR_PRINT_COLOR, "[Python]Failed to init npc module\n");
	}

	if (PyImport_AppendInittab("mathlib", initmathlib) == -1)
	{
		cvar->ConsoleColorPrintf( PYTHON_ERROR_PRINT_COLOR, "[Python]Failed to init mathlib module\n");
	}

	cvar->ConsoleColorPrintf( PYTHON_PRINT_COLOR, "[Python] Python initialized.\n");

	// get our main space
	main_module = import("__main__");
	main_namespace = main_module.attr("__dict__");

	// import default modules
	exec("import sys", main_namespace, main_namespace );
	exec("import base", main_namespace, main_namespace );

	// build the list of available paths
	char *char_filename;
	char command[MAX_PATH];
	char char_output_full_filename[ _MAX_PATH ];

	// currently a fixed list
	char_filename = "python\\server";	
	filesystem->RelativePathToFullPath(char_filename,"MOD",char_output_full_filename,sizeof(char_output_full_filename));	
	cvar->ConsoleColorPrintf( PYTHON_PRINT_COLOR, "[Python]Adding '%s' to path\n", char_output_full_filename );
	Q_snprintf( command, sizeof( command ), "sys.path.append('%s')", char_output_full_filename );
	exec(command, main_namespace, main_namespace );

	char_filename = "python\\server\\units";	
	filesystem->RelativePathToFullPath(char_filename,"MOD",char_output_full_filename,sizeof(char_output_full_filename));	
	cvar->ConsoleColorPrintf( PYTHON_PRINT_COLOR, "[Python]Adding '%s' to path\n", char_output_full_filename );
	Q_snprintf( command, sizeof( command ), "sys.path.append('%s')", char_output_full_filename );
	exec(command, main_namespace, main_namespace );

	char_filename = "python\\server\\powers";	
	filesystem->RelativePathToFullPath(char_filename,"MOD",char_output_full_filename,sizeof(char_output_full_filename));	
	cvar->ConsoleColorPrintf( PYTHON_PRINT_COLOR, "[Python]Adding '%s' to path\n", char_output_full_filename );
	Q_snprintf( command, sizeof( command ), "sys.path.append('%s')", char_output_full_filename );
	exec(command, main_namespace, main_namespace );

	// change the except hook
	exec("sys.excepthook = base.sys_excepthook_hl2", main_namespace, main_namespace );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Close
//-----------------------------------------------------------------------------
bool CloseHL2Python()
{
    // Finish the Python Interpreter
    //Py_Finalize();

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Run a python file
//-----------------------------------------------------------------------------
bool RunPythonFile( const char* scriptname )
{
	char char_filename[ _MAX_PATH ];
	char char_output_full_filename[ _MAX_PATH ];

	strcpy( char_filename, scriptname);
	filesystem->RelativePathToFullPath(char_filename,"MOD",char_output_full_filename,sizeof(char_output_full_filename));

	const char *constcharpointer = reinterpret_cast<const char *>( char_output_full_filename );

	if (!filesystem->FileExists(constcharpointer))
	{
		cvar->ConsoleColorPrintf( PYTHON_ERROR_PRINT_COLOR, "[Python] IFileSystem Cannot find the file: %s\n", constcharpointer);
		return false;
	}

	try
	{
		exec_file(constcharpointer, main_namespace, main_namespace );
	}
	catch(...)
	{
		PyErr_Print();
	}

	return true;
}

CON_COMMAND( py_runscript, "Run a python script")
{
	RunPythonFile(args.ArgS());
}


//-----------------------------------------------------------------------------
// Purpose: Run a string on the python interpreter
//-----------------------------------------------------------------------------
void RunPythonString( const char* string )
{
	// execute statement
	try
	{
		CBasePlayer *pPlayer = UTIL_GetCommandClient();
		main_namespace["caller"] = boost::ref(pPlayer);
		exec( string, main_namespace, main_namespace );
	}
	catch(...)
	{
		PyErr_Print();
	}
}

CON_COMMAND( py_run, "Run a python string")
{
	RunPythonString(args.ArgS());
}

//-----------------------------------------------------------------------------
// Purpose: Run a string on the python interpreter and return the template type
//			FIXME: not working, will crash the interpreter
//-----------------------------------------------------------------------------
template <class T>
T RunPythonStringT( const char* string )
{
	object result;

	// execute statement
	try
	{
		CBasePlayer *pPlayer = UTIL_GetCommandClient();
		main_namespace["caller"] = boost::ref(pPlayer);
		result = exec( string, main_namespace, main_namespace );
	}
	catch(...)
	{
		PyErr_Print();	
	}
	// extract result
	try
	{
		return extract<T>(result);
	}
	catch(...)
	{
		PyErr_Print();
	}

	return T();		// just create a default instance
}

//-----------------------------------------------------------------------------
// Purpose: Run a string on the python interpreter with one argument
//			FIXME: not working, will crash the interpreter
//-----------------------------------------------------------------------------
template <class T, class A1>
T RunPythonStringT( const char* string, A1 arg1 )
{
	object result;
	// execute statement
	try
	{
		CBasePlayer *pPlayer = UTIL_GetCommandClient();
		main_namespace["caller"] = boost::ref(pPlayer);
		main_namespace["arg1"] = boost::ref(arg1);
		result = exec( string, main_namespace, main_namespace );
	}
	catch(...)
	{
		PyErr_Print();
	}
	// extract result
	try
	{
		return extract<T>(result);
	}
	catch(...)
	{
		PyErr_Print();
	}

	return T();		// just create a default instance
}

//-----------------------------------------------------------------------------
// Purpose: Import a python script
//-----------------------------------------------------------------------------
bool ImportPythonModule( const char* scriptname )
{
	try
	{
		// import into the main space
		char command[MAX_PATH];
		Q_snprintf( command, sizeof( command ), "import %s", scriptname );
		exec(command, main_namespace, main_namespace );
	}
	catch(...)
	{
		PyErr_Print();
	}

	return true;
}

CON_COMMAND( py_import, "Import a python script")
{
	ImportPythonModule(args.ArgS());
}

//-----------------------------------------------------------------------------
// Purpose: Reload a module
//-----------------------------------------------------------------------------
bool ReloadPythonModule( const char* scriptname )
{
	try
	{
		// import into the main space
		char command[MAX_PATH];
		Q_snprintf( command, sizeof( command ), "reload(%s)", scriptname );
		exec(command, main_namespace, main_namespace );
	}
	catch(...)
	{
		PyErr_Print();
	}

	return true;
}

CON_COMMAND( py_reload, "Reload a python script")
{
	ReloadPythonModule(args.ArgS());
}

CON_COMMAND( py_test, "py_test")
{
	try
	{
		const char *desc;

		exec("import rebel_soldier", main_namespace, main_namespace );
		desc = RunPythonStringT<const char *>("print rebel_soldier.description");
		Msg("Description is: %s\n", desc);
	/*
		// some testing
		exec("import example2", main_namespace, main_namespace );

		CBaseEntity *pEnt = CreateEntityByName("prop_dynamic");
		pEnt->Spawn();
		main_namespace["model"] = boost::ref(pEnt);
		exec("example2.takethis(model)", main_namespace, main_namespace );*/
	}
	catch(...)
	{
		PyErr_Print();
	}

}
