// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
		\brief Implements the YafrayReader object, which imports Yafray XML files
		\author Romain Behar (romainbehar@yahoo.com)
*/

#include <k3dsdk/algebra.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/color.h>
#include <k3dsdk/file_helpers.h>
#include <k3dsdk/idag.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/idocument_plugin_factory.h>
#include <k3dsdk/ifile_format.h>
#include <k3dsdk/igeometry_read_format.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/imaterial_collection.h>
#include <k3dsdk/imesh_sink.h>
#include <k3dsdk/imesh_source.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/iobject_collection.h>
#include <k3dsdk/itransform_sink.h>
#include <k3dsdk/itransform_source.h>
#include <k3dsdk/material.h>
#include <k3dsdk/mesh.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/property.h>
#include <k3dsdk/result.h>
#include <k3dsdk/selection.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/utility.h>
#include <k3dsdk/vectors.h>

#include <Hapy/Parser.h>
#include <Hapy/Rules.h>

#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/path.hpp>

#include <iostream>
#include <map>
#include <stack>

namespace libk3dyafray
{

using namespace Hapy;

Rule rXML("xml", 0);
Rule rPi("pi", 0);
Rule rElement("element", 0);
Rule rOpenElement("open-element", 0);
Rule rCloseElement("close-element", 0);
Rule rClosedElement("closed-element", 0);
Rule rText("text", 0);
Rule rAttr("attr", 0);
Rule rName("name", 0);
Rule rValue("value", 0);
Rule rComment("comment", 0);

static bool hapy_yafray_parser = false;

void create_parser()
{
	if(hapy_yafray_parser)
		return;

	hapy_yafray_parser = true;


	rXML = rOpenElement | rText | rPi | rComment | rCloseElement | rClosedElement;
	rXML.trim(*space_r);

	rPi = "<?" >> rName >> *(anychar_r - "?>") >> "?>";

	rOpenElement = "<" >> rName >> *rAttr >> ">";
	rCloseElement = "</" >> rName >> ">";
	rClosedElement = "<" >> rName >> *rAttr >> "/>";

	rText = +(anychar_r - '<');

	rAttr = rName >> '=' >> rValue;
	rName = alpha_r >> *(alnum_r | '_' | ':');
	rValue = quoted_r(anychar_r);

	rComment = "<!--" >> *(anychar_r - "-->") >> "-->";

	// trimming rules
	rText.verbatim(true);
	rName.verbatim(true);
	rValue.verbatim(true);

	// parse tree shaping rules
	rText.leaf(true);
	rName.leaf(true);
	rValue.leaf(true);

	// parsing optimization rules
	rText.committed(true);
	rName.committed(true);
	rValue.committed(true);

	// skip comments
	rComment.verbatim(true);
	rComment.leaf(true);
}

// Helper functions
const std::string no_quotes(const std::string& s)
{
	if(s[0] == '"' && s[s.size() - 1] == '"')
		return std::string(s.begin() + 1, s.end() - 1);

	return s;
}

// Creates a new mesh, put it in a FrozenMesh() and connect it a MeshInstance
k3d::mesh* create_mesh_instance(k3d::idocument& Document, const std::string& Name, k3d::iobject*& ReturnFrozenMesh, k3d::iobject*& ReturnMeshInstance)
{
	k3d::mesh* const mesh = new k3d::mesh();
	return_val_if_fail(mesh, 0);

	// Create document object ...
	k3d::iobject* const frozen_mesh = k3d::create_document_plugin(k3d::classes::FrozenMesh(), Document);
	return_val_if_fail(frozen_mesh, 0);
	ReturnFrozenMesh = frozen_mesh;

	k3d::imesh_sink* const frozen_mesh_sink = dynamic_cast<k3d::imesh_sink*>(frozen_mesh);
	return_val_if_fail(frozen_mesh_sink, 0);
	k3d::imesh_source* const frozen_mesh_source = dynamic_cast<k3d::imesh_source*>(frozen_mesh);
	return_val_if_fail(frozen_mesh_source, 0);
	k3d::set_property_value(frozen_mesh_sink->mesh_sink_input(), mesh);

	frozen_mesh->set_name(Name);

	// Create mesh object instance ...
	k3d::iobject* instance = k3d::create_document_plugin(k3d::classes::MeshInstance(), Document);
	return_val_if_fail(instance, 0);
	ReturnMeshInstance = instance;

	instance->set_name(Name + " instance");

	// Set dependencies ...
	k3d::imesh_sink* const instance_sink = dynamic_cast<k3d::imesh_sink*>(instance);
	return_val_if_fail(instance_sink, false);

	k3d::idag::dependencies_t dependencies;
	dependencies[&instance_sink->mesh_sink_input()] = &frozen_mesh_source->mesh_source_output();
	Document.dag().set_dependencies(dependencies);

	return mesh;
}

/////////////////////////////////////////////////////////////////////////////
// yafray_reader_implementation

class yafray_reader_implementation :
	public k3d::ifile_format,
	public k3d::igeometry_read_format,
	public k3d::ideletable
{
public:
	yafray_reader_implementation()
	{
		m_inside_world = false;

		m_current_mesh = 0;
		m_current_polyhedron = 0;
		m_current_linear_curve_group = 0;

		m_is_solid = false;

		m_transform_number = 0;

		// Init parser ...
		create_parser();
	}

	unsigned long priority()
	{
		return 0;
	}

	bool query_can_handle(const boost::filesystem::path& FilePath)
	{
		return "xml" == k3d::file_extension(FilePath);
	}

	bool pre_read(k3d::idocument& Document, const boost::filesystem::path& FilePath)
	{
		return true;
	}

	bool read_options(k3d::idocument& Document, const boost::filesystem::path& FilePath)
	{
		return true;
	}

	bool read_file(k3d::idocument& Document, const boost::filesystem::path& FilePath);

	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::application_plugin<yafray_reader_implementation>, k3d::interface_list<k3d::igeometry_read_format> > factory(
			k3d::uuid(0x872c46ea, 0x5b81454b, 0xb269f9db, 0x5184d7cd),
			"YafrayReader",
			"Yafray ( .xml )",
			"");

		return factory;
	}

private:
	// Parser
	Hapy::Parser parser;
	std::stack<std::string> xml_tag_stack;

	std::map<std::string, k3d::imaterial*> m_materials;

	// Properties
	std::map<std::string, std::string> m_declarations;
	k3d::color m_current_color;
	k3d::color m_current_opacity;

	// Frame parameters
	bool m_inside_world;
	k3d::matrix4 frame_transformation;
	k3d::vector3 frame_translation;
	k3d::matrix4 frame_rotation;
	k3d::vector3 frame_scaling;

	// Transformation stacks
	unsigned long m_transform_number;
	std::stack<std::string> begin_end_stack;
	std::vector<k3d::matrix4> m_transformations;
	std::vector<k3d::vector3> m_translations;
	std::vector<k3d::matrix4> m_rotations;
	std::vector<k3d::vector3> m_scalings;

	bool m_is_solid;
	std::string m_solid_type;

	// Mesh variables and functions
	std::stack<k3d::iobject*> m_transform_objects;
	k3d::mesh* m_current_mesh;
	unsigned long m_current_mesh_points_index;
	std::string m_current_object_name;
	std::string m_current_name;
	k3d::iobject* m_current_mesh_object;
	k3d::iobject* m_current_mesh_instance;
	k3d::nupatch* m_current_nupatch;
	k3d::bilinear_patch* m_current_bilinear_patch;
	k3d::polyhedron* m_current_polyhedron;
	k3d::linear_curve_group* m_current_linear_curve_group;
	k3d::iobject* m_current_material;
	k3d::iobject* m_current_shader;

	bool create_mesh(k3d::idocument& Document, const std::string& name)
	{
		// Create document object ...
		k3d::mesh* const mesh = create_mesh_instance(Document, name, m_current_mesh_object, m_current_mesh_instance);
		return_val_if_fail(mesh, false);

		m_current_mesh = mesh;

		return true;
	}

	bool add_polyhedron()
	{
		if(!m_current_mesh)
			{
				m_current_polyhedron = 0;
				return false;
			}

		k3d::polyhedron* polyhedron = new k3d::polyhedron();
		return_val_if_fail(polyhedron, false);

		m_current_mesh->polyhedra.push_back(polyhedron);

		m_current_polyhedron = polyhedron;

		return true;
	}

	void parse_subpree(const Hapy::Pree& Node, k3d::idocument& Document);
	bool interpret(const Result& result, k3d::idocument& Document);
	void load_light(const Hapy::Pree& Light, k3d::idocument& Document);

	void set_current_transformations(k3d::iobject* Object)
	{
		if(!m_transformations.size())
			return;

		assert_warning(Object);

		assert_warning(k3d::set_property_value(*Object, "position", k3d::vector3(extractTranslation(*m_transformations.rbegin()))));
		assert_warning(k3d::set_property_value(*Object, "orientation", k3d::angle_axis(k3d::rotation3D(*m_transformations.rbegin()))));
		assert_warning(k3d::set_property_value(*Object, "scale", k3d::vector3(k3d::extractScaling(*m_transformations.rbegin()))));
	}
};

const std::string get_attribute(const Pree& tag, const std::string& name)
{
	for(Hapy::Pree::const_iterator node = tag.begin(); node != tag.end(); node++)
		{
			if(node->rid() == rName.id())
				continue;

			for(Hapy::Pree::const_iterator attrs = node->begin(); attrs != node->end(); attrs++)
				{
					if(attrs->rid() != rAttr.id())
						continue;

					std::string attr_name = attrs->begin()->image();
					if(attr_name == name)
						{
							std::string value = (attrs->begin() + 2)->image();
							return no_quotes(value);
						}
				}
		}

	return std::string("");
}

void yafray_reader_implementation::parse_subpree(const Hapy::Pree& Node, k3d::idocument& Document)
{
	const Pree& alt = Node[0];
	if(alt.rid() == rOpenElement.id())
		{
			const Pree& name = alt.find(rName.id());
			xml_tag_stack.push(name.image());

			if(name.image() == "transform")
				{
					k3d::matrix4 m;

					double m00 = k3d::from_string<double>(get_attribute(alt, "m00"), 0);
					double m01 = k3d::from_string<double>(get_attribute(alt, "m01"), 0);
					double m02 = k3d::from_string<double>(get_attribute(alt, "m02"), 0);
					double m03 = k3d::from_string<double>(get_attribute(alt, "m03"), 0);
					m[0] = k3d::vector4(m00, m01, m02, m03);

					m00 = k3d::from_string<double>(get_attribute(alt, "m10"), 0);
					m01 = k3d::from_string<double>(get_attribute(alt, "m11"), 0);
					m02 = k3d::from_string<double>(get_attribute(alt, "m12"), 0);
					m03 = k3d::from_string<double>(get_attribute(alt, "m13"), 0);
					m[1] = k3d::vector4(m00, m01, m02, m03);

					m00 = k3d::from_string<double>(get_attribute(alt, "m20"), 0);
					m01 = k3d::from_string<double>(get_attribute(alt, "m21"), 0);
					m02 = k3d::from_string<double>(get_attribute(alt, "m22"), 0);
					m03 = k3d::from_string<double>(get_attribute(alt, "m23"), 0);
					m[2] = k3d::vector4(m00, m01, m02, m03);

					m00 = k3d::from_string<double>(get_attribute(alt, "m30"), 0);
					m01 = k3d::from_string<double>(get_attribute(alt, "m31"), 0);
					m02 = k3d::from_string<double>(get_attribute(alt, "m32"), 0);
					m03 = k3d::from_string<double>(get_attribute(alt, "m33"), 0);
					m[3] = k3d::vector4(m00, m01, m02, m03);

					m_transformations.push_back(m);
				}
			else if(name.image() == "light")
				{
					load_light(alt, Document);
				}
			else if(name.image() == "object")
				{
					m_current_object_name = get_attribute(alt, "name");
				}
			else if(name.image() == "mesh")
				{
					create_mesh(Document, m_current_object_name);
					set_current_transformations(m_current_mesh_instance);
				}
			else if(name.image() == "points")
				{
					m_current_mesh_points_index = m_current_mesh->points.size();
				}
			else if(name.image() == "faces")
				{
					assert_warning(add_polyhedron());
				}
		}
	else if(alt.rid() == rText.id())
		{
			std::cout << "text " << alt.image() << std::endl;
		}
	else if(alt.rid() == rPi.id())
		{
			std::cout << "pi : " << alt.image() << std::endl;
		}
	else if(alt.rid() == rComment.id())
		{
			// Skip
		}
	else if(alt.rid() == rCloseElement.id())
		{
			const Pree& name = alt.find(rName.id());
			assert_warning(xml_tag_stack.top() == name.image());

			if(name.image() == "transform")
				{
					assert_warning(m_transformations.size());
					m_transformations.pop_back();
				}

			xml_tag_stack.pop();
		}
	else if(alt.rid() == rClosedElement.id())
		{
			const Pree& name = alt.find(rName.id());
			if(name.image() == "p")
				{
					if(xml_tag_stack.top() == "points")
						{
							double x = k3d::from_string<double>(get_attribute(alt, "x"), 0);
							double y = k3d::from_string<double>(get_attribute(alt, "y"), 0);
							double z = k3d::from_string<double>(get_attribute(alt, "z"), 0);

							// Change handedness
							k3d::point* point = new k3d::point(-x, z, -y);
							m_current_mesh->points.push_back(point);
						}
					else
						std::cerr << warning << "unused 'p' element : " << alt.image() << std::endl;
				}
			else if(name.image() == "f")
				{
					if(xml_tag_stack.top() == "faces")
						{
							return_if_fail(m_current_polyhedron);

							unsigned long a = k3d::from_string<unsigned long>(get_attribute(alt, "a"), 0);
							unsigned long b = k3d::from_string<unsigned long>(get_attribute(alt, "b"), 0);
							unsigned long c = k3d::from_string<unsigned long>(get_attribute(alt, "c"), 0);

							// Create triangle
							k3d::polyhedron::edges_t edges;
							edges.push_back(new k3d::split_edge(m_current_mesh->points[a + m_current_mesh_points_index]));
							edges.push_back(new k3d::split_edge(m_current_mesh->points[b + m_current_mesh_points_index]));
							edges.push_back(new k3d::split_edge(m_current_mesh->points[c + m_current_mesh_points_index]));

							k3d::loop_edges(edges.begin(), edges.end());
							m_current_polyhedron->edges.insert(m_current_polyhedron->edges.end(), edges.begin(), edges.end());

							k3d::face* const face = new k3d::face(*edges.begin());
							return_if_fail(face);

							m_current_polyhedron->faces.push_back(face);
						}
					else
						std::cerr << warning << "unused 'f' element : " << alt.image() << std::endl;
				}
			else
				std::cerr << warning << "unknown element : " << alt.image() << std::endl;
		}
	else
		std::cerr << warning << "Unknown element" << std::endl;
}

void yafray_reader_implementation::load_light(const Hapy::Pree& Light, k3d::idocument& Document)
{
	const std::string type = get_attribute(Light, "type");
	if(type == "hemilight")
		{
			k3d::iobject* const hemi_light = k3d::create_document_plugin(k3d::uuid(0xa0661dc7, 0x52cd4990, 0x8e6a0aa8, 0x87bdd89d), Document);

			const std::string name = get_attribute(Light, "name");
			hemi_light->set_name(name);

			// Set properties
			assert_warning(k3d::set_property_value(*hemi_light, "power", k3d::from_string<double>(get_attribute(Light, "power"), 0)));
			assert_warning(k3d::set_property_value(*hemi_light, "samples", k3d::from_string<unsigned long>(get_attribute(Light, "samples"), 0)));
		}
	else if(type == "spotlight")
		{
			k3d::iobject* const spot_light = k3d::create_document_plugin(k3d::uuid(0x5e363371, 0xf8464895, 0x99f0ddf0, 0x4e26ee4a), Document);

			const std::string name = get_attribute(Light, "name");
			spot_light->set_name(name);

			// Set properties
			assert_warning(k3d::set_property_value(*spot_light, "power", k3d::from_string<double>(get_attribute(Light, "power"), 0)));
			assert_warning(k3d::set_property_value(*spot_light, "size", k3d::from_string<double>(get_attribute(Light, "size"), 0)));
			assert_warning(k3d::set_property_value(*spot_light, "nblend", k3d::from_string<double>(get_attribute(Light, "blend"), 0)));
			assert_warning(k3d::set_property_value(*spot_light, "beam_falloff", k3d::from_string<double>(get_attribute(Light, "beam_falloff"), 0)));
		}
	else
		{
			std::cerr << warning << "unknown light type : " << type << std::endl;
		}
}

bool yafray_reader_implementation::interpret(const Result& result, k3d::idocument& Document)
{
	if(result.statusCode == Result::scMatch)
		{
			parse_subpree(result.pree, Document);
			return true;
		}

	if(result.input.size() > 0)
		std::cerr << result.location() << ": syntax error" << std::endl;

	return false;
}


bool yafray_reader_implementation::read_file(k3d::idocument& Document, const boost::filesystem::path& FilePath)
{
	// Try to open the input file ...
	boost::filesystem::ifstream yafray_file(FilePath);
	if(!yafray_file.good())
		{
			std::cerr << warning << __PRETTY_FUNCTION__ << ": error opening [" << FilePath.native_file_string() << "]" << std::endl;
			return_val_if_fail(0, false);
		}

	// Parse file
	Parser parser;
	parser.grammar(rXML);

	do
		{
			parser.moveOn();
			if(parser.begin())
				{
					while(parser.step())
						{
							if(parser.sawDataEnd())
								continue;

							if(yafray_file.eof())
								{
									parser.sawDataEnd(true);
									continue;
								}

							char c = yafray_file.get();
							while(c != ' ' && c != '\n' && c != '\r' && c != '\t')
								{
									parser.pushData(string(1, c));
									c = yafray_file.get();
if(yafray_file.eof())
{
									parser.sawDataEnd(true);
	break;
}
								}

							parser.pushData(string(1, c));
						}
				}

			parser.end();
		}
	while(interpret(parser.result(), Document));

	return true;
}

k3d::iplugin_factory& yafray_reader_factory()
{
	return yafray_reader_implementation::get_factory();
}

} // namespace libk3dyafray


