/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   SceneParser.cpp                                    :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: TheRed <TheRed@students.42.fr>             +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2024/12/26 21:43:51 by TheRed            #+#    #+#             */
/*   Updated: 2024/12/26 21:43:51 by TheRed           ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

#include "SceneParser.hpp"

SceneParser::SceneParser(Scene *scene) : _scene(scene)
{
	object_parsers["sp"] = [](std::stringstream &ss) -> Object *
	{
		try { return (new Sphere(ss)); }
		catch (const std::exception &e) { throw; }
	};

	object_parsers["pl"] = [](std::stringstream &ss) -> Object *
	{
		try { return (new Plane(ss)); }
		catch (const std::exception &e) { throw; }
	};

	object_parsers["qu"] = [](std::stringstream &ss) -> Object *
	{
		try { return (new Quad(ss)); }
		catch (const std::exception &e) { throw; }
	};

	object_parsers["tr"] = [](std::stringstream &ss) -> Object *
	{
		try { return (new Triangle(ss)); }
		catch (const std::exception &e) { throw; }
	};

	object_parsers["cu"] = [](std::stringstream &ss) -> Object *
	{
		try { return (new Cube(ss)); }
		catch (const std::exception &e) { throw; }
	};
	
	object_parsers["po"] = [](std::stringstream &ss) -> Object *
	{
		try { return (new Portal(ss)); }
		catch (const std::exception &e) { throw; }
	};

	object_parsers["cy"] = [](std::stringstream &ss) -> Object *
	{
		try { return (new Cylinder(ss)); }
		catch (const std::exception &e) { throw; }
	};
}

void	SceneParser::parseMaterial(std::stringstream &line)
{
	float		r,g,b;
	float		emission;
	float		rough_refrac;
	float		metallic;
	std::string	type;

	Material	*mat;

	if (!(line >> r >> g >> b >> emission >> rough_refrac >> metallic))
		throw std::runtime_error("Material: Missing material properties");

	if (!(line >> type))
		type = "LAM";
	
	mat = new Material;

	mat->color = glm::vec3(r / 255.0f, g / 255.0f, b / 255.0f);
	mat->emission = emission;
	mat->roughness = rough_refrac;
	mat->metallic = metallic;
	mat->refraction = rough_refrac;
	
	mat->type = 0;
	if (type == "LAM")
		mat->type = 0;
	else if (type == "DIE")
		mat->type = 1;

	_scene->addMaterial(mat);
}

void	SceneParser::parseCamera(std::stringstream &line)
{
	float	x,y,z;
	float	yaw, pitch;
	float	aperture, focus, fov;
	int		bounce;
	
	if (!(line >> x >> y >> z))
		throw std::runtime_error("Camera: Missing camera properties");

	if (!(line >> yaw >> pitch))
	{
		yaw = 0;
		pitch = -90;
	}

	if (!(line >> aperture >> focus >> fov))
	{
		aperture = 0.0;
		focus = 1.0;
		fov = 90.0f;
	}

	if (!(line >> bounce))
		bounce = 5;

	_scene->getCamera()->setPosition(glm::vec3(x, y, z));
	_scene->getCamera()->setDirection(yaw, pitch);
	_scene->getCamera()->updateCameraVectors();
	
	_scene->getCamera()->setDOV(aperture, focus);
	_scene->getCamera()->setFov(fov);
	
	_scene->getCamera()->setBounce(bounce);

}

bool		SceneParser::parseLine(const std::string &line)
{
	if (line.empty() || line[0] == '#')
		return (true);
	
	std::stringstream	ss(line);
	std::string			identifier;
	
	ss >> identifier;

	try
	{
		auto it = object_parsers.find(identifier);
		if (it != object_parsers.end())
		{
			Object *obj = it->second(ss);
			(void) _scene->getMaterial(obj->getMaterialIndex()); //verify material
			
			if (obj->getType() == Object::Type::PORTAL)
				_scene->addObject(static_cast<Portal *>(obj)->createSupportQuad());
			
			_scene->addObject(obj);
		}

		if (identifier == "MAT")
			this->parseMaterial(ss);
		else if (identifier == "CAM")
			this->parseCamera(ss);
	}
	catch (const std::exception& e)
	{
		std::cerr << e.what() << std::endl;
		return (false);
	}

	return (true);
}
