#include "utils/logger.h"
#include "filesystem/file.h"
#include "filesystem/stream.h"
#include "filesystem/filemanager.h"
#include "render/modelmanager.h"
#include "render/debugrender.h"
#include "game/game_object.h"
#include "game/game_object_factory.h"
#include "game/level.h"

Level* g_level = nullptr;

Level::Level() : 
	m_busy(false), /*m_physics_world(nullptr),*/ 
	m_level_name(nullptr),
	m_needToDestroyEnt(false)
{
	//m_physics_world = mem_new<PhysicsWorld>();
	//m_physics_world->Create();
}

Level::~Level()
{
	for (auto it : m_entities)
	{
		delete it;
	}

	m_entities.clear();

	//if (m_physics_world)
	//{
	//	m_physics_world->Destroy();
	//	mem_delete(m_physics_world);
	//	m_physics_world = nullptr;
	//}

	if (m_level_name)
	{
		free((void*)m_level_name);
		m_level_name = nullptr;
	}
}

bool Level::IsLevelExist(const char* levelname)
{
	char levelpath[512];
	sprintf(levelpath, "content/levels/%s/", levelname);
	size_t const pathend = strlen(levelpath);
	strcpy_s(levelpath + pathend, sizeof(levelpath) - pathend, "level.lmf");

	if (!g_fileManager->FileExist(levelpath))
	{
		Msg("Level::IsLevelExist: not found level mesh %s", levelname);
		return false;
	}

	return true;
}

void Level::Load(const char* levelname)
{
	Msg("loading level %s", levelname);

	m_level_name = strdup(levelname);

	StreamBase* reader = nullptr;
	char levelpath[512];
	sprintf(levelpath, "content/levels/%s/", levelname);
	size_t const pathend = strlen(levelpath);
	
	strcpy_s(levelpath + pathend, sizeof(levelpath) - pathend, "level.lmf");
	reader = g_fileManager->OpenStream(levelpath, FileAccess::Read);
	//LoadLMF(reader);
//	g_FileSystem->CloseFile(reader);
	delete reader;

	//strcpy(levelpath + pathend, "level.xml");
	//SaveXML(levelpath);

	//reader = g_FileSystem->OpenRead(levelpath);
	//LoadXML(reader);
	//g_FileSystem->CloseFile(reader);

	// strcpy(levelpath + pathend, "level.somefile");
	// reader = g_FileSystem->openRead(levelpath);
	// loadSomeFile(reader);
	// etc.
}

template <size_t buffer_capacity>
void RemoveNextLine(char(&buffer)[buffer_capacity])
{
	for (int i = 0; i < buffer_capacity; i++)
	{
		if (buffer[i] == '\n')
			buffer[i] = '\0';
	}
}

void Level::LoadText(const char* filename)
{
	Msg("loading level %s", filename);

	char fn[kMaxPathLength];
	snprintf(fn, sizeof(fn), "%s/%s", g_fileManager->GetDefaultPath(), filename);
	osResolvePath(fn);
	FILE* file = fopen(fn, "r");
	if (!file)
	{
		Msg("can't load level %s file not exist", filename);
		return;
	}

	bool beginEntity = false;
	bool endEntity = false;
	char entitynameBuffer[256];
	char classnameBuffer[256];
	char visualnameBuffer[256];
	char tagnameBuffer[256];
	Vec3 pos = {0.0f};
	Vec3 rot = {0.0f};
	entitynameBuffer[0] = '\0';
	classnameBuffer[0] = '\0';
	visualnameBuffer[0] = '\0';
	tagnameBuffer[0] = '\0';

	while (true)
	{
		if (endEntity) {
			GameObject* gameObject = CreateEntityByClassName(classnameBuffer);
			gameObject->m_position = pos;
			gameObject->m_rotation = rot;
			gameObject->m_tag = tagnameBuffer;
			gameObject->LoadModel(visualnameBuffer);
			endEntity = false;

			memset(entitynameBuffer, 0, sizeof(entitynameBuffer));
			entitynameBuffer[0] = '\0';
			memset(classnameBuffer, 0, sizeof(classnameBuffer));
			classnameBuffer[0] = '\0';
			memset(visualnameBuffer, 0, sizeof(visualnameBuffer));
			visualnameBuffer[0] = '\0';
			memset(tagnameBuffer, 0, sizeof(tagnameBuffer));
			tagnameBuffer[0] = '\0';
		}

		char lineHeader[128];

		// read the first word of the line
		int res = fscanf(file, "%s", lineHeader);
		if (res == EOF)
			break; // EOF = End Of File. Quit the loop.
		
		if (lineHeader[0] == '#')
		{
			// Probably a comment, eat up the rest of the line
			char stupidBuffer[1000];
			fgets(stupidBuffer, 1000, file);
			continue;
		}

		if (!beginEntity)
		{
			if (strcmp(lineHeader, "BeginEntity") == 0)
			{
				fscanf(file, "%s", entitynameBuffer);
				//fgets(entitynameBuffer, sizeof(entitynameBuffer), file);
				RemoveNextLine(entitynameBuffer);
				beginEntity = true;
			}
		}
		else
		{
			if (strcmp(lineHeader, "ClassName") == 0) {
				fscanf(file, "%s", classnameBuffer);
				//fgets(classnameBuffer, sizeof(classnameBuffer), file);
				RemoveNextLine(classnameBuffer);
			} else if (strcmp(lineHeader, "Visual") == 0) {
				fscanf(file, "%s", visualnameBuffer);
				//fgets(visualnameBuffer, sizeof(visualnameBuffer), file);
				RemoveNextLine(visualnameBuffer);
			} else if (strcmp(lineHeader, "Position") == 0) {
				fscanf(file, "%f %f %f\n", &pos.x, &pos.y, &pos.z);
			} else if (strcmp(lineHeader, "Rotation") == 0) {
				fscanf(file, "%f %f %f\n", &rot.x, &rot.y, &rot.z);
			} else if (strcmp(lineHeader, "EndEntity") == 0) {
				beginEntity = false;
				endEntity = true;
			}else if (strcmp(lineHeader, "Tag") == 0) {
				fscanf(file, "%s", tagnameBuffer);
				RemoveNextLine(tagnameBuffer);
			}
		}
	}

	fclose(file);
}

GameObject* Level::CreateEntity()
{
	if (m_busy)
	{
#ifdef _DEBUG
		Logger::error("Level::CreateEntity: level is currently busy");
#endif
		return (NULL);
	}

	GameObject* entity = GameObject::_Construct();
	m_entities.push_back(entity);
	return entity;
}

GameObject* Level::CreateEntityByRC(const RuntimeClass* _pRuntimeClass)
{
	return CreateEntityByClassName(_pRuntimeClass->Name());
}

GameObject* Level::CreateEntityByClassName(const char* classname)
{
	if (m_busy)
	{
#ifdef _DEBUG
		Logger::error("Level::CreateEntityByClassName: level is currently busy");
#endif
		return (NULL);
	}


	GameObject* entity = g_objectFactory->Create(classname);
	if (!entity)
	{
		Logger::error("Level::CreateEntityByClassName: Entity with classname '%s' is unregistered", classname);
		return (NULL);
	}

	m_entities.push_back(entity);
	return entity;
}

void Level::AddEntity(GameObject* entity)
{
	if (m_busy)
	{
		Logger::error("Level::AddEntity: level is currently busy");
	}
	if (!entity)
	{
		Logger::error("Level::AddEntity: failed to add NULL entity");
	}

	m_entities.push_back(entity);
}

void Level::DestroyEntity(GameObject* entity)
{
	if (!entity) return;

	std::vector<GameObject*>::iterator it = std::find(
		m_entities.begin(),
		m_entities.end(),
		entity
	);
	if (it != m_entities.end())
	{
		if (m_busy)
		{
			m_entitiesToDestroy.push_back(*it);
			m_needToDestroyEnt = true;
		}
		else
		{
			delete *it;
			m_entities.erase(it);
		}
	}
}

#include "game/player.h"

void Level::Update(float fDeltaTime)
{
	if (m_needToDestroyEnt)
	{
		DestroyEntities();
		m_needToDestroyEnt = false;
	}

	m_busy = true;
	
	g_PhysicsWorld->Update(fDeltaTime);

	//if (m_physics_world)
	//	m_physics_world->Update(fDeltaTime);

	for (auto it : m_entities)
	{
		if ( it->IsKindOf<Player>() )
		{
			Player* player = ( Player* )it;
			player->Update( fDeltaTime );
		}
	}

	m_busy = false;
}

void Level::Render()
{
	for (int i = 0; i < m_entities.size(); i++)
	{
		GameObject* ent = m_entities[i];
		if (ent->m_model)
			ent->m_model->Draw(ent->GetModelMatrix());

		static bool showBounds = true;
		if (showBounds)
		{
			BoundingBox bbox;
			ent->GetTransformedBBox(&bbox);
			g_debugRender->DrawBoundingBox(bbox, glm::vec3(1.0f));
			
		//	if (strcmp(ent->GetRuntimeClass()->Name(), "Player"))
		//		g_debugRender->DrawEllipse(ent->GetModelMatrix(), Vec3(0.5f, 0.5f, 1.0f));
		}
	}
}

GameObject* Level::GetObjectByTag(const String& tag)
{
	std::vector<GameObject*>::iterator it = std::find_if(
		m_entities.begin(),
		m_entities.end(),
		[=](const GameObject* O)
		{
			return tag == O->m_tag;
		}
	);

	if (it != m_entities.end())
		return *it;

	return nullptr;
}

const char* Level::GetLevelName()
{
	return m_level_name;
}

void Level::DestroyEntities()
{
	// Remove entities from pool
	m_entities.erase(std::remove_if(m_entities.begin(), m_entities.end(),
		[=](GameObject* pE)
		{
			if (FindEntityToDestroy(pE))
			{
				return (true);
			}
			return (false);
		}
	),
	m_entities.end()
	);

	// Delete entities
	for (int i = 0; i < m_entitiesToDestroy.size(); i++)
	{
		if (m_entitiesToDestroy[i])
			delete m_entitiesToDestroy[i];
	}

	m_entitiesToDestroy.clear();

	// Ok
}

bool Level::FindEntityToDestroy(GameObject* O)
{
	std::vector<GameObject*>::iterator it = std::find(
		m_entitiesToDestroy.begin(),
		m_entitiesToDestroy.end(),
		O
	);

	if (it != m_entitiesToDestroy.end())
		return true;

	return false;
}
