#include "Utils.h"
#include <fstream>
#include <string>
#include <vector>
#include <iostream>
#include <GL/glfw.h>
#include <map>
#include <Windows.h>

using namespace std;

namespace GLUtils
{
	void CalculateFrameRate()
	{
		// Thanks to http://stackoverflow.com/questions/5627803/calculate-fps-in-opengl
		static float framesPerSecond = 0.0f; // This will store our fps
		static float lastTime = 0.0f;		 // This will hold the time from the last frame
		float currentTime = GetTickCount() * 0.001f;    
		++framesPerSecond;
		if( currentTime - lastTime > 1.0f )
		{
			lastTime = currentTime;
			std:: cout << "FPS: " << (int)framesPerSecond << std::endl;
			framesPerSecond = 0;
		}
	}

	glm::vec3 Lerp(glm::vec3 aFromPos, glm::vec3 aToPos, float aTime)
	{
		// Thanks to http://stackoverflow.com/questions/14853845/lerp-implementation-for-a-tween
		return (1-aTime)*aFromPos + aTime*aToPos;
	}


	////
	//	Used for LoadShaders
	////
	string LoadLinesFromFile(const char* aFile)
	{
		string lines;

		if (aFile)
		{
			ifstream stream(aFile, ios::in);
			if (stream.is_open())
			{
				string line = "";
				while (getline(stream, line))
					lines += "\n" + line;

				stream.close();
			}
		}

		return lines;
	}

	GLuint CheckShader(GLuint aShaderId)
	{
		GLint result = GL_FALSE;
		int logLength = 0;

		glGetShaderiv(aShaderId, GL_COMPILE_STATUS, &result);
		glGetShaderiv(aShaderId, GL_INFO_LOG_LENGTH, &logLength);
		if (logLength > 1) // For some reason, always had 1 character: the ""
		{
			char* errorMessage = new char[logLength+1];
			glGetShaderInfoLog(aShaderId, logLength, NULL, errorMessage);
			cerr << errorMessage << endl;
		}

		return result;
	}

	GLint CheckProgram(GLuint aProgramId)
	{
		GLint result = GL_FALSE;
		int logLength = 0;

		glGetShaderiv(aProgramId, GL_COMPILE_STATUS, &result);
		glGetShaderiv(aProgramId, GL_INFO_LOG_LENGTH, &logLength);
		if (logLength > 1) // For some reason, always had 1 character: the ""
		{
			char* errorMessage = new char[logLength+1];
			glGetProgramInfoLog(aProgramId, logLength, NULL, errorMessage);
			cerr << errorMessage << endl;
		}

		return result;
	}

	GLuint CompileCheckShader(GLuint aType, string& aCode)
	{
		GLuint id = glCreateShader(aType);
		const char* codeStr = aCode.c_str();
		glShaderSource(id, 1, &codeStr, NULL);
		glCompileShader(id);

		// Check!
		GLint result = CheckShader(id);

		return id;
	}

	GLuint LoadShaders(const char* aVertFilePath, const char* aFragFilePath)
	{
		string vsCode = LoadLinesFromFile(aVertFilePath);
		string fsCode = LoadLinesFromFile(aFragFilePath);

		GLuint result = 0;

		if (vsCode.length() > 0 && fsCode.length() > 0)
		{
			GLuint vsId = CompileCheckShader(GL_VERTEX_SHADER, vsCode);
			GLuint fsId = CompileCheckShader(GL_FRAGMENT_SHADER, fsCode);

			// Link the shaders with a program
			GLuint progId = glCreateProgram();
			glAttachShader(progId, vsId);
			glAttachShader(progId, fsId);
			glLinkProgram(progId);

			CheckProgram(progId);
			result = progId;

			// Clean up
			glDeleteShader(vsId);
			glDeleteShader(fsId);
		}

		return result;
	}
	// End LoadShaders

	GLuint LoadTGA(const char* aFilePath)
	{
		// Create a texture
		GLuint textureId;
		glGenTextures(1, &textureId);

		// Bind the texture, any functions modifying textures will use this texture now
		glBindTexture(GL_TEXTURE_2D, textureId);

		// Load the texture from the file path
		glfwLoadTexture2D(aFilePath, 0);

		// Trilinear filtering
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glGenerateMipmap(GL_TEXTURE_2D);

		return textureId;
	}

	////
	//	Used for LoadObj
	////
	glm::vec3 Get3Floats(char* aStr, char aSep)
	{
		// Used for v and vn (example: v 1.000000 -1.000000 1.000000)
		char* nextSpace = strchr(aStr, aSep);
		char* lastSpace = strchr(nextSpace+1, aSep);

		glm::vec3 v;
		// atof works because it will stop if it finds a space
		v.x = atof(aStr);
		v.y = atof(nextSpace+1); // +1 since they start with a space
		v.z = atof(lastSpace);

		return v;
	}

	glm::vec2 Get2Floats(char* aStr, char aSep)
	{
		// Used for vt (example: vt 0.499422 0.500239)
		// Functions just like Get3Floats
		char* nextSpace = strchr(aStr, aSep);

		glm::vec2 v;
		v.x = atof(aStr);
		v.y = atof(nextSpace+1);

		return v;
	}

	char* Get3Ints(char* aStr, char aSep, unsigned int* aOut)
	{
		// This is used to get the faces information (Example:1/2/3 2/3/1 3/1/2)
		char* firstSep = strchr(aStr, aSep);
		char* nextSep = strchr(firstSep+1, aSep); // +1 because firstStep starts at /

		aOut[0] = atoi(aStr);
		aOut[1] = atoi(firstSep+1);
		aOut[2] = atoi(nextSep+1);

		// Return string minus the 3 ints we took out
		char* retString = strchr(aStr, ' '); // Have to hardcode ' ' here because aSep is '/'

		if (!retString) // Last of the 3 points for a face
			return 0;

		return retString+1; // +1 because retString starts with a space
	}

	bool LoadObj(const char* aFilePath, std::vector<glm::vec3>& aOutVerts, std::vector<glm::vec2>& aOutUVs, std::vector<glm::vec3>& aOutNorms)
	{
		bool loaded = false;

		// Quick check
		if (aFilePath == 0)
			return loaded;

		ifstream f;
		f.open(aFilePath);

		std::vector<glm::vec3> tempVertBuffer;
		std::vector<glm::vec2> tempUVBuffer;
		std::vector<glm::vec3> tempNormBuffer;

		/* // OBJ prefixes \\

			#	- Comment
			v	- Vertex
			vt	- UV coordinate
			nv	- Vertex normal
			f	- Face (example 1, 2, 3: 1st VERTEX in file/2nd TEXTURE COORDINATE in file, 3rd VERTEX NORMAL in file)
		*/

		const int bufferSize = 256;
		char buffer[bufferSize];

		// This is because in some of my OBJs, there were two sets of v, vt, vn, and f,
		// (one right after the last ones f) and I want to stop after the first one.
		bool fFound = false;

		while (f.good())
		{
			f.getline(buffer, bufferSize);

			// # found. Ignore
			char* result = strchr(buffer, '#');
			if (result != 0)
				continue;

			// v found. Put the vec3 into the tempVertBuffer
			result = strstr(buffer, "v ");
			if (result != 0)
			{
				if (fFound)
					break;

				tempVertBuffer.push_back(Get3Floats(result+2, ' '));
				continue;
			}

			// vt found. Put the vec2 into the tempUVBuffer
			result = strstr(buffer, "vt ");
			if (result != 0)
			{
				tempUVBuffer.push_back(Get2Floats(result+3, ' '));
				continue;
			}

			// vn found. Put the vec3 into the tempNormBuffer
			result = strstr(buffer, "vn ");
			if (result != 0)
			{
				tempNormBuffer.push_back(Get3Floats(result+3, ' '));
				continue;
			}

			// f found...
			result = strstr(buffer, "f ");
			if (result != 0)
			{
				fFound = true;
				unsigned int f0[3];
				unsigned int f1[3];
				unsigned int f2[3];

				char* temp = strchr(result, '/');
				if (temp != 0)
				{
					// Get the 3 vert/uv/norm numbers from the face...
					result = Get3Ints(result+2, '/', f0);
					result = Get3Ints(result, '/', f1);
					result = Get3Ints(result, '/', f2);

					// Take those from the temp buffers and put them into the aOutBuffers
					// Remember to -1 for the index, since the obj starts at 1, while C++ starts at 0
					// Face vert 1
					aOutVerts.push_back(tempVertBuffer[f0[0]-1]);
					aOutUVs.push_back(tempUVBuffer[f0[1]-1]);
					aOutNorms.push_back(tempNormBuffer[f0[2]-1]);

					// Face vert 2
					aOutVerts.push_back(tempVertBuffer[f1[0]-1]);
					aOutUVs.push_back(tempUVBuffer[f1[1]-1]);
					aOutNorms.push_back(tempNormBuffer[f1[2]-1]);

					// Face vert 3
					aOutVerts.push_back(tempVertBuffer[f2[0]-1]);
					aOutUVs.push_back(tempUVBuffer[f2[1]-1]);
					aOutNorms.push_back(tempNormBuffer[f2[2]-1]);
				}

				loaded = true;
			}
		}

		return loaded;
	}
	// End LoadObj

	////
	//	Used for IndexVBO
	////
	struct PackedVertex
	{
		glm::vec3 mPosition;
		glm::vec2 mUV;
		glm::vec3 mNormal;
		// Need to override the < operator, because the map uses it. To be honest, not completely sure how memcmp works...
		bool operator<(const PackedVertex that) const { return memcmp((void*)this, (void*)&that, sizeof(PackedVertex))>0; }
	};

	typedef std::map<PackedVertex, unsigned short> PVMap;

	bool GetSimilarVertex(PackedVertex& aPVToCheck, PVMap& aVertexToOutIndex, unsigned short& aResult)
	{
		PVMap::iterator it = aVertexToOutIndex.find(aPVToCheck);

		if (it == aVertexToOutIndex.end()) // The iterator makes it to the end if it doesn't find it
			return false;
		else
		{
			aResult = it->second;
			return true;
		}
	}

	void IndexVBO(
			std::vector<glm::vec3>& aInVerts,
			std::vector<glm::vec2>& aInUVs,
			std::vector<glm::vec3>& aInNorms,

			std::vector<unsigned short>& aOutIndices,
			std::vector<glm::vec3>& aOutVerts,
			std::vector<glm::vec2>& aOutUVs,
			std::vector<glm::vec3>& aOutNorms
			)
	{
		PVMap VertexToOutIndex;

		// Go through each aInVerts...
		for (unsigned int i = 0; i < aInVerts.size(); ++i)
		{
			PackedVertex packed = { aInVerts[i], aInUVs[i], aInNorms[i] };

			// And see there is a similar vertex already in VertexToOutIndex
			unsigned short index;
			bool found = GetSimilarVertex(packed, VertexToOutIndex, index);

			if (found) // A similar one is found, use that indice instead of putting same information into vertex/uv/norm
				aOutIndices.push_back(index);
			else // Not found, put all information into output data
			{
				aOutVerts.push_back(aInVerts[i]);
				aOutUVs.push_back(aInUVs[i]);
				aOutNorms.push_back(aInNorms[i]);
				unsigned short newIndex = (unsigned short)aOutVerts.size() - 1;
				aOutIndices.push_back(newIndex);
				VertexToOutIndex[packed] = newIndex;
			}
		}
	}


	struct Vertex
	{
		glm::vec3 mPosition;
		// Need to override the < operator, because the map uses it. To be honest, not completely sure how memcmp works...
		bool operator<(const Vertex that) const { return memcmp((void*)this, (void*)&that, sizeof(Vertex))>0; }
	};

	typedef std::map<Vertex, unsigned short> VMap;

	bool GetSimilarVertex(Vertex& aVToCheck, VMap& aVertexToOutIndex, unsigned short& aResult)
	{
		VMap::iterator it = aVertexToOutIndex.find(aVToCheck);

		if (it == aVertexToOutIndex.end()) // The iterator makes it to the end if it doesn't find it
			return false;
		else
		{
			aResult = it->second;
			return true;
		}
	}

	void IndexVBO(
			std::vector<glm::vec3>& aInVerts,

			std::vector<unsigned short>& aOutIndices,
			std::vector<glm::vec3>& aOutVerts
			)
	{
		VMap VertexToOutIndex;

		// Go through each aInVerts...
		for (unsigned int i = 0; i < aInVerts.size(); ++i)
		{
			Vertex packed = { aInVerts[i] };

			// And see there is a similar vertex already in VertexToOutIndex
			unsigned short index;
			bool found = GetSimilarVertex(packed, VertexToOutIndex, index);

			if (found) // A similar one is found, use that indice instead of putting same information into vertex/uv/norm
				aOutIndices.push_back(index);
			else // Not found, put all information into output data
			{
				aOutVerts.push_back(aInVerts[i]);
				unsigned short newIndex = (unsigned short)aOutVerts.size() - 1;
				aOutIndices.push_back(newIndex);
				VertexToOutIndex[packed] = newIndex;
			}
		}
	}
	// End IndexVBO
}