#include <opengl-playground/model.hpp>
#include <opengl-playground/material.hpp>
#include <iostream>

Model::Model(const std::string &path, const glm::vec4 &static_color, std::shared_ptr<OpenGlShaderProgram> shader)
	: OpenGlGraphics(shader)
{
	this->model_path = path;
	this->static_color = static_color;
}

void Model::render()
{
	for (unsigned int i = 0; i < meshes.size(); i++) {
		meshes[i].render(this->model_matrix, this->normal_matrix);
	}
}

void Model::realize()
{
	Assimp::Importer importer;

	const aiScene *scene = importer.ReadFile(this->model_path, aiProcess_Triangulate | aiProcess_FlipUVs);

	if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
		std::cout << "Error: Assimp: " << importer.GetErrorString() << std::endl;
		return;
	}

	this->directory = this->model_path.substr(0, this->model_path.find_last_of('/'));
	this->processNode(scene->mRootNode, scene);

	importer.FreeScene();
}

void Model::processNode(aiNode *node, const aiScene *scene)
{
	for(unsigned int i = 0; i < node->mNumMeshes; i++)
	{
		aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
		this->meshes.push_back(this->processMesh(mesh, scene));
	}

	for(unsigned int i = 0; i < node->mNumChildren; i++)
	{
		this->processNode(node->mChildren[i], scene);
	}
}


std::vector<std::shared_ptr<OpenGlTexture>> Model::loadMaterialTextures(aiMaterial *mat, aiTextureType type)
{
	std::vector<std::shared_ptr<OpenGlTexture>> tex_vector;

	for (unsigned int i = 0; i < mat->GetTextureCount(type); i++) {
		aiString str;
		mat->GetTexture(type, i, &str);
		bool already_loaded = false;
		for (unsigned int j = 0; j < this->allocated_textures.size(); j++) {
			if (std::strcmp(this->allocated_textures[j]->getPath().c_str(), str.C_Str()) == 0) {
				/* texture already loaded */
				tex_vector.push_back(this->allocated_textures[j]);
				already_loaded = true;
				break;
			}
		}

		if (!already_loaded) {
			auto new_texture = std::make_shared<OpenGlTexture>(str.C_Str());
			new_texture->loadFromImagePath(this->directory);
			tex_vector.push_back(new_texture);
			this->allocated_textures.push_back(new_texture);
		}
	}

	return tex_vector;
}

Mesh Model::processMesh(aiMesh *mesh, const aiScene *scene)
{
	std::vector<Vertex> vertices;
	std::vector<unsigned int> indices;
	Material mesh_material;

	for(unsigned int i = 0; i < mesh->mNumVertices; i++)
	{
		Vertex vertex;
		glm::vec3 vector;
		vector.x = mesh->mVertices[i].x;
		vector.y = mesh->mVertices[i].y;
		vector.z = mesh->mVertices[i].z;
		vertex.Position = vector;
		vector.x = mesh->mNormals[i].x;
		vector.y = mesh->mNormals[i].y;
		vector.z = mesh->mNormals[i].z;
		vertex.Normal = vector;

		if(mesh->mTextureCoords[0]) {
			glm::vec2 vec;
			vec.x = mesh->mTextureCoords[0][i].x;
			vec.y = mesh->mTextureCoords[0][i].y;
			vertex.TextureCoords = vec;
		} else {
			vertex.TextureCoords = glm::vec2(0.0f, 0.0f);
		}
		vertices.push_back(vertex);
	}
	// process indices
	for(unsigned int i = 0; i < mesh->mNumFaces; i++)
	{
		aiFace face = mesh->mFaces[i];
		for(unsigned int j = 0; j < face.mNumIndices; j++)
			indices.push_back(face.mIndices[j]);
	}

	if(mesh->mMaterialIndex >= 0)
	{
		aiColor3D col;

		aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
		auto diffuseTextures = loadMaterialTextures(material, aiTextureType_DIFFUSE);
		auto specularTextures = loadMaterialTextures(material, aiTextureType_SPECULAR);
		auto ambientTextures = loadMaterialTextures(material, aiTextureType_AMBIENT);

		mesh_material.pushDiffuseTexture(diffuseTextures);
		mesh_material.pushSpecularTexture(specularTextures);
		mesh_material.pushAmbientTexture(ambientTextures);

		if (!material->Get(AI_MATKEY_COLOR_AMBIENT, col))
			mesh_material.setAmbientColor(glm::vec3(col.r, col.g, col.b));
		if (!material->Get(AI_MATKEY_COLOR_SPECULAR, col))
			mesh_material.setSpecularColor(glm::vec3(col.r, col.g, col.b));
		if (!material->Get(AI_MATKEY_COLOR_DIFFUSE, col))
			mesh_material.setDiffuseColor(glm::vec3(col.r, col.g, col.b));

		material->Get(AI_MATKEY_SHININESS, mesh_material.shininess);
		material->Get(AI_MATKEY_SHININESS_STRENGTH, mesh_material.shininess_strength);
		std::cout << "Material: " << mesh_material << std::endl;
	}


	Mesh my_mesh = Mesh(vertices, indices, mesh_material, this->shaderprog);
	my_mesh.realize();
	return my_mesh;
}
