#include "ifilesystem.h"
#include "core.h"
#include "log.h"
#include "game.h"
#include "game_lua_help.h"
#include "ientity.h"
#include "entitymanager.h"
#include "world.h"
#include "game_object.h"

#include <pugixml.hpp>

static Game s_game;
Game* g_game = &s_game;

void consoleMsg(const char* msg)
{
	Logger::Msg("%s", msg);
}

void engineError(const char* msg)
{
	Core::Error("%s", msg);
}

void engineWarning(const char* msg)
{
	Core::Warning("%s", msg);
}

LuaPlus::LuaObject engineCreateEntity(const char* classname)
{
	LuaPlus::LuaObject entityObject;
	entityObject.AssignNewTable(GetLuaState());

	//GameObject* entity = (GameObject*)g_entityManager->createEntity(classname);
	//entity->init_from_lua(entityObject);
	//entity->init();

	return entityObject;
}

void engineAddEntityToWorld(LuaPlus::LuaObject& object)
{
	LuaPlus::LuaObject cppclass = object["__cpp_entity"];

	// todo
}

void registerEngine()
{
	using namespace LuaPlus;

	// register engine functions
	LuaObject engineTable = GetLuaState().GetGlobals().CreateTable("engine");
	engineTable.RegisterDirect("error", &engineError);
	engineTable.RegisterDirect("warning", &engineWarning);
	engineTable.RegisterDirect("create_entity", &engineCreateEntity);
	engineTable.RegisterDirect("add_entity_to_world", &engineAddEntityToWorld);

	LuaObject consoleTable = GetLuaState().GetGlobals().CreateTable("console");
	consoleTable.RegisterDirect("print", &consoleMsg);
}

void registerClasses()
{
	using namespace LuaPlus;

	// base thing
	GetLuaState().GetGlobals().RegisterDirect("load_script", &luaLoadScript);
	GetLuaState().DoString("g_factory = {}");

	registerEngine();
}

struct LuaPrototype
{
	std::string m_luaname;
	std::string m_enginename;
	std::string m_description;
};

std::vector<LuaPrototype> g_prototypes;

void initializeEntityPrototypesFromLua()
{
	using namespace LuaPlus;
	LuaObject entitiesTable = GetLuaState().GetGlobal("g_entity_table");

	for (LuaTableIterator it(entitiesTable); it; it.Next()) {
		LuaObject entityTable = it.GetValue();
		assert(entityTable.IsTable());

		if (entityTable.GetTableCount() < 3)
		{
			Core::Error("Entry in entity prototype table is bad, missing argument");
		}

		LuaObject luaname = entityTable[1];
		LuaObject classname = entityTable[2];
		LuaObject description = entityTable[3];
		
		Logger::Msg("lua ent: name: %s classname: %s description: %s", luaname.ToString(), classname.ToString(), description.ToString());

		LuaPrototype prototype;
		prototype.m_luaname = luaname.ToString();
		prototype.m_enginename = classname.ToString();
		prototype.m_description = description.ToString();
		g_prototypes.push_back(prototype);

		
		/*for (LuaTableIterator it2(entityTable); it2; it2.Next()) {
			Logger::Msg("%s", it2.GetValue().ToString());
		}*/
	}
}

void initializeEntitiesTable()
{
	using namespace LuaPlus;

	LuaObject entitiesTable = GetLuaState().GetGlobal("g_entity_table");
	if (entitiesTable.IsNil()) {
		Core::Error("initializeEntitiesTable: failed initialize the entity table! g_entity_table is nil");
	}

	if (!entitiesTable.IsTable()) {
		Core::Error("initializeEntitiesTable: failed initialize the entity table! g_entity_table is not a table");
	}

	initializeEntityPrototypesFromLua();
}

void initializeLua()
{
	using namespace LuaPlus;

	// register base classes
	registerClasses();

	// load game base script
	luaLoadScript("game_init.lua");
}

void Game::Init()
{
	initializeLua();
	initializeEntitiesTable();
}

void Game::InitForNewMap(const char* mapname)
{
	LoadLevelXML(mapname);
}

void Game::LoadLevelXML(const char* mapname)
{
	// Load XML file of the level
	char buffer[kMaxPathLength];
	snprintf(buffer, kMaxPathLength, "data/levels/%s/%s.xml", mapname, mapname);

	FileHandle_t file = GetFileSystem()->OpenFile(buffer, "rb");
	SDL_assert_always(file != kInvalidFileHandleValue);

	size_t length = GetFileSystem()->GetFileLength(file);

	char* filedata = new char[length + 1];
	GetFileSystem()->ReadFile(file, filedata, length);
	filedata[length] = '\0';

	GetFileSystem()->CloseFile(file);

	pugi::xml_document doc;
	pugi::xml_parse_result result = doc.load_buffer(filedata, length);
	delete[] filedata;
	if (!result) {
		Core::Error("Game::LoadLevelXML: Error while reading level description file '%s'\nError: %s:%i",
		buffer, result.description(), result.offset);
	}

//	std::map<std::string, std::string> properties;

	for (pugi::xml_node entity : doc.child("Level").child("Entities").children("Entity")) {
		pugi::xml_attribute entityname = entity.attribute("name");
		pugi::xml_attribute classname = entity.attribute("classname");

		if (classname.empty()) {
			if (!entityname.empty()) {
				Core::Error(" Game::LoadLevelXML: Classname is not specified for entity '%s'", entityname.as_string());
			}
			else {
				Core::Error(" Game::LoadLevelXML: Classname is not specified for entity");
			}
		}

		IEntityBase* entity = Lua_CreateEntity(classname.as_string());
		
		// this is pure C++ entity :)
		if (!entity)
			entity = g_entityManager->CreateEntity(classname.as_string());

		//IEntityBase* entity = g_entityManager->CreateEntity(classname.as_string());
		g_world->AddEntity(entity);
	}
}

IEntityBase* Game::Lua_CreateEntity(const char* classname)
{
	using namespace LuaPlus;

	//if (!classname)
	//	classname = "Entity";

	SDL_assert_always(classname);

	LuaPrototype* pluaprototype = nullptr;

	// find a prototype
	for (std::vector<LuaPrototype>::iterator it = g_prototypes.begin();
		it != g_prototypes.end();
		++it)
	{
		if (strcmp((*it).m_luaname.c_str(), classname) == 0)
		{
			pluaprototype = &(*it);
			break;
		}
	}
	
	if (pluaprototype)
	{
		Entity* entity = static_cast<Entity*>(g_entityManager->CreateEntity(pluaprototype->m_enginename.c_str()));
		SDL_assert_always(entity);

		// get a prototype
		LuaObject prototype = GetLuaState().GetGlobal(pluaprototype->m_luaname.c_str());
		SDL_assert_always(!prototype.IsNil());
		prototype.SetObject("__index", prototype);

		// generate table
		LuaObject factory = GetLuaState().GetGlobal("g_factory");

		// generate name
		std::string entityname = pluaprototype->m_luaname + "_" + std::to_string(factory.GetTableCount());
		
		// create an table
		LuaObject entityTable = GetLuaState().GetGlobals().CreateTable(entityname.c_str());
		entityTable.SetMetatable(prototype);
		entityTable.SetString("m_name", entityname.c_str());
		
		// push in to the factory
		factory.SetObject(entityname.c_str(), entityTable);

		// link to the entity
		entity->InitFromTable(entityTable);

		return entity;
	}

	//IEntityBase* entity = g_entityManager->CreateEntity(classname);

	return nullptr;
}

void Game::Shutdown()
{
}
