/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   ObjParser.cpp                                      :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: tomoron <tomoron@student.42angouleme.fr>   +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2025/01/16 15:00:33 by tomoron           #+#    #+#             */
/*   Updated: 2025/01/18 18:57:31 by tomoron          ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

#include "RT.hpp"

ObjParser::ObjParser(std::string &filename)
{
	_mat = 0;
	_file.open(filename);
	if(!_file.is_open())
		throw std::runtime_error("OBJ : could not open object file");
}

ObjParser::~ObjParser()
{
	_file.close();
}

glm::vec3	ObjParser::getVertex(std::stringstream &line)
{
	glm::vec3 res;

	if(!(line >> res.x >> res.y >> res.z))
		throw std::runtime_error("syntax error in obj file while parsing vertex");
	return(res);
}

glm::vec2	ObjParser::getUV(std::stringstream &line)
{
	glm::vec2 res;

	if(!(line >> res.x) || (!(line >> res.y) && !line.eof()))
		throw std::runtime_error("syntax error in obj file while parsing texture vertex");
	return(res);
}

long int	ObjParser::checkVertexIndex(int index, size_t size)
{
	if((size_t)index > size || index == 0 || (index < 0 && (size_t)(-index) > size))
		throw std::runtime_error("obj file error, invalid vertex index");	
	if(index < 0)
		index = size - index;
	return(index - 1);
}

int ObjParser::pointInTriangle(glm::vec3 pts[3], std::vector<glm::vec3> vertices, size_t cur)
{
	glm::vec3 v0, v1, v2;
	float d00, d01, d11, d20, d21;
	float den;
	float u, v;

	for(size_t i = 0; i < vertices.size(); i++)
	{
		if(i == ((cur - 1) % vertices.size())  || i == cur || i == ((cur + 1) % vertices.size()))
				continue;
		v0 = pts[2] - pts[0];
		v1 = pts[1] - pts[0];
		v2 = vertices[i] - pts[0];	
		
		d00 = glm::dot(v0, v0);
		d01 = glm::dot(v0, v1);
		d11 = glm::dot(v1, v1);
		d20 = glm::dot(v2, v0);
		d21 = glm::dot(v2, v1);
		den = glm::dot(d00, d11) - glm::dot(d01, d01);

		u = (glm::dot(d11, d20) - glm::dot(d01, d21)) / den;
		v = (glm::dot(d00, d21) - glm::dot(d01, d20)) / den;
		if(u >= 0 && v >= 0 && (u + v) <= 1)
			return(1);
	}
	return(0);
}

bool ObjParser::addTriangleFromPolygon(std::vector<glm::vec3> &vertices, Scene &scene, int inv)
{
	glm::vec3 v1, v2 ,v3;
	float dot;

	for (size_t i = 0; i < vertices.size(); i++)
	{
		if(!i)
			v1 = vertices.back();
		else
			v1 = vertices[i - 1];
		v2 = vertices[i];
		v3 = vertices[(i + 1) % vertices.size()];

		if (inv)
			dot = glm::cross(v2 - v1, v2 - v3).z;
		else
			dot = glm::cross(v2 - v3, v2 - v1).z;
		if(dot <= 0)
			continue;
		glm::vec3 triangleVertices[3] = {v1, v2, v3};
		if(pointInTriangle(triangleVertices, vertices, i))
			continue;
		vertices.erase(vertices.begin() + i);
		addTriangle(v1, v2 ,v3 , scene);
		return(1);
	}
	return(0);
}

std::vector<std::string> ObjParser::objSplit(std::string str, std::string delim)
{
	
	std::vector<std::string> res;
	
	while(str.find(delim) != std::string::npos)
	{
		res.push_back(str.substr(0, str.find(delim)));
		str.erase(0, str.find(delim) + 1);
	}
	res.push_back(str);
	return(res);
}

void ObjParser::getFaceVertices(std::vector<glm::vec3> &faceVertices, std::stringstream &line)
{
	std::string el;
	std::vector<std::string> sp;

	while(line >> el)
	{
		sp = objSplit(el, "/");
		if(sp.size() > 3)
			std::runtime_error("OBJ : too many values in an element of a face");
		if(sp.size() == 0)
			std::runtime_error("OBJ : wtf ?");
		faceVertices.push_back(_vertices[checkVertexIndex(std::stoi(sp[0]), _vertices.size())]);
	}
}

void ObjParser::addFace(std::stringstream &line, Scene &scene)
{
	std::vector<glm::vec3> faceVertices;

	getFaceVertices(faceVertices, line);
	if(!line.eof())
		throw std::runtime_error("OBJ : an error occured while paring face");
	if(faceVertices.size() < 3)
		throw std::runtime_error("OBJ : face does not have enough vertices");

	while(faceVertices.size() > 3)
		if (!addTriangleFromPolygon(faceVertices, scene, 0))
			addTriangleFromPolygon(faceVertices, scene, 1);

	if(!line.eof())
		throw std::runtime_error("OBJ: an error occured while parsing face");
	addTriangle(faceVertices[0], faceVertices[1], faceVertices[2], scene);
}

void	ObjParser::addTriangle(glm::vec3 v1, glm::vec3 v2, glm::vec3 v3, Scene &scene)
{
	scene.addObject(new Triangle(v1, v2, v3, _mat));
}

void	ObjParser::parseMtl(std::stringstream &input_line, Scene &scene)
{	
	std::string filename;
	std::ifstream file;
	std::string matName;
	std::string identifier;
	std::string line;
	Material *mat;

	input_line >> filename;
	file.open(filename);
	mat = 0;
	if(!file.is_open())
		throw std::runtime_error("OBJ : could not open material file");
	while(getline(file, line))
	{
		if (line.empty() || line[0] == '#')
			continue;
		std::stringstream lineStream(line);
		lineStream >> identifier;
		if(identifier == "newmtl")
		{
			if(mat)
			{
				scene.addMaterial(mat);
				_matNames[matName] = scene.getMaterialData().size() - 1;
			}
			lineStream >> matName;
			if(matName.empty())
				throw std::runtime_error("OBJ: syntax error in material file, missing material name");
			mat = new Material;
			memset(mat, 0, sizeof(Material));
			mat->metallic = 1.0f;
			continue;
		}
		if(!mat)
			throw std::runtime_error("OBJ: error in material file, material name not defined");
		if(identifier == "Kd")
		{
			if(!(lineStream >> mat->color.x >> mat->color.y >> mat->color.z))
				throw std::runtime_error("OBJ: syntax error while getting material color");
		}
		else if(identifier == "Ns")
		{
			if(!(lineStream >> mat->roughness) || mat->roughness > 1000 || mat->roughness < 0)
				throw std::runtime_error("OBJ: syntax error while getting material softness");
			mat->roughness /= 1000;
		}
		else if(identifier == "Ke")
		{
			float x, y, z;
			if(!(lineStream >> x >> y >> z))
				throw std::runtime_error("OBJ: syntax error while getting material emission");
			mat->emission = (x + y + z) / 3;
		}
		else if(identifier == "Ni")
		{
			if(!(lineStream >> mat->refraction))
				throw std::runtime_error("OBJ: syntax error while getting material refraction");
		}
		else
			std::cerr << "unsupported material setting : " << identifier << std::endl;
	}
	if(mat)
	{
		scene.addMaterial(mat);
		_matNames[matName] = scene.getMaterialData().size() - 1;
	}
	file.close();
}

void	ObjParser::parse(Scene &scene)
{
	std::string				line;
	std::string				identifier;

	while (getline(_file, line))
	{
		try{
			if(line[0] == '#' || line.empty())
				continue;
			std::stringstream		lineStream(line);
			identifier = "";
			lineStream >> identifier;
			if(identifier == "v")
				_vertices.push_back(getVertex(lineStream));
			else if (identifier == "vt")
				_textureVertices.push_back(getUV(lineStream));
			else if (identifier == "f")
				addFace(lineStream, scene);
			else if (identifier == "mtllib")
				parseMtl(lineStream, scene);
			else if (identifier == "usemtl")
			{
				lineStream >> identifier;
				if(_matNames.find(identifier) == _matNames.end())
					throw std::runtime_error("OBJ: invalid material name");
				_mat = _matNames[identifier];
			}
		} catch (std::exception &e)
		{
			std::cerr << line << std::endl;
			throw;
		}
	}
}

