#include "model_class.h"


model_class::model_class()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_model = 0;
	m_vertexCount = 0;
	m_indexCount = 0;
	m_Textures.diffuse.clear();
	m_Textures.normal.clear();
	m_Textures.specular.clear();
	m_Textures.alpha.clear();
	m_Textures.diffusePaths.clear();
	m_Textures.normalPaths.clear();
	m_Textures.specularPaths.clear();
	m_Textures.alphaPaths.clear();
}

model_class::~model_class()
{

	// Destructor
	Shutdown();
}

bool model_class::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, char* modelFilename, const TextureContainer& textures) {

	bool result = Initialize(device, deviceContext, modelFilename);
	if (!result) {
		R_FALSE
	}

	m_Textures = textures; // Copie de la structure de textures

	R_TRUE
}

bool model_class::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, char* modelFilename) {

	bool result;

	// Load in the model data.
	result = LoadModel(modelFilename);
	if (!result) {
		LOG_ERROR("Failed to load model data");
		R_FALSE
	}

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

	// Initialize the vertex and index buffers.
	result = InitializeBuffers(device);
	if (!result) {
		LOG_ERROR("Failed to initialize buffers");
		R_FALSE
	}
	
	R_TRUE
}

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

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

	// Release the model data.
	ReleaseModel();

	return;
}


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

	return;
}


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

ID3D11ShaderResourceView* model_class::GetTexture(TextureType type, int index) const {
	return m_Textures.GetTexture(type, index);
}

bool model_class::InitializeBuffers(ID3D11Device* device)
{

	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))
	{
		LOG_ERROR("Failed to create vertex buffer");
		R_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))
	{
		LOG_ERROR("Failed to create index buffer");
		R_FALSE
	}

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

	delete[] indices;
	indices = 0;
	R_TRUE
}


void model_class::ShutdownBuffers()
{
	if (m_indexBuffer)    { m_indexBuffer->Release();    m_indexBuffer    = nullptr; }
	if (m_vertexBuffer)   { m_vertexBuffer->Release();   m_vertexBuffer   = nullptr; }
}

void model_class::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 model_class::ReleaseTextures()
{
    
	// Utilise la méthode ReleaseAll de TextureContainer
	m_Textures.ReleaseAll();
}

bool model_class::LoadModel(char* filename)
{

	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
	{
		LOG_ERROR("Unsupported file format");
		R_FALSE
	}
}

bool model_class::LoadObjModel(char* filename)
{
    // Lecture optimisée du fichier en mode binaire
    std::ifstream fin(filename, std::ios::in | std::ios::binary);
    if (!fin)
    {
        LOG_ERROR("Échec d'ouverture du fichier modèle");
        R_FALSE
    }

    // Lecture du fichier entier d'un coup (évite la lecture ligne par ligne)
    fin.seekg(0, std::ios::end);
    const size_t fileSize = fin.tellg();
    fin.seekg(0, std::ios::beg);
    
    std::string fileContent;
    fileContent.resize(fileSize);
    fin.read(&fileContent[0], fileSize);
    fin.close();

    // Pré-allocation des vecteurs (évite les réallocations)
    const size_t estimatedVertices = fileSize / 150;
    std::vector<XMFLOAT3> temp_positions;
    std::vector<XMFLOAT2> temp_texcoords;
    std::vector<XMFLOAT3> temp_normals;
    std::vector<ModelType> temp_model;

    temp_positions.reserve(estimatedVertices);
    temp_texcoords.reserve(estimatedVertices);
    temp_normals.reserve(estimatedVertices);
    temp_model.reserve(estimatedVertices * 3);

    // Analyse du contenu
    std::istringstream iss(fileContent);
    std::string line;

    while (std::getline(iss, line))
    {
        if (line.empty() || line[0] == '#') continue;

        // Analyse plus rapide basée sur le premier caractère
        if (line[0] == 'v')
        {
            if (line[1] == ' ') // Position de sommet
            {
                XMFLOAT3 pos;
                sscanf_s(line.c_str() + 2, "%f %f %f", &pos.x, &pos.y, &pos.z);
            	pos.z = -pos.z; // Inversion de l'axe Z pour DirectX
                temp_positions.push_back(pos);
            }
            else if (line[1] == 't') // Coordonnées de texture
            {
                XMFLOAT2 tex;
                sscanf_s(line.c_str() + 3, "%f %f", &tex.x, &tex.y);
                temp_texcoords.push_back(tex);
            }
            else if (line[1] == 'n') // Normales
            {
                XMFLOAT3 norm;
                sscanf_s(line.c_str() + 3, "%f %f %f", &norm.x, &norm.y, &norm.z);
                temp_normals.push_back(norm);
            }
        }
        else if (line[0] == 'f')
        {
            int posIndex[3], texIndex[3], normIndex[3];
            const char* linePtr = line.c_str() + 2; // Sauter "f "
            
            for (int i = 0; i < 3; i++)
            {
                // Analyse rapide du format v/vt/vn
                sscanf_s(linePtr, "%d/%d/%d", &posIndex[i], &texIndex[i], &normIndex[i]);
                
                // Avancer au prochain ensemble d'indices
                while (*linePtr && *linePtr != ' ') linePtr++;
                while (*linePtr == ' ') linePtr++;

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

        	for (int i = 2; i >= 0; --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);
        	}
        }
    }

    // Allocation et copie efficace du modèle final
    m_vertexCount = size_t_to_int(temp_model.size());
    m_indexCount = size_t_to_int(temp_model.size());
    m_model = new ModelType[m_vertexCount];
    std::memcpy(m_model, temp_model.data(), m_vertexCount * sizeof(ModelType));

    R_TRUE
}

bool model_class::LoadTxtModel(char* filename)
{
	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())
	{
		LOG_ERROR("Failed to open model file");
		R_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();
	
	R_TRUE
}

void model_class::CalculateModelVectors()
{
	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;
	}
}

void model_class::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;
}

void model_class::ReleaseModel()
{

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

bool model_class::PreloadTextures(ID3D11Device* device, ID3D11DeviceContext* deviceContext, TextureContainer& textureContainer)
{
    HRESULT hResult;
    
    // Charger les textures diffuses
    for (const auto& texturePath : textureContainer.diffusePaths)
    {
        ID3D11ShaderResourceView* texture = nullptr;
        hResult = DirectX::CreateWICTextureFromFile(device, deviceContext, texturePath.c_str(), nullptr, &texture);
        if (FAILED(hResult))
        {
            LOG_ERROR("Échec du chargement de la texture diffuse: " + std::string(texturePath.begin(), texturePath.end()));
            R_FALSE
        }
        textureContainer.diffuse.push_back(texture);
    }
    
    // Charger les textures normales
    for (const auto& texturePath : textureContainer.normalPaths)
    {
        ID3D11ShaderResourceView* texture = nullptr;
        hResult = DirectX::CreateWICTextureFromFile(device, deviceContext, texturePath.c_str(), nullptr, &texture);
        if (FAILED(hResult))
        {
            LOG_ERROR("Échec du chargement de la texture normale: " + std::string(texturePath.begin(), texturePath.end()));
            R_FALSE
        }
        textureContainer.normal.push_back(texture);
    }
    
    // Charger les textures spéculaires
    for (const auto& texturePath : textureContainer.specularPaths)
    {
        ID3D11ShaderResourceView* texture = nullptr;
        hResult = DirectX::CreateWICTextureFromFile(device, deviceContext, texturePath.c_str(), nullptr, &texture);
        if (FAILED(hResult))
        {
            LOG_ERROR("Échec du chargement de la texture spéculaire: " + std::string(texturePath.begin(), texturePath.end()));
            R_FALSE
        }
        textureContainer.specular.push_back(texture);
    }
    
    // Charger les textures alpha
    for (const auto& texturePath : textureContainer.alphaPaths)
    {
        ID3D11ShaderResourceView* texture = nullptr;
        hResult = DirectX::CreateWICTextureFromFile(device, deviceContext, texturePath.c_str(), nullptr, &texture);
        if (FAILED(hResult))
        {
            LOG_ERROR("Échec du chargement de la texture alpha: " + std::string(texturePath.begin(), texturePath.end()));
            R_FALSE
        }
        textureContainer.alpha.push_back(texture);
    }
    
    R_TRUE
}

bool model_class::ChangeTexture(ID3D11Device* device, ID3D11DeviceContext* deviceContext, std::wstring filename, TextureType type, int index) {
	LOG_INIT("Changing texture");

	HRESULT result;
	ID3D11ShaderResourceView* newTexture = nullptr;

	// Charger la nouvelle texture
	result = DirectX::CreateWICTextureFromFile(device, deviceContext, filename.c_str(), nullptr, &newTexture);
	if (FAILED(result)) {
		LOG_ERROR("Failed to load texture: " + std::string(filename.begin(), filename.end()));
		R_FALSE
	}

	// Récupérer le vecteur correspondant au type de texture
	auto& textureVector = m_Textures.Get(type);

	// Si l'index est hors limites, redimensionner le vecteur
	if (index >= textureVector.size()) {
		textureVector.resize(index + 1, nullptr);
	}

	// Libérer l'ancienne texture si elle existe
	if (textureVector[index]) {
		textureVector[index]->Release();
	}

	// Assigner la nouvelle texture
	textureVector[index] = newTexture;

	// Mettre à jour le chemin dans le conteneur approprié selon le type
	switch (type) {
	case TextureType::Diffuse:
		if (index >= m_Textures.diffusePaths.size()) {
			m_Textures.diffusePaths.resize(index + 1, L"");
		}
		m_Textures.diffusePaths[index] = filename;
		break;
	case TextureType::Normal:
		if (index >= m_Textures.normalPaths.size()) {
			m_Textures.normalPaths.resize(index + 1, L"");
		}
		m_Textures.normalPaths[index] = filename;
		break;
	case TextureType::Specular:
		if (index >= m_Textures.specularPaths.size()) {
			m_Textures.specularPaths.resize(index + 1, L"");
		}
		m_Textures.specularPaths[index] = filename;
		break;
	case TextureType::Alpha:
		if (index >= m_Textures.alphaPaths.size()) {
			m_Textures.alphaPaths.resize(index + 1, L"");
		}
		m_Textures.alphaPaths[index] = filename;
		break;
	}

	LOG_INIT("Texture changed successfully");
	R_TRUE
}

bool model_class::AddTexture(ID3D11Device* device, ID3D11DeviceContext* deviceContext, std::wstring filename, TextureType type) {
	LOG_INIT("Adding texture");

	HRESULT result;
	ID3D11ShaderResourceView* newTexture = nullptr;

	// Charger la nouvelle texture
	result = DirectX::CreateWICTextureFromFile(device, deviceContext, filename.c_str(), nullptr, &newTexture);
	if (FAILED(result)) {
		LOG_ERROR("Failed to load texture: " + std::string(filename.begin(), filename.end()));
		R_FALSE
	}

	// Ajouter la texture au vecteur approprié selon le type
	auto& textureVector = m_Textures.Get(type);
	textureVector.push_back(newTexture);

	// Ajouter le chemin de la texture au vecteur approprié selon le type
	switch (type) {
	case TextureType::Diffuse:
		m_Textures.diffusePaths.push_back(filename);
		break;
	case TextureType::Normal:
		m_Textures.normalPaths.push_back(filename);
		break;
	case TextureType::Specular:
		m_Textures.specularPaths.push_back(filename);
		break;
	case TextureType::Alpha:
		m_Textures.alphaPaths.push_back(filename);
		break;
	}

	LOG_INIT("Texture added successfully");
	R_TRUE
}

bool model_class::AddTexture(ID3D11ShaderResourceView* texture, TextureType type) {
	if (!texture) {
		LOG_ERROR("Cannot add null texture");
		R_FALSE
	}

	// Ajouter la texture au vecteur approprié
	auto& textureVector = m_Textures.Get(type);
	textureVector.push_back(texture);

	// Ajouter un chemin vide ou générique pour maintenir la synchronisation
	switch (type) {
	case TextureType::Diffuse:
		m_Textures.diffusePaths.push_back(L"[texture préchargée]");
		break;
	case TextureType::Normal:
		m_Textures.normalPaths.push_back(L"[texture préchargée]");
		break;
	case TextureType::Specular:
		m_Textures.specularPaths.push_back(L"[texture préchargée]");
		break;
	case TextureType::Alpha:
		m_Textures.alphaPaths.push_back(L"[texture préchargée]");
		break;
	}
	R_TRUE
}

