// 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 XWriter K-3D object, which exports .x files
		\author Adam Sakareassen (amsaka@ugrad.unimelb.edu.au)
		\author Timothy M. Shead (tshead@k-3d.com)
*/

#include <k3dsdk/classes.h>
#include <k3dsdk/iapplication.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/ifile_format.h>
#include <k3dsdk/igeometry_write_format.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/imaterial_collection.h>
#include <k3dsdk/imesh_source.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/iobject_collection.h>
#include <k3dsdk/mesh.h>
#include <k3dsdk/module.h>
#include <k3dsdk/objects.h>
#include <k3dsdk/property.h>
#include <k3dsdk/result.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/user_interface.h>
#include <k3dsdk/utility.h>
#include <k3dsdk/vectors.h>

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

#include <map>

namespace
{

/// Test to see if a mesh is all triangles
bool triangle_test(k3d::mesh* const Mesh)
{
	k3d::mesh::polyhedra_t::iterator polyhedron;
	for(polyhedron = Mesh->polyhedra.begin(); polyhedron != Mesh->polyhedra.end(); polyhedron++)
		{
			k3d::polyhedron::faces_t::iterator face;
			for(face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); face++)
				{
					k3d::split_edge* first = (*face)->first_edge;
					// Skip empty faces
					if(!first)
						continue;

					k3d::split_edge* current_edge = first->face_clockwise;
					// Skip one-edged faces
					if(!current_edge)
						continue;

					unsigned long edge_number = 1;

					while(current_edge != first)
						{
							edge_number++;
							current_edge = current_edge->face_clockwise;
						}

					if(edge_number != 3)
						return false;
				}
		}

	return true;
}

class write_mesh
{
public:
	write_mesh(k3d::idocument& Document, std::ostream& Stream) :
		m_Document(Document),
		m_stream(Stream),
		m_all_triangles(true)
	{
	}

	void operator()(k3d::iobject* const Object)
	{
		k3d::imesh_source* const mesh_source = dynamic_cast<k3d::imesh_source*>(Object);
		return_if_fail(mesh_source);

		k3d::mesh* const Mesh = boost::any_cast<k3d::mesh*>(k3d::get_property_value(m_Document.dag(), mesh_source->mesh_source_output()));
		return_if_fail(Mesh);

		// Process only if object is triangulated ...
		if(!triangle_test(Mesh))
			{
				//if(m_all_triangles)
					// TODO Warn the user that at least a mesh has been skipped

				m_all_triangles = false;

				return;
			}

		// Write Mesh...
		std::string obj_name = Object->name();
		// All spaces are removed from the mesh name
		std::replace(obj_name.begin(), obj_name.end(), ' ', '_');

		m_stream << "Mesh "<< obj_name << "{" << std::endl;

		// Keep track of 0-based point indices as we write them ...
		std::map<k3d::point*, unsigned long> point_map;
		unsigned long point_index = 0;

		// Write points ...
		m_stream << Mesh->points.size() << ";" << std::endl;
		for(k3d::mesh::points_t::const_iterator point = Mesh->points.begin(); point != Mesh->points.end(); point++)
			{
				if(point != Mesh->points.begin())
					m_stream << "," << std::endl;

				point_map[*point] = point_index++;

				const k3d::vector3 coords = (*point)->position;
				m_stream << coords[0] << ";" << coords[1] << ";" << coords[2] << ";";
			}

		m_stream << ";" << std::endl;

		// Build triangle collection ...
		typedef std::vector<unsigned long> Triangle;
		typedef std::vector<Triangle> TriangleCollection;
		TriangleCollection triangle_collection;
		for(k3d::mesh::polyhedra_t::const_iterator polyhedron = Mesh->polyhedra.begin(); polyhedron != Mesh->polyhedra.end(); polyhedron++)
			for(k3d::polyhedron::faces_t::const_iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); face++)
			{
				Triangle triangle;

				k3d::split_edge* first = (*face)->first_edge;
				// Skip empty faces
				if(!first)
					continue;

				triangle.push_back(point_map[first->vertex]);

				k3d::split_edge* current_edge = first->face_clockwise;
				// Skip one-edged faces
				if(!current_edge)
					continue;

				triangle.push_back(point_map[current_edge->vertex]);

				current_edge = current_edge->face_clockwise;
				if(current_edge)
					if(current_edge->face_clockwise == first)
						{
							triangle.push_back(point_map[current_edge->vertex]);
							triangle_collection.push_back(triangle);
						}
			}

		// Write the total number of faces
		m_stream << triangle_collection.size() << ";" <<std::endl;

		// Write triangles (as references to above points)
		for(TriangleCollection::const_iterator triangle = triangle_collection.begin(); triangle != triangle_collection.end(); triangle++)
			{
				if(triangle != triangle_collection.begin())
					m_stream << ";," << std::endl;

				// Write the triangle ...
				m_stream << "3;";
				for(Triangle::const_iterator point = triangle->begin(); point != triangle->end(); point++)
					{
						if(point != triangle->begin())
							m_stream << ",";

						m_stream << *point;
					}
			}

		m_stream << ";;" << std::endl;
		m_stream << "}" << std::endl;
	}

private:
	k3d::idocument& m_Document;
	std::ostream& m_stream;
	bool m_all_triangles;
};

/////////////////////////////////////////////////////////////////////////////
// x_writer_implementation

class x_writer_implementation :
	public k3d::ifile_format,
	public k3d::igeometry_write_format,
	public k3d::ideletable
{
public:
	unsigned long priority()
	{
		return 0;
	}

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

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

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

	bool write_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<x_writer_implementation>, k3d::interface_list<k3d::igeometry_write_format> > factory(
			k3d::uuid(0xefacec19, 0x863a4f94, 0x80057a31, 0x2a1f13a1),
			"XWriter",
			"DirectX ( .x )",
			"");

		return factory;
	}
};


bool x_writer_implementation::write_file(k3d::idocument& Document, const boost::filesystem::path& FilePath)
{
	// Try to open the output file ...
	boost::filesystem::ofstream file(FilePath);
	return_val_if_fail(file.good(), false);

	// Write Header information
	file << "xof 0302txt 0032" << std::endl;
	file << "Header {" << std::endl;
	file << " 1;" << std::endl;
	file << " 0;" << std::endl;
	file << " 1;" << std::endl;
	file << "}" << std::endl;

	file << "//Created using K-3D " << std::endl;
	file << "//Exporter plugin written by Adam Sakareassen"<< std::endl;
	file << "//Note: This file may have been created with unix style line endings"<< std::endl;
	file << "//      in this case you could use the utility unix2dos to convert to windows style"<< std::endl;

	// Write template information
	file << ""<< std::endl;
	file << "template Header {"<< std::endl;
	file << " <3D82AB43-62DA-11cf-AB39-0020AF71E433>"<< std::endl;
	file << " WORD major;"<< std::endl;
	file << " WORD minor;"<< std::endl;
	file << " DWORD flags;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template Vector {"<< std::endl;
	file << " <3D82AB5E-62DA-11cf-AB39-0020AF71E433>"<< std::endl;
	file << " FLOAT x;"<< std::endl;
	file << " FLOAT y;"<< std::endl;
	file << " FLOAT z;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template Coords2d {"<< std::endl;
	file << "<F6F23F44-7686-11cf-8F52-0040333594A3>"<< std::endl;
	file << "FLOAT u;"<< std::endl;
	file << "FLOAT v;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template Matrix4x4 {"<< std::endl;
	file << " <F6F23F45-7686-11cf-8F52-0040333594A3>"<< std::endl;
	file << "array FLOAT matrix[16];"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template ColorRGBA {"<< std::endl;
	file << " <35FF44E0-6C7C-11cf-8F52-0040333594A3>"<< std::endl;
	file << " FLOAT red;"<< std::endl;
	file << " FLOAT green;"<< std::endl;
	file << " FLOAT blue;"<< std::endl;
	file << " FLOAT alpha;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template ColorRGB {"<< std::endl;
	file << " <D3E16E81-7835-11cf-8F52-0040333594A3>"<< std::endl;
	file << " FLOAT red;"<< std::endl;
	file << " FLOAT green;"<< std::endl;
	file << " FLOAT blue;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template TextureFilename {"<< std::endl;
	file << " <A42790E1-7810-11cf-8F52-0040333594A3>"<< std::endl;
	file << " STRING filename;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template Material {"<< std::endl;
	file << " <3D82AB4D-62DA-11cf-AB39-0020AF71E433>"<< std::endl;
	file << " ColorRGBA faceColor;"<< std::endl;
	file << " FLOAT power;"<< std::endl;
	file << " ColorRGB specularColor;"<< std::endl;
	file << " ColorRGB emissiveColor;"<< std::endl;
	file << " [...]"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template MeshFace {"<< std::endl;
	file << " <3D82AB5F-62DA-11cf-AB39-0020AF71E433>"<< std::endl;
	file << " DWORD nFaceVertexIndices;"<< std::endl;
	file << " array DWORD faceVertexIndices[nFaceVertexIndices];"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template MeshTextureCoords {"<< std::endl;
	file << " <F6F23F40-7686-11cf-8F52-0040333594A3>"<< std::endl;
	file << " DWORD nTextureCoords;"<< std::endl;
	file << " array Coords2d textureCoords[nTextureCoords];"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template MeshMaterialList {"<< std::endl;
	file << " <F6F23F42-7686-11cf-8F52-0040333594A3>"<< std::endl;
	file << " DWORD nMaterials;"<< std::endl;
	file << " DWORD nFaceIndexes;"<< std::endl;
	file << " array DWORD faceIndexes[nFaceIndexes];"<< std::endl;
	file << " [Material]"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template MeshNormals {"<< std::endl;
	file << " <F6F23F43-7686-11cf-8F52-0040333594A3>"<< std::endl;
	file << " DWORD nNormals;"<< std::endl;
	file << " array Vector normals[nNormals];"<< std::endl;
	file << " DWORD nFaceNormals;"<< std::endl;
	file << " array MeshFace faceNormals[nFaceNormals];"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template Mesh {"<< std::endl;
	file << "<3D82AB44-62DA-11cf-AB39-0020AF71E433>"<< std::endl;
	file << " DWORD nVertices;"<< std::endl;
	file << " array Vector vertices[nVertices];"<< std::endl;
	file << " DWORD nFaces;"<< std::endl;
	file << " array MeshFace faces[nFaces];"<< std::endl;
	file << " [...]"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template FrameTransformMatrix {"<< std::endl;
	file << " <F6F23F41-7686-11cf-8F52-0040333594A3>"<< std::endl;
	file << " Matrix4x4 frameMatrix;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template Frame {"<< std::endl;
	file << "<3D82AB46-62DA-11cf-AB39-0020AF71E433>"<< std::endl;
	file << " [...]"<< std::endl;
	file << "}"<< std::endl;
	file << "template FloatKeys {"<< std::endl;
	file << " <10DD46A9-775B-11cf-8F52-0040333594A3>"<< std::endl;
	file << " DWORD nValues;"<< std::endl;
	file << " array FLOAT values[nValues];"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template TimedFloatKeys {"<< std::endl;
	file << " <F406B180-7B3B-11cf-8F52-0040333594A3>"<< std::endl;
	file << " DWORD time;"<< std::endl;
	file << " FloatKeys tfkeys;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template AnimationKey {"<< std::endl;
	file << " <10DD46A8-775B-11cf-8F52-0040333594A3>"<< std::endl;
	file << " DWORD keyType;"<< std::endl;
	file << " DWORD nKeys;"<< std::endl;
	file << "array TimedFloatKeys keys[nKeys];"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template AnimationOptions {"<< std::endl;
	file << " <E2BF56C0-840F-11cf-8F52-0040333594A3>"<< std::endl;
	file << " DWORD openclosed;"<< std::endl;
	file << " DWORD positionquality;"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template Animation {"<< std::endl;
	file << " <3D82AB4F-62DA-11cf-AB39-0020AF71E433>"<< std::endl;
	file << " [...]"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	file << "template AnimationSet {"<< std::endl;
	file << " <3D82AB50-62DA-11cf-AB39-0020AF71E433>"<< std::endl;
	file << " [Animation]"<< std::endl;
	file << "}"<< std::endl;
	file << ""<< std::endl;
	// end of template information

	// Get the set of available meshes ...
	k3d::objects_t meshes(k3d::find_objects(Document.objects(), k3d::classes::MeshInstance()));

	// Write them ...
	std::for_each(meshes.begin(), meshes.end(), write_mesh(Document, file));

	return true;
}

} // namespace

namespace libk3dgeometry
{

k3d::iplugin_factory& x_writer_factory()
{
	return x_writer_implementation::get_factory();
}

} // namespace libk3dgeometry


