#include "modelclass.h"


ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_model = 0;
}


ModelClass::ModelClass(const ModelClass& other)
{
}


ModelClass::~ModelClass()
{
}

bool ModelClass::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, char* modelFilename, std::vector<ID3D11ShaderResourceView*> textures) {
	Logger::Get().Log("Initializing model class with preloaded textures", __FILE__, __LINE__, Logger::LogLevel::Initialize);

	bool result;

	// Load in the model data.
	result = LoadModel(modelFilename);
	if (!result) {
		Logger::Get().Log("Failed to load model data", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	// Calculate the tangent and binormal vectors for the model.
	CalculateModelVectors();

	// Initialize the vertex and index buffers.
	result = InitializeBuffers(device);
	if (!result) {
		Logger::Get().Log("Failed to initialize buffers", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	m_Textures = textures;

	Logger::Get().Log("Model class initialized with preloaded textures", __FILE__, __LINE__, Logger::LogLevel::Initialize);

	return true;
}


void ModelClass::Shutdown()
{
	// Release the model textures.
	ReleaseTextures();

	// Shutdown the vertex and index buffers.
	ShutdownBuffers();

	// Release the model data.
	ReleaseModel();

	return;
}


void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers(deviceContext);

	return;
}


int ModelClass::GetIndexCount()
{
	return m_indexCount;
}

ID3D11ShaderResourceView* ModelClass::GetTexture(int index) const {
	if (index >= 0 && index < m_Textures.size()) {
		return m_Textures[index];
	}
	return nullptr; // Retourne nullptr si l'index est hors limites
}

bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
	Logger::Get().Log("Initializing buffers", __FILE__, __LINE__, Logger::LogLevel::Initialize);

	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	int i;

	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];

	// Create the index array.
	indices = new unsigned long[m_indexCount];

	// Load the vertex array and index array with data.
	for (i = 0; i < m_vertexCount; i++)
	{
		vertices[i].position = XMFLOAT3(m_model[i].x, m_model[i].y, m_model[i].z);
		vertices[i].texture = XMFLOAT2(m_model[i].tu, m_model[i].tv);
		vertices[i].normal = XMFLOAT3(m_model[i].nx, m_model[i].ny, m_model[i].nz);
		vertices[i].tangent = XMFLOAT3(m_model[i].tx, m_model[i].ty, m_model[i].tz);
		vertices[i].binormal = XMFLOAT3(m_model[i].bx, m_model[i].by, m_model[i].bz);

		indices[i] = i;
	}

	// Set up the description of the static vertex buffer.
	vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = 0;
	vertexBufferDesc.MiscFlags = 0;
	vertexBufferDesc.StructureByteStride = 0;

	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;

	// Now create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
	if (FAILED(result))
	{
		Logger::Get().Log("Failed to create vertex buffer", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	// Set up the description of the static index buffer.
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
	indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;
	indexBufferDesc.StructureByteStride = 0;

	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;

	// Create the index buffer.
	result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
	if (FAILED(result))
	{
		Logger::Get().Log("Failed to create index buffer", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	// Release the arrays now that the vertex and index buffers have been created and loaded.
	delete[] vertices;
	vertices = 0;

	delete[] indices;
	indices = 0;

	Logger::Get().Log("Buffers initialized", __FILE__, __LINE__, Logger::LogLevel::Initialize);

	return true;
}


void ModelClass::ShutdownBuffers()
{
	Logger::Get().Log("Shutting down buffers", __FILE__, __LINE__, Logger::LogLevel::Shutdown);
	// Release the index buffer.
	if (m_indexBuffer)
	{
		m_indexBuffer->Release();
		m_indexBuffer = 0;
	}

	// Release the vertex buffer.
	if (m_vertexBuffer)
	{
		m_vertexBuffer->Release();
		m_vertexBuffer = 0;
	}

	Logger::Get().Log("Buffers shut down", __FILE__, __LINE__, Logger::LogLevel::Shutdown);

	return;
}


void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
	unsigned int stride;
	unsigned int offset;


	// Set vertex buffer stride and offset.
	stride = sizeof(VertexType);
	offset = 0;

	// Set the vertex buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return;
}

void ModelClass::ReleaseTextures()
{
	Logger::Get().Log("Releasing textures", __FILE__, __LINE__);

	// Release the texture object array.
	for (auto& texture : m_Textures)
	{
		if (texture)
		{
			texture->Release();
			texture = nullptr;
		}
	}

	m_Textures.clear();

	Logger::Get().Log("Textures released", __FILE__, __LINE__);
}

bool ModelClass::LoadModel(char* filename)
{
	Logger::Get().Log("Loading model: "  + std::string(filename), __FILE__, __LINE__);

	std::string fileStr(filename);
	std::string extension = fileStr.substr(fileStr.find_last_of(".") + 1);

	if (extension == "obj")
	{
		// Load .obj file
		return LoadObjModel(filename);
	}
	else if (extension == "txt")
	{
		// Load .txt file
		return LoadTxtModel(filename);
	}
	else
	{
		Logger::Get().Log("Unsupported file format", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}
}

bool ModelClass::LoadObjModel(char* filename)
{
	Logger::Get().Log("Loading model", __FILE__, __LINE__);

	std::string line;
	std::ifstream fin(filename);

	// If it could not open the file then exit.
	if (!fin)
	{
		Logger::Get().Log("Failed to open model file", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	std::vector<XMFLOAT3> temp_positions;
	std::vector<XMFLOAT2> temp_texcoords;
	std::vector<XMFLOAT3> temp_normals;
	std::vector<ModelType> temp_model;

	// Read the file line by line.
	while (std::getline(fin, line))
	{
		std::istringstream iss(line);
		std::string type;
		iss >> type;

		if (type == "v")  // Vertex position
		{
			XMFLOAT3 pos;
			iss >> pos.x >> pos.y >> pos.z;
			temp_positions.push_back(pos);
		}
		else if (type == "vt")  // Texture coordinate
		{
			XMFLOAT2 tex;
			iss >> tex.x >> tex.y;
			temp_texcoords.push_back(tex);
		}
		else if (type == "vn")  // Vertex normal
		{
			XMFLOAT3 norm;
			iss >> norm.x >> norm.y >> norm.z;
			temp_normals.push_back(norm);
		}
		else if (type == "f")  // Face
		{
			int posIndex[3], texIndex[3], normIndex[3];
			char slash;  // To skip the slashes '/' in the .obj file format
			for (int i = 0; i < 3; i++)  // Each face in .obj format has 3 vertices
			{
				iss >> posIndex[i] >> slash >> texIndex[i] >> slash >> normIndex[i];

				// Handle negative indices
				if (posIndex[i] < 0) posIndex[i] += temp_positions.size() + 1;
				if (texIndex[i] < 0) texIndex[i] += temp_texcoords.size() + 1;
				if (normIndex[i] < 0) normIndex[i] += temp_normals.size() + 1;
			}

			// .obj indices start from 1, not 0
			for (int i = 0; i < 3; i++)
			{
				ModelType vertex;
				vertex.x = temp_positions[posIndex[i] - 1].x;
				vertex.y = temp_positions[posIndex[i] - 1].y;
				vertex.z = temp_positions[posIndex[i] - 1].z;
				vertex.tu = temp_texcoords[texIndex[i] - 1].x;
				vertex.tv = temp_texcoords[texIndex[i] - 1].y;
				vertex.nx = temp_normals[normIndex[i] - 1].x;
				vertex.ny = temp_normals[normIndex[i] - 1].y;
				vertex.nz = temp_normals[normIndex[i] - 1].z;
				temp_model.push_back(vertex);
			}
		}
	}

	m_vertexCount = temp_model.size();
	m_indexCount = temp_model.size();

	// Create the model using the vertex count that was read in.
	m_model = new ModelType[m_vertexCount];
	for (int i = 0; i < m_vertexCount; i++)
	{
		m_model[i] = temp_model[i];
	}

	fin.close();

	Logger::Get().Log("Model loaded", __FILE__, __LINE__);

	return true;
}


bool ModelClass::LoadTxtModel(char* filename)
{
	Logger::Get().Log("Loading model", __FILE__, __LINE__);

	ifstream fin;
	char input;
	int i;


	// Open the model file.
	fin.open(filename);

	// If it could not open the file then exit.
	if (fin.fail())
	{
		Logger::Get().Log("Failed to open model file", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	// Read up to the value of vertex count.
	fin.get(input);
	while (input != ':')
	{
		fin.get(input);
	}

	// Read in the vertex count.
	fin >> m_vertexCount;

	// Set the number of indices to be the same as the vertex count.
	m_indexCount = m_vertexCount;

	// Create the model using the vertex count that was read in.
	m_model = new ModelType[m_vertexCount];

	// Read up to the beginning of the data.
	fin.get(input);
	while (input != ':')
	{
		fin.get(input);
	}
	fin.get(input);
	fin.get(input);

	// Read in the vertex data.
	for (i = 0; i < m_vertexCount; i++)
	{
		fin >> m_model[i].x >> m_model[i].y >> m_model[i].z;
		fin >> m_model[i].tu >> m_model[i].tv;
		fin >> m_model[i].nx >> m_model[i].ny >> m_model[i].nz;
	}

	// Close the model file.
	fin.close();

	Logger::Get().Log("Model loaded "+ std::string(filename), __FILE__, __LINE__);

	return true;
}

void ModelClass::CalculateModelVectors()
{
	Logger::Get().Log("Calculating model vectors", __FILE__, __LINE__);

	int faceCount, i, index;
	TempVertexType vertex1, vertex2, vertex3;
	VectorType tangent, binormal;


	// Calculate the number of faces in the model.
	faceCount = m_vertexCount / 3;

	// Initialize the index to the model data.
	index = 0;

	// Go through all the faces and calculate the the tangent and binormal vectors.
	for (i = 0; i < faceCount; i++)
	{
		// Get the three vertices for this face from the model.
		vertex1.x = m_model[index].x;
		vertex1.y = m_model[index].y;
		vertex1.z = m_model[index].z;
		vertex1.tu = m_model[index].tu;
		vertex1.tv = m_model[index].tv;
		index++;

		vertex2.x = m_model[index].x;
		vertex2.y = m_model[index].y;
		vertex2.z = m_model[index].z;
		vertex2.tu = m_model[index].tu;
		vertex2.tv = m_model[index].tv;
		index++;

		vertex3.x = m_model[index].x;
		vertex3.y = m_model[index].y;
		vertex3.z = m_model[index].z;
		vertex3.tu = m_model[index].tu;
		vertex3.tv = m_model[index].tv;
		index++;

		// Calculate the tangent and binormal of that face.
		CalculateTangentBinormal(vertex1, vertex2, vertex3, tangent, binormal);

		// Store the tangent and binormal for this face back in the model structure.
		m_model[index - 1].tx = tangent.x;
		m_model[index - 1].ty = tangent.y;
		m_model[index - 1].tz = tangent.z;
		m_model[index - 1].bx = binormal.x;
		m_model[index - 1].by = binormal.y;
		m_model[index - 1].bz = binormal.z;

		m_model[index - 2].tx = tangent.x;
		m_model[index - 2].ty = tangent.y;
		m_model[index - 2].tz = tangent.z;
		m_model[index - 2].bx = binormal.x;
		m_model[index - 2].by = binormal.y;
		m_model[index - 2].bz = binormal.z;

		m_model[index - 3].tx = tangent.x;
		m_model[index - 3].ty = tangent.y;
		m_model[index - 3].tz = tangent.z;
		m_model[index - 3].bx = binormal.x;
		m_model[index - 3].by = binormal.y;
		m_model[index - 3].bz = binormal.z;
	}

	Logger::Get().Log("Model vectors calculated", __FILE__, __LINE__);

	return;
}

void ModelClass::CalculateTangentBinormal(TempVertexType vertex1, TempVertexType vertex2, TempVertexType vertex3, VectorType& tangent, VectorType& binormal)
{

	float vector1[3], vector2[3];
	float tuVector[2], tvVector[2];
	float den;
	float length;


	// Calculate the two vectors for this face.
	vector1[0] = vertex2.x - vertex1.x;
	vector1[1] = vertex2.y - vertex1.y;
	vector1[2] = vertex2.z - vertex1.z;

	vector2[0] = vertex3.x - vertex1.x;
	vector2[1] = vertex3.y - vertex1.y;
	vector2[2] = vertex3.z - vertex1.z;

	// Calculate the tu and tv texture space vectors.
	tuVector[0] = vertex2.tu - vertex1.tu;
	tvVector[0] = vertex2.tv - vertex1.tv;

	tuVector[1] = vertex3.tu - vertex1.tu;
	tvVector[1] = vertex3.tv - vertex1.tv;

	// Calculate the denominator of the tangent/binormal equation.
	den = 1.0f / (tuVector[0] * tvVector[1] - tuVector[1] * tvVector[0]);

	// Calculate the cross products and multiply by the coefficient to get the tangent and binormal.
	tangent.x = (tvVector[1] * vector1[0] - tvVector[0] * vector2[0]) * den;
	tangent.y = (tvVector[1] * vector1[1] - tvVector[0] * vector2[1]) * den;
	tangent.z = (tvVector[1] * vector1[2] - tvVector[0] * vector2[2]) * den;

	binormal.x = (tuVector[0] * vector2[0] - tuVector[1] * vector1[0]) * den;
	binormal.y = (tuVector[0] * vector2[1] - tuVector[1] * vector1[1]) * den;
	binormal.z = (tuVector[0] * vector2[2] - tuVector[1] * vector1[2]) * den;

	// Calculate the length of this normal.
	length = sqrt((tangent.x * tangent.x) + (tangent.y * tangent.y) + (tangent.z * tangent.z));

	// Normalize the normal and then store it
	tangent.x = tangent.x / length;
	tangent.y = tangent.y / length;
	tangent.z = tangent.z / length;

	// Calculate the length of this normal.
	length = sqrt((binormal.x * binormal.x) + (binormal.y * binormal.y) + (binormal.z * binormal.z));

	// Normalize the normal and then store it
	binormal.x = binormal.x / length;
	binormal.y = binormal.y / length;
	binormal.z = binormal.z / length;

	return;
}

void ModelClass::ReleaseModel()
{
	Logger::Get().Log("Releasing model", __FILE__, __LINE__);

	if (m_model)
	{
		delete[] m_model;
		m_model = 0;
	}

	Logger::Get().Log("Model released", __FILE__, __LINE__);

	return;
}

bool ModelClass::ChangeTexture(ID3D11Device* device, ID3D11DeviceContext* deviceContext, std::wstring filename, int index)
{
	Logger::Get().Log("Changing texture", __FILE__, __LINE__, Logger::LogLevel::Initialize);

	HRESULT result;
	ID3D11ShaderResourceView* newTexture = nullptr;

	// Load the new texture using WICTextureLoader.
	result = DirectX::CreateWICTextureFromFile(device, deviceContext, filename.c_str(), nullptr, &newTexture);
	if (FAILED(result))
	{
		Logger::Get().Log("Failed to load new texture", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

	// Release the old texture if it exists.
	if (index >= 0 && index < m_Textures.size() && m_Textures[index])
	{
		m_Textures[index]->Release();
		m_Textures[index] = nullptr;
	}

	// Assign the new texture to the specified index.
	if (index >= 0 && index < m_Textures.size())
	{
		m_Textures[index] = newTexture;
	}
	else
	{
		// If the index is out of range, log an error and release the new texture.
		Logger::Get().Log("Texture index out of range", __FILE__, __LINE__, Logger::LogLevel::Error);
		newTexture->Release();
		return false;
	}

	Logger::Get().Log("Texture changed successfully", __FILE__, __LINE__, Logger::LogLevel::Initialize);
	return true;
}