#include <psemek/gfx/obj_parser.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/exception.hpp>

#include <string>
#include <sstream>
#include <stdexcept>

namespace psemek::gfx
{

	obj_data parse_obj(std::istream & is)
	{
		std::vector<math::point<float, 3>> positions;
		std::vector<math::vector<float, 2>> texcoords;
		std::vector<math::vector<float, 3>> normals;

		obj_data result;

		std::string line;
		std::size_t line_count = 0;

		auto fail = [&](auto const & ... args){
			throw util::exception(util::to_string("Error parsing OBJ data, line ", line_count, ": ", args...));
		};

		while (std::getline(is >> std::ws, line))
		{
			++line_count;

			if (line.empty()) continue;

			if (line[0] == '#') continue;

			std::istringstream ls(std::move(line));

			std::string tag;
			ls >> tag;

			if (tag == "v")
			{
				auto & p = positions.emplace_back();
				ls >> p[0] >> p[1] >> p[2];
			}
			else if (tag == "vt")
			{
				auto & t = texcoords.emplace_back();
				ls >> t[0] >> t[1];
			}
			else if (tag == "vn")
			{
				auto & n = normals.emplace_back();
				ls >> n[0] >> n[1] >> n[2];
			}
			else if (tag == "f")
			{
				std::vector<obj_data::vertex> vertices;

				while (ls)
				{
					std::size_t ip, it, in;
					ls >> ip;
					if (ls.eof()) break;
					if (!ls || ls.get() != '/')
						fail("expected '/'");
					ls >> it;
					if (!ls || ls.get() != '/')
						fail("expected '/'");
					ls >> in;

					--ip;
					--it;
					--in;

					if (ip >= positions.size())
						fail("bad position index (", ip, ")");

					if (it >= texcoords.size())
						fail("bad texcoord index (", ip, ")");

					if (in >= normals.size())
						fail("bad normal index (", ip, ")");

					auto & v = vertices.emplace_back();
					v.position = positions[ip];
					v.texcoord = texcoords[it];
					v.normal = normals[in];
				}

				for (std::size_t i = 1; i + 1 < vertices.size(); ++i)
				{
					result.triangles.push_back({{vertices[0], vertices[i], vertices[i + 1]}});
				}
			}
		}

		return result;
	}

}
