#include <fstream>
#include <vector>

#include "objLoader.h"
#include "vertex.h"

using namespace std;
using namespace stdext;
using namespace Vertices;

namespace CBK
{
	namespace Shapes
	{
		objLoader::objLoader(const std::string& fileName, const MeshParam& params) : Mesh(params, fileName)
		{
			D3DXDeclaratorFromFVF(Vertex::FVF, vertexElement);
			load(fileName, params);
		}

		objLoader::~objLoader(void)
		{
		}

		UINT objLoader::stringToUInt(char* const buffer)
		{
			UINT placeVal = 1;
			UINT value = 0;
			const char* const start = buffer;
			char* end = buffer;

			//Find the end of the string
			while ((*end) != '\0')
				end++;

			end--;
			while (end >= start)
			{
				switch (*end)
				{
				case '1':
					value += placeVal;
					break;
				case '2':
					value += placeVal * 2;
					break;
				case '3':
					value += placeVal * 3;
					break;
				case '4':
					value += placeVal * 4;
					break;
				case '5':
					value += placeVal * 5;
					break;
				case '6':
					value += placeVal * 6;
					break;
				case '7':
					value += placeVal * 7;
					break;
				case '8':
					value += placeVal * 8;
					break;
				case '9':
					value += placeVal * 9;
					break;
				}
				placeVal *= 10;
				end--;
			}

			return value;
		}

		item& objLoader::load(const std::string& fileName, const MeshParam& params)
		{
			//Make sure the device is good
			if (params.device != 0)
				_device = params.device;
			if (_device == 0) return *this;

			//Load from file
			UINT vertices = 0;
			UINT faces = 0;
			UINT normals = 0;
			UINT texCoords = 0;

			vector<D3DXVECTOR3*> tVertices;
			vector<D3DXVECTOR3*> tNormals;
			vector<D3DXVECTOR2*> tTextures;
			vector<vector<UINT>*> tFaces;
			{
				ifstream myObj(fileName.c_str());
				D3DXVECTOR3* currentVertex;
				D3DXVECTOR2* currentTexCoord;
				vector<UINT>* currentFace;
				char cBuffer[512];
				enum
				{
					READTYPE_UNKNOWN,
					READTYPE_VERTEX,
					READTYPE_FACE,
					READTYPE_NORMAL,
					READTYPE_TEXCOORD
				} curType = READTYPE_UNKNOWN;
				UINT position = 0;

				while (myObj.good())
				{
					if (curType == READTYPE_UNKNOWN)
					{
						string lBuffer;
						myObj >> lBuffer;

						if (lBuffer == "v")
						{
							currentVertex = new D3DXVECTOR3(0.0f, 0.0f, 0.0f);
							tVertices.insert(tVertices.end(), currentVertex);
							position = 0;
							curType = READTYPE_VERTEX;
							vertices++;
						}
						else if (lBuffer == "f")
						{
							currentFace = new vector<UINT>();
							tFaces.insert(tFaces.end(), currentFace);
							position = 0;
							curType = READTYPE_FACE;
							faces++;
						}
						else if (lBuffer == "vn")
						{
							currentVertex = new D3DXVECTOR3(0.0f, 0.0f, 0.0f);
							tNormals.insert(tNormals.end(), currentVertex);
							position = 0;
							curType = READTYPE_NORMAL;
							normals++;
						}
						else if (lBuffer == "vt")
						{
							currentTexCoord = new D3DXVECTOR2(0.0f, 0.0f);
							tTextures.insert(tTextures.end(), currentTexCoord);
							position = 0;
							curType = READTYPE_TEXCOORD;
							texCoords++;
						}
						else //any lines we don't know how to handle, ignore
						{
							myObj.getline(cBuffer, 512);
						}
					}
					else if (curType == READTYPE_VERTEX)
					{
						float fBuffer;
						myObj >> fBuffer;

						(*currentVertex)[position] = fBuffer;
						position++;

						if (position >= 3)
							curType = READTYPE_UNKNOWN;
					}
					else if (curType == READTYPE_NORMAL)
					{
						float fBuffer;
						myObj >> fBuffer;

						(*currentVertex)[position] = fBuffer;
						position++;

						if (position >= 3)
							curType = READTYPE_UNKNOWN;
					}
					else if (curType == READTYPE_TEXCOORD)
					{
						float fBuffer;
						myObj >> fBuffer;

						(*currentTexCoord)[position] = fBuffer;
						position++;

						if (position >= 2)
							curType = READTYPE_UNKNOWN;
					}
					else if (curType == READTYPE_FACE)
					{
						string sBuffer;
						myObj >> sBuffer;
						string::iterator iterator;
						string::iterator end = sBuffer.end();
						int pos = 0;

						for (iterator = sBuffer.begin(); (iterator != end) && ((*iterator) != '/') && ((*iterator) != ' ') && ((*iterator) != '\n'); iterator++)
						{
							cBuffer[pos] = *iterator;
							pos++;
						}
						cBuffer[pos] = '\0';
						if (iterator != end)
							iterator++;

						currentFace->push_back(stringToUInt(cBuffer));
						position++;

						pos = 0;
						for (; (iterator != end) && ((*iterator) != '/') && ((*iterator) != ' ') && ((*iterator) != '\n'); iterator++)
						{
							cBuffer[pos] = *iterator;
							pos++;
						}
						cBuffer[pos] = '\0';
						if (iterator != end)
							iterator++;

						currentFace->push_back(stringToUInt(cBuffer));
						position++;

						pos = 0;
						for (; (iterator != end) && ((*iterator) != '/') && ((*iterator) != ' ') && ((*iterator) != '\n'); iterator++)
						{
							cBuffer[pos] = *iterator;
							pos++;
						}
						cBuffer[pos] = '\0';

						currentFace->push_back(stringToUInt(cBuffer));
						position++;

						if (position >= 9)
							curType = READTYPE_UNKNOWN;
					}
				}
			}

			copyData(tFaces, tVertices, tNormals, tTextures);

			return *this;
		}

		void objLoader::copyData(std::vector<vector<UINT>*>& tFaces, vector<D3DXVECTOR3*>& tPositions, vector<D3DXVECTOR3*>& tNormals, vector<D3DXVECTOR2*>& tTexCoords)
		{
			hash_set<falseIndex> RealIndexes;
			WORD nextIndex = 0;
			WORD faces;

			//Generate real indexes
			{
				UINT positionIndex, texCoordIndex, normalIndex;
				vector<vector<UINT>*>::iterator vEnd = tFaces.end();
				for (vector<vector<UINT>*>::iterator x = tFaces.begin(); x < vEnd; x++)//Go though the faces
				{
					vector<UINT>* u = *x;
					vector<UINT>::iterator e = u->end();
					for (vector<UINT>::iterator t = u->begin(); t < e;)
					{
						positionIndex = *t;
						t++;

						texCoordIndex = *t;
						t++;

						normalIndex = *t;
						t++;

						getRealIndex(positionIndex, texCoordIndex, normalIndex, RealIndexes, nextIndex);
					}
				}
			}

			//Create Mesh
			{
				faces = WORD(tFaces.at(0)->size() / 3) * WORD(tFaces.size()) / 3;
				D3DXCreateMesh(faces, nextIndex, D3DXMESH_MANAGED, vertexElement, _device, &_mesh);
			}

			//Copy Indices
			{
				WORD* vi = 0;
				_mesh->LockIndexBuffer(0, (LPVOID*)&vi);
				
				UINT positionIndex, texCoordIndex, normalIndex;
				WORD* vI = vi;
				WORD tIndex;

				vector<vector<UINT>*>::iterator vEnd = tFaces.end();
				for (vector<vector<UINT>*>::iterator x = tFaces.begin(); x < vEnd; x++)//Go though the faces
				{
					vector<UINT>* u = *x;
					vector<UINT>::iterator e = u->end();
					for (vector<UINT>::iterator t = u->begin(); t < e;)
					{
						positionIndex = *t;
						t++;

						texCoordIndex = *t;
						t++;

						normalIndex = *t;
						t++;

						tIndex = getRealIndex(positionIndex, texCoordIndex, normalIndex, RealIndexes, nextIndex);
						*vI = tIndex;
						vI++;
					}

					delete (*x);
					(*x) = 0;
				}

				_mesh->UnlockIndexBuffer();
			}

			//Copy positions, normals, and tex coords to proper order
			vector<D3DXVECTOR3*> nPositions(nextIndex);
			vector<D3DXVECTOR3*> nNormals(nextIndex);
			vector<D3DXVECTOR2*> nTexCoords(nextIndex);

			const size_t numPositions = tPositions.size();
			const size_t numTexCoords = tTexCoords.size();
			const size_t numNormals = tNormals.size();

			{
				hash_set<falseIndex>::iterator vEnd = RealIndexes.end();
				for (hash_set<falseIndex>::iterator x = RealIndexes.begin(); x != vEnd; x++)
				{
					const WORD cIndex = x->_index;
					if (x->_position != 0)
					{
						const UINT pIndex = x->_position - 1;
						if (pIndex < numPositions)
							nPositions.at(cIndex) = new D3DXVECTOR3(*tPositions.at(pIndex));
						else
							nPositions.at(cIndex) = 0;
					}

					if (x->_texcoord != 0)
					{
						const UINT tIndex = x->_texcoord - 1;
						if (tIndex < numTexCoords)
							nTexCoords.at(cIndex) = new D3DXVECTOR2(*tTexCoords.at(tIndex));
						else
							nTexCoords.at(cIndex) = 0;
					}

					if (x->_normal != 0)
					{
						const UINT nIndex = x->_normal - 1;
						if (nIndex < numNormals)
							nNormals.at(cIndex) = new D3DXVECTOR3(*tNormals.at(nIndex));
						else
							nNormals.at(cIndex) = 0;
					}
				}
			}
			//Clean up old positions, normals, and texture coords
			{
				vector<D3DXVECTOR3*>::iterator vEnd = tPositions.end();
				for (vector<D3DXVECTOR3*>::iterator x = tPositions.begin(); x != vEnd; x++)
				{
					delete (*x);
				}
			}
			{
				vector<D3DXVECTOR3*>::iterator vEnd = tNormals.end();
				for (vector<D3DXVECTOR3*>::iterator x = tNormals.begin(); x != vEnd; x++)
				{
					delete (*x);
				}
			}
			{
				vector<D3DXVECTOR2*>::iterator vEnd = tTexCoords.end();
				for (vector<D3DXVECTOR2*>::iterator x = tTexCoords.begin(); x != vEnd; x++)
				{
					delete (*x);
				}
			}

			//Copy positions, normals, and texcoords to buffer
			{
				vector<D3DXVECTOR3*>::iterator position = nPositions.begin();
				vector<D3DXVECTOR3*>::iterator normal = nNormals.begin();
				vector<D3DXVECTOR2*>::iterator texCoord = nTexCoords.begin();
				Vertex* vB = 0;
				_mesh->LockVertexBuffer(0, (LPVOID*)&vB);
				for (register WORD x = 0; x < nextIndex; x++)
				{
					*vB = Vertex((*position)->x, (*position)->y, (*position)->z, (*normal)->x, (*normal)->y, (*normal)->z, (*texCoord)->x, (*texCoord)->y);
					delete *position;
					delete *normal;
					delete *texCoord;
					position++;
					normal++;
					texCoord++;
					vB++;
				}
				_mesh->UnlockVertexBuffer();
			}

			_subsets = 1;
		}

		WORD objLoader::getRealIndex(const UINT PositionIndex, const UINT TexCoordIndex, const UINT NormalIndex, hash_set<falseIndex>& RealIndexes, WORD& nextIndex)
		{
			falseIndex TempIndex(PositionIndex, TexCoordIndex, NormalIndex, nextIndex);

			hash_set<falseIndex>::iterator iterator;

			iterator = RealIndexes.find(TempIndex);
			if (iterator != RealIndexes.end())
				return (iterator->_index);
			
			RealIndexes.insert(TempIndex);
			nextIndex++;
			return TempIndex._index;
		}
	}
}