#include "engine/core.h"
#include "engine/log.h"
#include "engine/ifilesystem.h"
#include "engine/camera.h"
#include "render/texturesmanager.h"
#include "render/modelsystem.h"
#include "render/scenemanager.h"
#include "render/renderdevice.h"
#include "render/gl_shared.h"
#include "render/texture2d.h"
#include "render/shader.h"
#include "render/shadersystem.h"
#include "render/render.h"
#include "render/debugrender.h"
#include "render/gpu_buffer.h"
#include "engine/camera.h"
#include "engine/physics/physicsworld.h"

#include <pugixml.hpp>

extern int g_NumModels;
static bool g_debugRenderScene = false;
glm::vec3 g_ambientColor = glm::vec3(0.0f);

static std::string getFileExtension(const std::string& filename)
{
	size_t whereIsDot = filename.find_last_of('.');
	if (whereIsDot != std::string::npos) {
		return filename.substr(whereIsDot);
	}

	return "";
}

static std::string getFileNameWithoutExtension(const std::string& filename)
{
	size_t lastindex = filename.find_last_of(".");
	if (lastindex != std::string::npos) {
		return filename.substr(0, lastindex);
	}

	return filename;
}

static std::string getFilenameWithoutPathAndExtension(const std::string& filename)
{
	size_t whereIsDot = filename.find_last_of('.');
	size_t whereIsSlash = filename.find_last_of('/');
	if (whereIsSlash == std::string::npos) {
		whereIsSlash = filename.find_last_of('\\');
	}

	if (whereIsDot == std::string::npos && whereIsSlash == std::string::npos) {
		return filename;
	}

	std::string string = filename.substr(whereIsSlash + 1);
	whereIsDot = string.find_last_of('.');
	string = string.substr(0, whereIsDot);

	return string;
}

template <typename T>
void TReadFile(FileHandle_t handle, T* value)
{
	GetFileSystem()->ReadFile(handle, (void*)value, sizeof(T));
}

template <typename T>
void TWriteFile(FileHandle_t handle, const T* value)
{
	GetFileSystem()->WriteFile(handle, (void*)value, sizeof(T));
}

static void WriteString(FileHandle_t handle, const std::string& string)
{
	uint16_t size = string.length();
	TWriteFile(handle, &size);

	GetFileSystem()->WriteFile(handle, (void*)string.c_str(), size);
}

static void ReadString(FileHandle_t handle, std::string& string)
{
	uint16_t size;
	TReadFile(handle, &size);

	//string.resize(size + 1);
	string.resize(size);
	GetFileSystem()->ReadFile(handle, (void*)string.c_str(), size);
	//string[size] = '\0';
}

//#ifdef NDEBUG
//#pragma comment(lib, "assimp-vc141-mt.lib")
//#else
//#pragma comment(lib, "assimp-vc141-mtd.lib")
//#endif // NDEBUG
//
//inline static glm::mat4 Assimp2Glm(const aiMatrix4x4& from)
//{
//	return glm::mat4(
//		(double)from.a1, (double)from.b1, (double)from.c1, (double)from.d1,
//		(double)from.a2, (double)from.b2, (double)from.c2, (double)from.d2,
//		(double)from.a3, (double)from.b3, (double)from.c3, (double)from.d3,
//		(double)from.a4, (double)from.b4, (double)from.c4, (double)from.d4
//	);
//}
//
//SceneStaticMesh* createSceneStaticMesh(const aiScene* scene, aiMesh* mesh)
//{
//	assert(scene);
//	assert(mesh);
//	//assert(mesh->mPrimitiveTypes == aiPrimitiveType_TRIANGLE);
//
//	std::vector<SceneStaticMeshVertex> vertices;
//	std::vector<uint32_t> indices;
//
//	// reserve 1024 vertices and indices
//	vertices.reserve(1024);
//	indices.reserve(1024);
//
//	for (uint32_t i = 0; i < mesh->mNumVertices; i++) {
//		SceneStaticMeshVertex vertex;
//
//		// position
//		aiVector3D position = mesh->mVertices[i];
//		vertex.m_position.x = position.x;
//		vertex.m_position.y = position.y;
//		vertex.m_position.z = position.z;
//
//		// normal
//		aiVector3D normal = mesh->mNormals[i];
//		vertex.m_normal.x = normal.x;
//		vertex.m_normal.y = normal.y;
//		vertex.m_normal.z = normal.z;
//
//		static bool inverseTexCoords = true;
//
//		if (inverseTexCoords) {
//			// texture coord 0 ( DIFFUSE )
//			if (mesh->mTextureCoords[0]) {
//				vertex.m_texcoord1.x = mesh->mTextureCoords[0][i].x;
//				vertex.m_texcoord1.y = mesh->mTextureCoords[0][i].y;
//			}
//			else {
//				vertex.m_texcoord1 = glm::vec2(0.0f, 0.0f);
//			}
//
//			// texture coord 1 ( LIGHTMAP )
//			if (mesh->mTextureCoords[1]) {
//				vertex.m_texcoord0.x = mesh->mTextureCoords[1][i].x;
//				vertex.m_texcoord0.y = mesh->mTextureCoords[1][i].y;
//			}
//			else {
//				vertex.m_texcoord0 = glm::vec2(0.0f, 0.0f);
//			}
//		}
//		else {
//			// texture coord 0 ( DIFFUSE )
//			if (mesh->mTextureCoords[0]) {
//				vertex.m_texcoord0.x = mesh->mTextureCoords[0][i].x;
//				vertex.m_texcoord0.y = mesh->mTextureCoords[0][i].y;
//			}
//			else {
//				vertex.m_texcoord0 = glm::vec2(0.0f, 0.0f);
//			}
//
//			// texture coord 0 ( LIGHTMAP )
//			if (mesh->mTextureCoords[1]) {
//				vertex.m_texcoord1.x = mesh->mTextureCoords[1][i].x;
//				vertex.m_texcoord1.y = mesh->mTextureCoords[1][i].y;
//			}
//			else {
//				vertex.m_texcoord1 = glm::vec2(0.0f, 0.0f);
//			}
//		}
//
//		vertices.push_back(vertex);
//	}
//
//	for (uint32_t i = 0; i < mesh->mNumFaces; i++) {
//		aiFace face = mesh->mFaces[i];
//		for (uint32_t j = 0; j < face.mNumIndices; j++)
//			indices.push_back(face.mIndices[j]);
//	}
//
//	aiMaterial* assImpMaterial = scene->mMaterials[mesh->mMaterialIndex];
//	aiString diffusePath;
//	assImpMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &diffusePath);
//
//	if (strlen(diffusePath.C_Str()) == 0) {
//		Logger::msg("Scene mesh '%s' has assImpMaterial, but diffuse texture is not specified.", mesh->mName.C_Str());
//	}
//
//	Material* material = g_materialsManager->createMaterial("default_lightmap", diffusePath.C_Str());
//
//	glm::mat4 transform = glm::identity<glm::mat4>();
//
//	return new SceneStaticMesh(vertices, indices, material, transform);
//}

SceneManager* g_sceneManager;

SceneManager::SceneManager()
{
	m_sceneLoaded = false;

	DLightManager::GetInstance()->Clear();
}

SceneManager::~SceneManager()
{
	m_sceneLoaded = false;
}

void SceneManager::loadScene(const char* filename)
{
	unloadIfScenePresent();

	// Reset ambient color
	setAmbientColor(glm::vec3(0.0f));

	char filenameBuffer[kMaxPathLength];
	snprintf(filenameBuffer, kMaxPathLength, "data/levels/%s/%s.xml", filename, filename);

	FileHandle_t file = GetFileSystem()->OpenFile(filenameBuffer, "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("SceneManager::LoadScene: Error while reading level description file '%s'\nError: %s:%i",
			filenameBuffer, result.description(), result.offset);
	}

	pugi::xml_node root = doc.document_element();
	const char* scenefilename = root.child("LevelDescription").child("SceneFile").attribute("filename").value();

	pugi::xml_node ambientnode = root.child("LevelDescription").child("AmbientColor");
	if (ambientnode)
	{
		glm::vec3 color = glm::vec3(0.0f);
		color.r = ambientnode.attribute("r").as_float();
		color.g = ambientnode.attribute("g").as_float();
		color.b = ambientnode.attribute("b").as_float();
		setAmbientColor(color);
	}

	m_sceneName = getFileNameWithoutExtension(scenefilename);

	sprintf(filenameBuffer, "data/levels/%s/%s", filename, scenefilename);

	if (!GetFileSystem()->IsExist(filenameBuffer)) {
		Core::Error("SceneManager::LoadScene: scene file '%s' doesnt exist", scenefilename);
	}

	m_sceneFilename = filenameBuffer;

	if (getFileExtension(m_sceneFilename) == ".scene") {
		LoadSceneXML(m_sceneFilename.c_str());
	}

	Logger::Msg("loaded %u meshes", m_sceneMeshes.size());

	m_sceneLoaded = true;
}

void SceneManager::LoadSceneXML(const char* filename)
{
	FileHandle_t file = GetFileSystem()->OpenFile(filename, "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("SceneManager::LoadSceneXML: Error while reading level description file '%s'\nError: %s:%i",
			filename, result.description(), result.offset);
	}

	for (pugi::xml_node staticMesh : doc.child("Scene").children("StaticMesh")) {
		const char* meshfilename = staticMesh.attribute("filename").as_string();
		if (meshfilename)
			loadStaticMesh(meshfilename);
	}
}

//void SceneManager::loadScene(const char* sceneName)
//{
//	if (sceneName)
//		m_sceneName = getFileNameWithoutExtension(sceneName);
//
//	if (!m_sceneName.size())
//		Core::Error("Scene::loadScene: Scene file is empty, check the 'SceneFile' attribute in level description.");
//
//	m_sceneFilename = "data/levels/" + std::string(sceneName) + "/" + std::string(sceneName) + ".scene.xml";
//
//	if (!GetFileSystem()->IsExist(m_sceneFilename.c_str()))
//		Core::Error("Scene::loadScene: scene file '%s' doesnt exist", m_sceneFilename.c_str());
//
//	Logger::Msg("loading scene %s", m_sceneFilename.c_str());
//
//	loadStaticMeshes();
//
//	// Load lightmap
////	m_lightmap = g_texturesManager->createTexture2D("**");
//
//	//if (!scene->HasTextures()) {
//	//	Core::error("Scene::loadScene: scene '%s' doesnt have textures. Please check assImpMaterial parameters!", scenefilename);
//	//}
//
//	m_sceneLoaded = true;
//}

void SceneManager::loadSkybox(const char* skybox)
{
	if (skybox && *skybox && strcmp(skybox, "none"))
		m_pSkybox = g_modelSystem->LoadModel(skybox);
}

void SceneManager::loadStaticMeshes()
{
	//File* file = g_fileManager->openFile(m_sceneFilename.c_str(), FileAccess::Read);

	//file->seek(SeekDir::End, 0);
	//size_t length = file->tell();
	//file->seek(SeekDir::Begin, 0);

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

	//g_fileManager->closeFile(file);

	//Assimp::Importer importer;
	//const aiScene* scene = importer.ReadFileFromMemory(filedata, length,
	//	aiProcessPreset_TargetRealtime_Quality |                     // some optimizations and safety checks
	//	aiProcess_OptimizeMeshes |                                   // minimize number of meshes
	//	aiProcess_PreTransformVertices |                             // apply node matrices
	//	//	aiProcess_Triangulate |
	//	aiProcess_SplitLargeMeshes |
	//	aiProcess_TransformUVCoords /*|*/ // apply UV transformations
	///*aiProcess_FlipUVs*/);

	//delete[] filedata;

	//if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
	//	Core::error("Scene::loadScene: failed to load scene '%s'.\n%s", m_sceneName, importer.GetErrorString());
	//}

	//if (scene && !scene->HasMaterials()) {
	//	Core::error("Scene::loadScene: scene '%s' doesnt have materials. Please check export parameters!", m_sceneName);
	//}

	//Logger::msg("loaded %u meshes", scene->mNumMeshes);
	//for (unsigned int i = 0; i < scene->mNumMeshes; i++) {
	//	m_sceneMeshes.push_back(createSceneStaticMesh(scene, scene->mMeshes[i]));
	//}
}

void SceneManager::loadStaticMesh(const char* filename)
{
	char filenamebuf[kMaxPathLength];
	snprintf(filenamebuf, kMaxPathLength, "data/levels/%s/%s", m_sceneName.c_str(), filename);

	if (GetFileSystem()->IsExist(filenamebuf)) {
		SceneStaticMesh* staticMesh = new SceneStaticMesh();

		const char* ext = strrchr(filenamebuf, '.');
		if (ext && strcmp(ext, ".obj") == 0)
			staticMesh->LoadObj(filenamebuf);
		else if (ext && strcmp(ext, ".wmb") == 0)
			staticMesh->LoadBin(filenamebuf);
		
		m_sceneMeshes.push_back(staticMesh);
	}
}

bool SceneManager::isSceneLoaded()
{
	return m_sceneLoaded;
}

void SceneManager::renderScene(const glm::mat4& cameraTranslation)
{
	if (!isSceneLoaded())
		return;

	Camera* camera = g_cameraManager.GetActiveCamera();
	if (!camera)
		return;

	Frustum& frustum = camera->GetFrustum();

	for (std::list<SceneStaticMesh*>::iterator it = m_sceneMeshes.begin(); it != m_sceneMeshes.end(); ++it) {
		if (*it) {
			// cull mesh
			if (frustum.CullBoundingBox((*it)->GetBoundingBox()))
				continue;

			(*it)->RenderObjects();

			if (g_debugRenderScene)
				g_debugRender->DrawBoundingBox((*it)->GetBoundingBox(), glm::vec3(1.0f));
		}
	}

	// draw skybox
	//if (m_pSkybox)
	//	m_pSkybox->renderObjects(cameraTranslation);

	//glActiveTexture(GL_TEXTURE1);
	//m_lightmap->bind();

	//if (g_lmTexFilter.getValueB()) {
	//	//m_lightmap->setMin
	//}

	//m_lightmap->setWrapS(TextureWrap::ClampToEdge);
	//m_lightmap->setWrapT(TextureWrap::ClampToEdge);

	//for (std::list<SceneStaticMesh*>::iterator it = m_sceneMeshes.begin(); it != m_sceneMeshes.end(); ++it) {
	//	if (*it) {
	//		(*it)->renderObjects();
	//	}
	//}

	//glActiveTexture(GL_TEXTURE1);
	//glBindTexture(GL_TEXTURE_2D, 0);

}

void SceneManager::unloadIfScenePresent()
{
	if (!isSceneLoaded())
		return;

	unloadScene();
}

void SceneManager::replaceMaterial(const char* oldMaterialname, const char* newMaterialName)
{

}

void SceneManager::replaceAllMaterialsWithBruteForce(const char* materialname)
{
	/*for (std::list<SceneStaticMesh*>::iterator it = m_sceneMeshes.begin();
		it != m_sceneMeshes.end();
		++it)
	{
		SceneStaticMesh* mesh = (*it);
		assert(mesh);
		assert(mesh->m_material && "scene mesh has nullptr assImpMaterial");

		mesh->m_material = g_materialsManager->createMaterial(materialname,
			mesh->m_material->getTemplateMaterial()->m_diffuseTextures[0].c_str());
	}*/
}

void SceneManager::unloadScene()
{
	Logger::Msg("deleting scene ...");

	if (m_sceneMeshes.empty())
		return;

	for (std::list<SceneStaticMesh*>::iterator it = m_sceneMeshes.begin();
		it != m_sceneMeshes.end();
		++it)
	{
		if (*it) {
			delete* it;
			*it = nullptr;
		}
	}

	m_sceneMeshes.clear();

	DLightManager::GetInstance()->Clear();

	m_sceneLoaded = false;
}

const char* SceneManager::getSceneName()
{
	return m_sceneName.c_str();
}

void SceneManager::toggleDebugRender()
{
	g_debugRenderScene = !g_debugRenderScene;
}

void SceneManager::setAmbientColor(const glm::vec3& color)
{
	g_ambientColor = color;
}

const glm::vec3& SceneManager::getAmbientColor()
{
	return g_ambientColor;
}

// SceneStaticMesh

SceneStaticMesh::SceneStaticMesh() :
	m_vb(nullptr),
	m_ib(nullptr),
	m_albedoTexture(nullptr),
	m_vbcount(0),
	m_ibcount(0)
{
}

SceneStaticMesh::~SceneStaticMesh()
{
	if (m_ib) {
		delete m_ib;
		m_ib = nullptr;
	}

	if (m_vb) {
		delete m_vb;
		m_vb = nullptr;
	}
}

void SceneStaticMesh::LoadObj(const char* filename)
{
	std::vector<unsigned int> vertexIndices, uvIndices, normalIndices;
	std::vector<glm::vec3> temp_vertices;
	std::vector<glm::vec2> temp_uvs;
	std::vector<glm::vec3> temp_normals;

	std::vector<glm::vec3> out_vertices;
	std::vector<glm::vec2> out_uvs;
	std::vector<glm::vec3> out_normals;

	FILE* file = fopen(filename, "r");
	if (file == NULL) {
		Msg("SceneStaticMesh::LoadObj: Impossible to open the file !");
		return;
	}

	while (1) {

		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.

		// else : parse lineHeader

		if (strcmp(lineHeader, "v") == 0) {
			glm::vec3 vertex;
			fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
			temp_vertices.push_back(vertex);
		}
		else if (strcmp(lineHeader, "vt") == 0) {
			glm::vec2 uv;
			fscanf(file, "%f %f\n", &uv.x, &uv.y);
			uv.y = -uv.y; // Invert V coordinate since we will only use DDS texture, which are inverted. Remove if you want to use TGA or BMP loaders.
			temp_uvs.push_back(uv);
		}
		else if (strcmp(lineHeader, "vn") == 0) {
			glm::vec3 normal;
			fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z);
			temp_normals.push_back(normal);
		}
		else if (strcmp(lineHeader, "f") == 0) {
			std::string vertex1, vertex2, vertex3;
			unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
			int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2]);
			if (matches != 9) {
				Msg("SceneStaticMesh::LoadObj: File can't be read by our simple parser :-( Try exporting with other options");
				fclose(file);
				return;
			}
			vertexIndices.push_back(vertexIndex[0]);
			vertexIndices.push_back(vertexIndex[1]);
			vertexIndices.push_back(vertexIndex[2]);
			uvIndices.push_back(uvIndex[0]);
			uvIndices.push_back(uvIndex[1]);
			uvIndices.push_back(uvIndex[2]);
			normalIndices.push_back(normalIndex[0]);
			normalIndices.push_back(normalIndex[1]);
			normalIndices.push_back(normalIndex[2]);
		}
		else {
			// Probably a comment, eat up the rest of the line
			char stupidBuffer[1000];
			fgets(stupidBuffer, 1000, file);
		}

	}

	// For each vertex of each triangle
	for (unsigned int i = 0; i < vertexIndices.size(); i++) {

		// Get the indices of its attributes
		unsigned int vertexIndex = vertexIndices[i];
		unsigned int uvIndex = uvIndices[i];
		unsigned int normalIndex = normalIndices[i];

		// Get the attributes thanks to the index
		glm::vec3 vertex = temp_vertices[vertexIndex - 1];
		glm::vec2 uv = temp_uvs[uvIndex - 1];
		glm::vec3 normal = temp_normals[normalIndex - 1];

		// Put the attributes in buffers
		out_vertices.push_back(vertex);
		out_uvs.push_back(uv);
		out_normals.push_back(normal);

	}

	fclose(file);

	m_boundingBox.m_min = glm::vec3(FLT_MAX);
	m_boundingBox.m_max = glm::vec3(FLT_MIN);

	// Combine in to the one array
	std::vector<StaticMeshVertex>  vertices;
	for (unsigned int i = 0; i < vertexIndices.size(); i++)
	{
		// Get the indices of its attributes
		unsigned int vertexIndex = vertexIndices[i];
		unsigned int uvIndex = uvIndices[i];
		unsigned int normalIndex = normalIndices[i];

		// Get the attributes thanks to the index
		glm::vec3 vertex = temp_vertices[vertexIndex - 1];
		glm::vec2 uv = temp_uvs[uvIndex - 1];
		glm::vec3 normal = temp_normals[normalIndex - 1];

		StaticMeshVertex vtx = {};
		vtx.position = vertex;
		vtx.normal = normal;
		vtx.texcoord = uv;
		vertices.push_back(vtx);

		if (i == 0)
		{
			m_boundingBox.m_min = vertex;
			m_boundingBox.m_max = vertex;
		}
		else
		{
			m_boundingBox.m_min = glm::min(m_boundingBox.m_min, vertex);
			m_boundingBox.m_max = glm::max(m_boundingBox.m_max, vertex);
		}
	}

	// Fill face indices
	std::vector<uint32_t> indices;
	for (uint32_t i = 0; i < vertices.size(); i++) {
		indices.push_back(i);
	}

	std::string binaryCache = getFileNameWithoutExtension(filename);
	binaryCache += ".wmb";

	if (!GetFileSystem()->IsExist(binaryCache.c_str()))
	{
		FileHandle_t handle = GetFileSystem()->OpenFile(binaryCache.c_str(), "wb");

		// write header
		WriteString(handle, "StaticSceneMeshFile");
		WriteString(handle, "0.1");

		// write material file

		std::string mtlfilename = getFileNameWithoutExtension(filename);
		mtlfilename += ".mtl";
		WriteString(handle, mtlfilename);

		// write vertices
		uint32_t vbcount = vertices.size();
		TWriteFile(handle, &vbcount);
		GetFileSystem()->WriteFile(handle, vertices.data(), vertices.size() * sizeof(StaticMeshVertex));

		// write indices
		uint32_t ibcount = indices.size();
		TWriteFile(handle, &ibcount);
		GetFileSystem()->WriteFile(handle, indices.data(), indices.size() * sizeof(uint32_t));

		GetFileSystem()->CloseFile(handle);
	}
	
//	m_Vertices = vertices;

	m_vb = g_renderDevice->CreateVertexBuffer(vertices.data(), (int)sizeof(StaticMeshVertex) * (int)vertices.size());
	m_vbcount = vertices.size();

	std::string mtlfilename = getFileNameWithoutExtension(filename);
	mtlfilename += ".mtl";
	LoadMtl(mtlfilename.c_str());

	if (g_PhysicsWorld) {
		g_PhysicsWorld->AddCollisionModel(vertices.data(), vertices.size(), indices.data(), indices.size());
	}
}

void SceneStaticMesh::LoadBin(const char* filename)
{
	FileHandle_t handle = GetFileSystem()->OpenFile(filename, "rb");

	std::string header;
	ReadString(handle, header);
	if (header != "StaticSceneMeshFile")
		Core::Error(" SceneStaticMesh::LoadBin: Error during scene loading!\n%s is not a proper scene mesh.\n%s (should be %s)", filename, header.c_str(), "StaticSceneMeshFile");

	std::string version;
	ReadString(handle, version);
	if (version != "0.1")
		Core::Error(" SceneStaticMesh::LoadBin: Error during scene loading!\n%s is outdated.\nversion %s (current is %s)", filename, version.c_str(), "0.1");

	std::string mtlfilename;
	ReadString(handle, mtlfilename);

	LoadMtl(mtlfilename.c_str());

	// read vertices
	uint32_t vbcount;
	TReadFile(handle, &vbcount);
	
	std::vector<StaticMeshVertex> vertices;
	vertices.resize(vbcount);
	GetFileSystem()->ReadFile(handle, vertices.data(), vertices.size() * sizeof(StaticMeshVertex));

	// read indices
	uint32_t ibcount;
	TReadFile(handle, &ibcount);

	std::vector<unsigned int> vertexIndices;
	vertexIndices.resize(ibcount);
	GetFileSystem()->ReadFile(handle, vertexIndices.data(), vertexIndices.size() * sizeof(unsigned int));

	// bbox calculation
	for (unsigned int i = 0; i < vertices.size(); i++)
	{
		
		if (i == 0)
		{
			m_boundingBox.m_min = vertices[i].position;
			m_boundingBox.m_max = vertices[i].position;
		}
		else
		{
			m_boundingBox.m_min = glm::min(m_boundingBox.m_min, vertices[i].position);
			m_boundingBox.m_max = glm::max(m_boundingBox.m_max, vertices[i].position);
		}
	}


	// create vb
	m_vb = g_renderDevice->CreateVertexBuffer(vertices.data(), (int)sizeof(StaticMeshVertex) * (int)vertices.size());
	m_vbcount = vertices.size();

	// create ib ??

	if (g_PhysicsWorld) {
		g_PhysicsWorld->AddCollisionModel(vertices.data(), vertices.size(), vertexIndices.data(), vertexIndices.size());
	}

	GetFileSystem()->CloseFile(handle);
}

void SceneStaticMesh::LoadMtl(const char* filename)
{
	FILE* file = fopen(filename, "r");
	if (file == NULL) {
		Msg("SceneStaticMesh::LoadObj: Impossible to open the file !");
		return;
	}

	while (1) {

		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 (strcmp(lineHeader, "map_Kd") == 0) {
			char stupidBuffer[1000];
			fgets(stupidBuffer, 1000, file);

			char* textureFilename = stupidBuffer + 1;

			int str_len = strlen(textureFilename);

			if (textureFilename[str_len - 1] == '\n')
				textureFilename[str_len - 1] = '\0';


			m_albedoTexture = g_texturesManager->LoadTexture2D(textureFilename, true);
		}

		//if (strcmp(lineHeader, "v") == 0) {
		//	glm::vec3 vertex;
		//	fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
		//	temp_vertices.push_back(vertex);
		//}
		//else {
		//	// Probably a comment, eat up the rest of the line
		//	char stupidBuffer[1000];
		//	fgets(stupidBuffer, 1000, file);
		//}

	}

	fclose(file);
}

static glm::mat4 s_identity = glm::mat4(1.0f);

void R_SceneStaticMesh_BindShader(const glm::mat4& worldMatrix, Texture2D* albedoTexture, DLight* light)
{
	extern Shader* g_unlitShader;
	extern Shader* g_litShader;

	SDL_assert(g_unlitShader);
	SDL_assert(g_litShader);

	Shader* shader = light ? g_litShader : g_unlitShader;

	g_shaderSystem->SetShader(shader);

	g_shaderSystem->SetUniformMatrix(shader, UNIFORM_MODEL_MATRIX, &worldMatrix[0]);

	glm::mat4 mvp = g_render->GetProjectionMatrix() * g_render->GetViewMatrix() * worldMatrix;
	g_shaderSystem->SetUniformMatrix(shader, UNIFORM_MVP_MATRIX, &mvp[0]);

	Camera* camera = g_cameraManager.GetActiveCamera();
	if (camera && shader->HasUniform(UNIFORM_CAMERA_POS))
	{
		glm::vec4 campos = glm::vec4(camera->GetPosition(), 1.0f);
		g_shaderSystem->SetUniformFloat4(shader, UNIFORM_CAMERA_POS, &campos);
	}

	if (shader->HasUniform(UNIFORM_SUN_DIRECTION))
	{
		glm::vec4 lightPos = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
		if (light)
			lightPos = glm::vec4(light->position, light->radius);

		g_shaderSystem->SetUniformFloat4(shader, UNIFORM_SUN_DIRECTION, &lightPos);
	}

	if (shader->HasUniform(UNIFORM_SUN_COLOR))
	{
		glm::vec4 lightColor = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
		if (light)
			lightColor = glm::vec4(light->color, 1.0f);

		g_shaderSystem->SetUniformFloat4(shader, UNIFORM_SUN_COLOR, &lightColor);
	}

	if (shader->HasUniform(UNIFORM_SUN_AMBIENT))
	{
		glm::vec4 lightColor = glm::vec4(g_sceneManager->getAmbientColor(), 1.0f);
		g_shaderSystem->SetUniformFloat4(shader, UNIFORM_SUN_AMBIENT, &lightColor);
	}

	g_texturesManager->SetTexture(0, albedoTexture);
	g_shaderSystem->SetUniformSampler(shader, SAMPLER_ALBEDO, 0);
}

void SceneStaticMesh::RenderObjects()
{
	glFrontFace(GL_CCW);
	glDepthFunc(GL_LESS);

	g_renderDevice->SetCullFace(true);
	g_renderDevice->SetDepthTest(true);
	g_renderDevice->SetDepthWrite(true);
	g_renderDevice->SetBlending(false);

	int numlights = DLightManager::GetInstance()->GetNumLights();
	if (!numlights)
	{
		g_renderDevice->SetVerticesBuffer(m_vb);

		R_SceneStaticMesh_BindShader(s_identity, m_albedoTexture, nullptr);

		g_renderDevice->DrawArrays(PT_TRIANGLES, 0, m_vbcount);

		g_NumModels++;
	}
	else
	{
		// first pass

		g_renderDevice->SetVerticesBuffer(m_vb);
		R_SceneStaticMesh_BindShader(s_identity, m_albedoTexture, DLightManager::GetInstance()->GetDLight(0));
		g_renderDevice->DrawArrays(PT_TRIANGLES, 0, m_vbcount);
		g_NumModels++; // overdraw YAAAY

		if (numlights > 1)
		{
			// fragment passed, can turn off depth write
			g_renderDevice->SetDepthWrite(false);

			// testing only written fragments
			glDepthFunc(GL_EQUAL);

			// additive blending
			g_renderDevice->SetBlending(true);

			g_renderDevice->SetBlendingFunction(BF_ONE, BF_ONE);

			glBlendFunc(GL_ONE, GL_ONE);
			for (int i = 1; i < numlights; i++)
			{
				g_renderDevice->SetVerticesBuffer(m_vb);
				R_SceneStaticMesh_BindShader(s_identity, m_albedoTexture, DLightManager::GetInstance()->GetDLight(i));
				g_renderDevice->DrawArrays(PT_TRIANGLES, 0, m_vbcount);
				g_NumModels++; // overdraw YAAAY
			}

			g_renderDevice->SetBlending(false);
			glDepthFunc(GL_LESS);
		}
	}
}

DLightManager* DLightManager::GetInstance()
{
	static DLightManager instance;
	return &instance;
}

DLight* DLightManager::AllocLight()
{
	return &m_dlights[m_numdlights++];
}

void DLightManager::Clear()
{
	memset(m_dlights, 0, sizeof(m_dlights));
	m_numdlights = 0;
}

DLight* DLightManager::GetDLight(int index)
{
	if (index >= m_numdlights)
		Core::Error("DLightManager::GetDLight: index is out of range");

	return &m_dlights[index];
}

int DLightManager::GetNumLights()
{
	return m_numdlights;
}

//void R_SceneStaticMesh_BindShader(const glm::mat4& worldMatrix, Texture2D* albedoTexture)
//{
//	extern Shader* g_unlitShader;
//	extern Shader* g_litShader;
//
//	SDL_assert(g_unlitShader);
//	SDL_assert(g_litShader);
//
//	Shader* shader = g_litShader;
//
//	g_shaderSystem->SetShader(shader);
//
//	g_shaderSystem->SetUniformMatrix(shader, UNIFORM_MODEL_MATRIX, &worldMatrix[0]);
//
//	glm::mat4 mvp = g_render->GetProjectionMatrix() * g_render->GetViewMatrix() * worldMatrix;
//	g_shaderSystem->SetUniformMatrix(shader, UNIFORM_MVP_MATRIX, &mvp[0]);
//
//	Camera* camera = g_cameraManager.GetActiveCamera();
//	if (camera && shader->HasUniform(UNIFORM_CAMERA_POS))
//	{
//		glm::vec4 campos = glm::vec4(camera->GetPosition(), 1.0f);
//		g_shaderSystem->SetUniformFloat4(shader, UNIFORM_CAMERA_POS, &campos);
//	}
//
//	if (shader->HasUniform(UNIFORM_SUN_DIRECTION))
//	{
//		glm::vec4 lightPos = glm::vec4(1.0f, 1.0f, 1.0f, 0.0f);
//
//		//	g_debugRender->DrawAxis(glm::vec3(lightPos));
//
//		Camera* camera = g_cameraManager.GetActiveCamera();
//		if (camera)
//			lightPos = glm::vec4(camera->GetPosition(), 1.0f);
//
//		g_shaderSystem->SetUniformFloat4(shader, UNIFORM_SUN_DIRECTION, &lightPos);
//	}
//
//	if (shader->HasUniform(UNIFORM_SUN_AMBIENT))
//	{
//		glm::vec4 lightColor = glm::vec4(0.1f);
//		g_shaderSystem->SetUniformFloat4(shader, UNIFORM_SUN_AMBIENT, &lightColor);
//	}
//
//	g_texturesManager->SetTexture(0, albedoTexture);
//	g_shaderSystem->SetUniformSampler(shader, SAMPLER_ALBEDO, 0);
//}
//
//void SceneStaticMesh::RenderObjects()
//{
//	glFrontFace(GL_CCW);
//	glDepthFunc(GL_LESS);
//
//	g_renderDevice->SetCullFace(true);
//	g_renderDevice->SetDepthTest(true);
//	g_renderDevice->SetDepthWrite(true);
//
//	g_renderDevice->SetBlending(false);
//
//	g_renderDevice->SetVerticesBuffer(m_vb);
//
//	R_SceneStaticMesh_BindShader(s_identity, m_albedoTexture);
//
//	g_renderDevice->DrawArrays(PT_TRIANGLES, 0, m_vbcount);
//
//	g_NumModels++;
//}