#include <assert.h>
#include <vector>
#include <string>
#include "ifilesystem.h"
#include "core.h"
#include "log.h"
#include "render.h"
#include "gpu_buffer.h"
#include "shader.h"
#include "shadersystem.h"
#include "renderdevice.h"
#include "model.h"
#include "modelsystem.h"
#include "texturesmanager.h"
#include "iqm.h"
#include "debugrender.h"
#include "scenemanager.h"
#include "camera.h"

extern Shader* g_unlitShader;
extern Shader* g_unlitSkinnedShader;
extern Shader* g_litShader;
extern Shader* g_litSkinnedShader;

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;
}

Model::Model()
{
	m_numPoses = 0;

	//m_boundingBox.min = glm::vec3(0.0f);
	//m_boundingBox.max = glm::vec3(0.0f);
}

Model::~Model()
{

	for (int i = 0; i < m_meshes.size(); i++)
		ReleaseModelData(m_meshes[i]);
}

// IQM

typedef unsigned char byte;
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef signed long long int llong;
typedef unsigned long long int ullong;

inline iqmheader* LoadIQMHeader(const char* filename)
{
	FileHandle_t handle = GetFileSystem()->OpenFile(filename, "rb");
	SDL_assert(handle != kInvalidFileHandleValue);

	size_t size = GetFileSystem()->GetFileLength(handle);

	char* buffer = (char*)malloc(size);
	GetFileSystem()->ReadFile(handle, buffer, size);

	GetFileSystem()->CloseFile(handle);

	return (iqmheader*)buffer;
}

inline void* IQMHeaderPtr(iqmheader* pHeader, uint offset)
{
	return ((byte*)pHeader) + offset;
}

static bool g_bIsLittleEndian = false;

static void EndianSwap_Init()
{
	union
	{
		int i;
		uchar b[sizeof(int)];
	} conv;
	conv.i = 1;

	g_bIsLittleEndian = conv.b[0] != 0;
}

static inline bool islittleendian() { union { int i; uchar b[sizeof(int)]; } conv; conv.i = 1; return conv.b[0] != 0; }
inline ushort endianswap16(ushort n) { return (n << 8) | (n >> 8); }
inline uint endianswap32(uint n) { return (n << 24) | (n >> 24) | ((n >> 8) & 0xFF00) | ((n << 8) & 0xFF0000); }
template<class T> inline T endianswap(T n) { union { T t; uint i; } conv; conv.t = n; conv.i = endianswap32(conv.i); return conv.t; }
template<> inline ushort endianswap<ushort>(ushort n) { return endianswap16(n); }
template<> inline short endianswap<short>(short n) { return endianswap16(n); }
template<> inline uint endianswap<uint>(uint n) { return endianswap32(n); }
template<> inline int endianswap<int>(int n) { return endianswap32(n); }
template<class T> inline void endianswap(T* buf, int len) { for (T* end = &buf[len]; buf < end; buf++) *buf = endianswap(*buf); }
template<class T> inline T lilswap(T n) { return islittleendian() ? n : endianswap(n); }
template<class T> inline void lilswap(T* buf, int len) { if (!islittleendian()) endianswap(buf, len); }
template<class T> inline T bigswap(T n) { return islittleendian() ? endianswap(n) : n; }
template<class T> inline void bigswap(T* buf, int len) { if (islittleendian()) endianswap(buf, len); }

template<class T> T getval(FILE* f) { T n; return fread(&n, 1, sizeof(n), f) == sizeof(n) ? n : 0; }
template<class T> T getlil(FILE* f) { return lilswap(getval<T>(f)); }
template<class T> T getbig(FILE* f) { return bigswap(getval<T>(f)); }

void Model::LoadIqm(const char* filename)
{
	iqmheader* pHdr = LoadIQMHeader(filename);
	pHdr->version = lilswap(pHdr->version);
	pHdr->filesize = lilswap(pHdr->filesize);
	pHdr->num_vertexarrays = lilswap(pHdr->num_vertexarrays);
	pHdr->num_vertexes = lilswap(pHdr->num_vertexes);
	pHdr->num_triangles = lilswap(pHdr->num_triangles);
	pHdr->num_meshes = lilswap(pHdr->num_meshes);
	pHdr->num_joints = lilswap(pHdr->num_joints);
	pHdr->num_poses = lilswap(pHdr->num_poses);
	pHdr->num_anims = lilswap(pHdr->num_anims);
	pHdr->num_frames = lilswap(pHdr->num_frames);
	pHdr->num_framechannels = lilswap(pHdr->num_framechannels);

	pHdr->ofs_vertexarrays = lilswap(pHdr->ofs_vertexarrays);
	pHdr->ofs_triangles = lilswap(pHdr->ofs_triangles);
	pHdr->ofs_meshes = lilswap(pHdr->ofs_meshes);
	pHdr->ofs_joints = lilswap(pHdr->ofs_joints);
	pHdr->ofs_poses = lilswap(pHdr->ofs_poses);
	pHdr->ofs_anims = lilswap(pHdr->ofs_anims);
	pHdr->ofs_frames = lilswap(pHdr->ofs_frames);

	m_numPoses = pHdr->num_poses;

	if (pHdr->version != IQM_VERSION)
	{
		Msg("Model::LoadIQM: %s is outdated. current is %i, model %i", filename, IQM_VERSION, pHdr->version);
		free(pHdr);
		return;
	}

	if (pHdr->filesize > (16 << 20))
	{
		Msg("Model::LoadIQM: %s is bigger(%i) than 16 MB!", filename, pHdr->filesize);
		free(pHdr);
		return;
	}

	iqmvertexarray* pVertexArrays = (iqmvertexarray*)IQMHeaderPtr(pHdr, pHdr->ofs_vertexarrays);

	float* position = nullptr;
	float* normal = nullptr;
	float* texcoord = nullptr;
	byte* weight = nullptr;
	byte* boneid = nullptr;

	for (uint32_t i = 0; i < pHdr->num_vertexarrays; i++)
	{
		const iqmvertexarray& vertarray = pVertexArrays[i];

		if (vertarray.type == IQM_POSITION)
		{
			if (vertarray.format != IQM_FLOAT || vertarray.size != 3)
				Core::Error("Model::LoadIQM: %s wrong model! position attribute are not vec3 and float.", filename);

			position = (float*)IQMHeaderPtr(pHdr, vertarray.offset);
		}
		else if (vertarray.type == IQM_NORMAL)
		{
			if (vertarray.format != IQM_FLOAT || vertarray.size != 3)
				Core::Error("Model::LoadIQM: %s wrong model! normal attribute are not vec3 and float.", filename);

			normal = (float*)IQMHeaderPtr(pHdr, vertarray.offset);
		}
		else if (vertarray.type == IQM_TEXCOORD)
		{
			if (vertarray.format != IQM_FLOAT || vertarray.size != 2)
				Core::Error("Model::LoadIQM: %s wrong model! texcoord attribute are not vec2 and float.", filename);

			texcoord = (float*)IQMHeaderPtr(pHdr, vertarray.offset);
		}
		else if (vertarray.type == IQM_BLENDWEIGHTS)
		{
			if (vertarray.format != IQM_UBYTE || vertarray.size != 4)
				Core::Error("Model::LoadIQM: %s wrong model! bone weights attribute are not vec4 and ubyte.", filename);

			weight = (byte*)IQMHeaderPtr(pHdr, vertarray.offset);
		}
		else if (vertarray.type == IQM_BLENDINDEXES)
		{
			if (vertarray.format != IQM_UBYTE || vertarray.size != 4)
				Core::Error("Model::LoadIQM: %s wrong model! bone indices attribute are not vec4 and ubyte.", filename);

			boneid = (byte*)IQMHeaderPtr(pHdr, vertarray.offset);
		}
	}

	std::vector< SkinnedMeshVertex > vertices;
	vertices.resize(pHdr->num_vertexes);

	std::vector< uint > indices;
	indices.resize(pHdr->num_triangles * 3);

	iqmmesh* pMeshes = (iqmmesh*)IQMHeaderPtr(pHdr, pHdr->ofs_meshes);
	iqmtriangle* pTriangles = (iqmtriangle*)IQMHeaderPtr(pHdr, pHdr->ofs_triangles);
//	Material* pMaterial = nullptr;
	bool firstMesh = true;

	for (int i = 0; i < pHdr->num_meshes; ++i)
	{
		iqmmesh* pMesh = &pMeshes[i];

		for (uint32_t j = 0; j < pMesh->num_vertexes; ++j)
		{
			memcpy(&vertices[j].position, &position[(pMesh->first_vertex + j) * 3], sizeof(vertices[j].position));
			memcpy(&vertices[j].normal, &normal[(pMesh->first_vertex + j) * 3], sizeof(vertices[j].normal));
			memcpy(&vertices[j].texcoord, &texcoord[(pMesh->first_vertex + j) * 2], sizeof(vertices[j].texcoord));

			if (boneid)
			{
				vertices[j].boneIds.x = (float)boneid[(pMesh->first_vertex + j) * 4];
				vertices[j].boneIds.y = (float)boneid[(pMesh->first_vertex + j) * 4 + 1];
				vertices[j].boneIds.z = (float)boneid[(pMesh->first_vertex + j) * 4 + 2];
				vertices[j].boneIds.w = (float)boneid[(pMesh->first_vertex + j) * 4 + 3];
			}
			else
			{
				vertices[j].boneIds.x = 0.0f;
				vertices[j].boneIds.y = 0.0f;
				vertices[j].boneIds.z = 0.0f;
				vertices[j].boneIds.w = 0.0f;
			}

			if (weight)
			{
				vertices[j].weights.x = weight[(pMesh->first_vertex + j) * 4] / 255.0f;
				vertices[j].weights.y = weight[(pMesh->first_vertex + j) * 4 + 1] / 255.0f;
				vertices[j].weights.z = weight[(pMesh->first_vertex + j) * 4 + 2] / 255.0f;
				vertices[j].weights.w = weight[(pMesh->first_vertex + j) * 4 + 3] / 255.0f;
			}
			else
			{
				vertices[j].weights.x = 0.0f;
				vertices[j].weights.y = 0.0f;
				vertices[j].weights.z = 0.0f;
				vertices[j].weights.w = 0.0f;
			}

			if (i == 0 && (firstMesh && j == 0))
			{
				m_boundingBox.m_min = vertices[j].position;
				m_boundingBox.m_max= vertices[j].position;
			}
			else
			{
				m_boundingBox.m_min = glm::min(m_boundingBox.m_min, vertices[j].position);
				m_boundingBox.m_max = glm::max(m_boundingBox.m_max, vertices[j].position);
			}
		}

		// Re-order triangle face counting to counter-clockwise
		for (uint j = 0; j < pMesh->num_triangles; j++)
		{
			uint a = pTriangles[pMesh->first_triangle + j].vertex[0];
			uint b = pTriangles[pMesh->first_triangle + j].vertex[1];
			uint c = pTriangles[pMesh->first_triangle + j].vertex[2];
			indices[j * 3 + 0] = c - pMesh->first_vertex;
			indices[j * 3 + 1] = b - pMesh->first_vertex;
			indices[j * 3 + 2] = a - pMesh->first_vertex;
		}

		const char* pMaterialname = nullptr;

		if (pHdr->ofs_text)
		{
			// #TODO: weird weird getting material name string is so wrong

			const char* str = (char*)pHdr + pHdr->ofs_text;
			const char* materialname = &str[pMesh->material];

			if (materialname)
				pMaterialname = materialname;
				//pMaterial = g_materialSystem.LoadMaterial(materialname);
		}

		ModelData_t mesh = {};
		//mesh.m_vertices = vertices;
		//mesh.m_indices = indices;
		mesh.vb = g_renderDevice->CreateVertexBuffer(vertices.data(), pMesh->num_vertexes * sizeof(SkinnedMeshVertex), true);
		mesh.vbcount = pMesh->num_vertexes;
		mesh.ib = g_renderDevice->CreateIndexBuffer(indices.data(), pMesh->num_triangles * 3 * sizeof(uint), false);
		mesh.ibcount = pMesh->num_triangles * 3;


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

		//mesh.m_material = pMaterial;
		//
		//if (!mesh.m_material)
		//	mesh.m_material = g_materialSystem.CreateMaterial("default_model", "textures/system/notex.tga", NULL, true);

		m_meshes.push_back(mesh);

		firstMesh = false;
	}

	// get string data
	char* pStrings = (char*)IQMHeaderPtr(pHdr, pHdr->ofs_text);

	// load joints
	iqmjoint* pJoints = (iqmjoint*)IQMHeaderPtr(pHdr, pHdr->ofs_joints);
	m_joints.resize(pHdr->num_joints);

	for (uint32_t i = 0; i < pHdr->num_joints; i++)
	{
		const char* jointName = &pStrings[pJoints[i].name];
		strncpy(m_joints[i].name, jointName, sizeof(m_joints[i].name));

		m_joints[i].parentId = pJoints[i].parent;

		memcpy(&m_joints[i].origin, pJoints[i].translate, sizeof(glm::vec3));
		memcpy(&m_joints[i].orient, pJoints[i].rotate, sizeof(glm::vec4));

		m_joints[i].orient = normalize(m_joints[i].orient);

		//Msg("%i origin %.2f %.2f %.2f, orient %.2f %.2f %.2f %.2f(w)",
		//	i,
		//	m_joints[i].origin.x, m_joints[i].origin.y, m_joints[i].origin.z,
		//	m_joints[i].orient.x, m_joints[i].orient.y, m_joints[i].orient.z, m_joints[i].orient.w);
	}

	m_basePose = m_joints;
	m_inverseBasePose.resize(m_basePose.size());

	for (int i = 0; i < m_basePose.size(); i++)
	{
		// calculate inverse transform matrix
		glm::mat4 mat = glm::translate(glm::mat4(1.0f), m_basePose[i].origin);
		mat *= glm::toMat4(m_basePose[i].orient);

		m_inverseBasePose[i] = glm::inverse(mat);
		if (m_basePose[i].parentId >= 0)
		{
			m_inverseBasePose[i] *= m_inverseBasePose[m_basePose[i].parentId];
		}
	}

	// load animations
	iqmanim* pAnims = (iqmanim*)IQMHeaderPtr(pHdr, pHdr->ofs_anims);
	iqmpose* pPoses = (iqmpose*)IQMHeaderPtr(pHdr, pHdr->ofs_poses);
	ushort* pFramedata = (ushort*)IQMHeaderPtr(pHdr, pHdr->ofs_frames);

	// #TODO: tracks are Array class, with resize no ctor are called
	// m_animations.resize(pHdr->num_anims);

	//m_jointTranslation.resize(pHdr->num_frames* pHdr->num_poses);

	glm::vec3 origin;
	glm::vec3 scale;
	glm::quat orient;

	for (uint32_t i = 0; i < pHdr->num_anims; i++)
	{
		Animation animation;

		const char* animName = &pStrings[pAnims[i].name];
		strncpy(animation.name, animName, sizeof(animation.name));

		animation.numPoses = m_numPoses;
		animation.numFrames = pAnims[i].num_frames;
		animation.framerate = pAnims[i].framerate;
		if (animation.framerate <= 0.0f)
			animation.framerate = 30.0f;

		Msg("Animation: %s %i %i %i", animName, pAnims[i].first_frame, pAnims[i].num_frames, pAnims[i].framerate);

		animation.origin.resize(pHdr->num_poses * pAnims[i].num_frames);
		animation.orient.resize(pHdr->num_poses * pAnims[i].num_frames);

		int dcounter = pAnims[i].first_frame * pHdr->num_framechannels;

		for (uint32_t j = 0; j < pAnims[i].num_frames; j++)
		{
			for (uint32_t k = 0; k < pHdr->num_poses; k++)
			{
				const iqmpose& p = pPoses[k];
				origin.x = p.channeloffset[0]; if (p.mask & 0x01) origin.x += *pFramedata++ * p.channelscale[0];
				origin.y = p.channeloffset[1]; if (p.mask & 0x02) origin.y += *pFramedata++ * p.channelscale[1];
				origin.z = p.channeloffset[2]; if (p.mask & 0x04) origin.z += *pFramedata++ * p.channelscale[2];
				orient.x = p.channeloffset[3]; if (p.mask & 0x08) orient.x += *pFramedata++ * p.channelscale[3];
				orient.y = p.channeloffset[4]; if (p.mask & 0x10) orient.y += *pFramedata++ * p.channelscale[4];
				orient.z = p.channeloffset[5]; if (p.mask & 0x20) orient.z += *pFramedata++ * p.channelscale[5];
				orient.w = p.channeloffset[6]; if (p.mask & 0x40) orient.w += *pFramedata++ * p.channelscale[6];
				scale.x = p.channeloffset[7];  if (p.mask & 0x80) scale.x += *pFramedata++ * p.channelscale[7];
				scale.y = p.channeloffset[8];  if (p.mask & 0x100) scale.y += *pFramedata++ * p.channelscale[8];
				scale.z = p.channeloffset[9];  if (p.mask & 0x200) scale.z += *pFramedata++ * p.channelscale[9];

				// normalize orientation
				orient = glm::normalize(orient);

				// assign to the animation track
				animation.origin[j * pHdr->num_poses + k] = origin;
				animation.orient[j * pHdr->num_poses + k] = orient;
			}
		}

		m_animations.push_back(animation);
	}

	m_jointMatrices.resize(m_joints.size());
	m_finalMatrices.resize(m_joints.size());

	// matrices
	for (int i = 0; i < m_joints.size(); ++i)
	{
		glm::mat4 mat = glm::translate(glm::mat4(1.0f), m_joints[i].origin);
		mat *= glm::toMat4(m_joints[i].orient);

		if (m_joints[i].parentId >= 0)
			m_jointMatrices[i] = m_jointMatrices[m_joints[i].parentId] * mat;
		else
			m_jointMatrices[i] = mat;

		m_finalMatrices[i] = m_jointMatrices[i] * m_inverseBasePose[i];
	}

	free(pHdr);
}

void Model::LoadObj(const char* filename)
{
	Msg("Loading OBJ file %s...", 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("Model::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("Model::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);

		m_boundingBox.m_min = glm::min(m_boundingBox.m_min, vertex);
		m_boundingBox.m_max = glm::max(m_boundingBox.m_max, vertex);
	}

	m_Vertices = vertices;

	ModelData_t m_data = {};
	m_data.vb = g_renderDevice->CreateVertexBuffer(vertices.data(), (int)sizeof(StaticMeshVertex) * (int)vertices.size());
	m_data.vbcount = vertices.size();
	m_meshes.push_back(m_data);

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

Texture2D* Model::LoadMtl(const char* filename)
{
	Msg("Loading MTL file %s...", filename);

	Texture2D* pAlbedo = nullptr;

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

	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);

			const char* textureFilename = stupidBuffer + 1;
			pAlbedo = 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);

	return pAlbedo;
}

void Model::Draw(const glm::mat4& model, SkeletonInstance* instance /*= nullptr*/)
{
	SDL_assert(g_unlitShader);

	glm::vec3 pos = model[3];

	Shader* shader = g_unlitSkinnedShader; //instance ? g_unlitSkinnedShader : g_unlitShader;

	static glm::mat4 s_identityBones[128];
	static bool ready = false;
	if (!ready)
	{
		for (int i = 0; i < 128; i++)
			s_identityBones[i] = glm::mat4(1.0f);

		ready = true;
	}

	DLight* light = nullptr;
	
	float lastDistSq = FLT_MAX;

	for (int i = 0; i < DLightManager::GetInstance()->GetNumLights(); i++)
	{
		float distSq = glm::length2(pos - DLightManager::GetInstance()->GetDLight(i)->position );

		if (distSq <= lastDistSq)
		{
			lastDistSq = distSq;
			light = DLightManager::GetInstance()->GetDLight(i);
		}
	}

	if (light)
		shader = instance ? g_litSkinnedShader : g_litShader;

	for (int i = 0; i < m_meshes.size(); i++)
	{
		ModelData_t& m_data = m_meshes[i];

		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_data.vb);
		if (m_data.ib)
			g_renderDevice->SetIndicesBuffer(m_data.ib);

		g_shaderSystem->SetShader(shader);
		g_shaderSystem->SetUniformMatrix(shader, UNIFORM_MODEL_MATRIX, &model[0]);
		
		g_shaderSystem->SetUniformMatrix(shader, UNIFORM_PROJ_MATRIX, &g_render->GetProjectionMatrix()[0]);
		g_shaderSystem->SetUniformMatrix(shader, UNIFORM_VIEW_MATRIX, &g_render->GetViewMatrix()[0]);

		glm::mat4 mvp = glm::identity<glm::mat4>();
		mvp = g_render->GetProjectionMatrix() * g_render->GetViewMatrix() * model;
		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);
		}

		if (!m_data.m_AlbedoTexture)
			m_data.m_AlbedoTexture = g_texturesManager->LoadTexture2D("MustBeEvilHackButDontCare");

		g_texturesManager->SetTexture(0, m_data.m_AlbedoTexture);
		g_shaderSystem->SetUniformSampler(shader, SAMPLER_ALBEDO, 0);

		if (instance)
			g_shaderSystem->SetUniformMatrix(shader, UNIFORM_BONE_MATRICES, instance->m_finalMatrices.data(), instance->m_finalMatrices.size());
		else
			g_shaderSystem->SetUniformMatrix(shader, UNIFORM_BONE_MATRICES, s_identityBones, 128);

		if (m_data.ib)
			g_renderDevice->DrawElements(PT_TRIANGLES, m_data.ibcount, 0, NULL);
		else
			g_renderDevice->DrawArrays(PT_TRIANGLES, 0, m_data.vbcount);
		
	}

	// debug draw
	//if (instance)
	//{
	//	glm::mat4 worldMat;
	//	glm::mat4 worldMatParent;
	//	for (int i = 0; i < instance->m_jointMatrices.size(); i++)
	//	{
	//		worldMat = model * instance->m_jointMatrices[i];
	//		glm::vec3 worldPos = worldMat[3];
	//	//	g_debugRender->DrawAxis(worldPos);

	//		const Joint& joint = instance->m_joints[i];
	//		if (joint.parentId != -1)
	//		{
	//			worldMatParent = model * instance->m_jointMatrices[joint.parentId];
	//			g_debugRender->DrawLine(worldMat[3], worldMatParent[3], glm::vec3(1.0f, 1.0f, 1.0f));
	//		}
	//	}
	//}

}

AnimationId_t Model::FindAnimation(const char* name)
{
	for (int i = 0, numAnims = GetNumAnimations(); i < numAnims; i++)
	{
		if (strcmp(m_animations[i].name, name) == 0)
			return i;
	}

	return -1;
}

uint32_t Model::GetNumAnimations()
{
	return (uint32_t)m_animations.size();
}

const Animation* Model::GetAnimation(AnimationId_t index)
{
	if (-1 >= index || index >= GetNumAnimations())
		Core::Error("Model::GetAnimation: index %d is out of bounds.", index);

	return &m_animations[index];
}

int Model::GetNumPoses()
{
	return m_numPoses;
}

SkeletonInstance* Model::CreateSkeletonInstance()
{
	SkeletonInstance* instance = new SkeletonInstance();
	instance->m_model = this;
	instance->m_joints = m_joints;
	instance->m_jointMatrices.resize(m_joints.size());
	instance->m_finalMatrices.resize(m_joints.size());
	instance->m_animation = INVALID_ANIMATION_HANDLE;
	instance->m_time = 0.0;
	instance->m_looped = false;

	for (int i = 0; i < m_joints.size(); ++i)
	{
		glm::mat4 mat = glm::translate(glm::mat4(1.0f), m_joints[i].origin);
		mat *= glm::toMat4(m_joints[i].orient);

		if (m_joints[i].parentId >= 0)
			instance->m_jointMatrices[i] = instance->m_jointMatrices[m_joints[i].parentId] * mat;
		else
			instance->m_jointMatrices[i] = mat;

		instance->m_finalMatrices[i] = instance->m_jointMatrices[i] * m_inverseBasePose[i];
	}

	return instance;
}

void Model::UpdateSkeletonInstance(SkeletonInstance* instance, float dt)
{
	if (!instance)
		return;

	if (instance->m_animation == -1)
		return;

	const Animation& animation = m_animations[instance->m_animation];

	instance->m_time += dt;

	float frameTime = 1.0f / animation.framerate;
	float totalDuration = (animation.numFrames - 1) * frameTime;

	if (instance->m_time >= totalDuration && instance->m_looped)
		instance->m_time = 0.0f;

	float animationFrame = instance->m_time / frameTime;
	int frameA = (int)(floor(animationFrame));
	int frameB = (frameA + 1) % animation.numFrames;
	float t = animationFrame - frameA;

	if (!instance->m_looped && frameA >= animation.numFrames - 1)
		frameA = animation.numFrames - 1;

	if (instance->m_time >= totalDuration)
	{
		frameA = animation.numFrames - 1;
		frameB = animation.numFrames - 1;
		t = 0.0f;
	}

	for (int i = 0; i < instance->m_joints.size(); ++i)
	{
		const glm::vec3& a = animation.origin[frameA * m_numPoses + i];
		const glm::vec3& b = animation.origin[frameB * m_numPoses + i];

		const glm::quat& qa = animation.orient[frameA * m_numPoses + i];
		const glm::quat& qb = animation.orient[frameB * m_numPoses + i];

		instance->m_joints[i].origin = glm::lerp(a, b, t);
		instance->m_joints[i].orient = glm::normalize(glm::slerp(qa, qb, t));
	}

	// matrices
	for (int i = 0; i < instance->m_joints.size(); ++i)
	{
		glm::mat4 mat = glm::translate(glm::mat4(1.0f), instance->m_joints[i].origin);
		mat *= glm::toMat4(instance->m_joints[i].orient);

		if (m_joints[i].parentId >= 0)
			instance->m_jointMatrices[i] = instance->m_jointMatrices[m_joints[i].parentId] * mat;
		else
			instance->m_jointMatrices[i] = mat;

		instance->m_finalMatrices[i] = instance->m_jointMatrices[i] * m_inverseBasePose[i];
	}

}

void ReleaseModelData(ModelData_t& data)
{
	if (data.ib)
	{
		delete data.ib;
		data.ib = nullptr;
	}

	if (data.vb)
	{
		delete data.vb;
		data.vb = nullptr;
	}
}
