// 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 GTSWriter K-3D object, which exports GTS *.gts files
		\author Tom Browder (tbrowder@home.com)
		\author Romain Behar (romainbehar@yahoo.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>
#include <set>

namespace libk3dgeometry
{

/// 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;
}

/// std::pair equivalent that maintains the order of its members
template<typename T1, typename T2>
class ordered_pair;

template<typename T1, typename T2>
bool operator<(const ordered_pair<T1,T2>& lhs, const ordered_pair<T1,T2>& rhs);

template<typename T1, typename T2>
class ordered_pair :
	public std::pair<T1, T2>
{
public:
	typedef T1 first_type;
	typedef T2 second_type;

	T1 first;
	T2 second;

	explicit ordered_pair()
	{
	}

	explicit ordered_pair(const T1& First, const T2& Second) :
		first(First < Second ? First : Second),
		second(First < Second ? Second : First)
	{
	}

	explicit ordered_pair(const k3d::split_edge* Edge) :
		first(Edge->vertex < Edge->face_clockwise->vertex ? Edge->vertex : Edge->face_clockwise->vertex),
		second(Edge->vertex < Edge->face_clockwise->vertex ? Edge->face_clockwise->vertex : Edge->vertex)
	{
	}

	friend bool operator< <>(const ordered_pair& lhs, const ordered_pair& rhs);
};

template<typename T1, typename T2>
bool operator<(const ordered_pair<T1,T2>& lhs, const ordered_pair<T1,T2>& rhs)
{
	if(lhs.first != rhs.first)
		return lhs.first < rhs.first;

	return lhs.second < rhs.second;
}

typedef std::vector<k3d::point*> PointCollection;
typedef std::map<k3d::point*, unsigned long> PointMap;
typedef ordered_pair<k3d::point*, k3d::point*> Edge;
typedef std::set<Edge> EdgeCollection;
typedef std::map<Edge, unsigned long> EdgeMap;
typedef std::vector<Edge> Triangle;
typedef std::vector<Triangle> TriangleCollection;

class WriteMesh
{
public:
	WriteMesh(k3d::idocument& Document, std::ostream& Stream) :
		m_Document(Document),
		m_Stream(Stream)
	{
	}

	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))
			return;

		// Get the collection of points ...
		k3d::mesh::points_t points;
		std::copy(Mesh->points.begin(), Mesh->points.end(), std::back_inserter(points));

		// Create a mapping of points-to-one-based-indices ...
		PointMap pointmap;
		for(PointCollection::iterator point = points.begin(); point != points.end(); ++point)
			pointmap[*point] = pointmap.size();

		// Build the collection of edges ...
		EdgeCollection edges;
		for(k3d::mesh::polyhedra_t::const_iterator polyhedron = Mesh->polyhedra.begin(); polyhedron != Mesh->polyhedra.end(); polyhedron++)
			for(k3d::polyhedron::edges_t::const_iterator edge = (*polyhedron)->edges.begin(); edge != (*polyhedron)->edges.end(); edge++)
				edges.insert(Edge(*edge));

		// Create a mapping of edges-to-one-based-indices ...
		EdgeMap edgemap;
		for(EdgeCollection::iterator edge = edges.begin(); edge != edges.end(); ++edge)
			edgemap[*edge] = edgemap.size();

		// Build triangle collection ...
		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(Edge(first));

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

				triangle.push_back(Edge(current_edge));

				current_edge = current_edge->face_clockwise;
				if(current_edge)
					if(current_edge->face_clockwise == first)
						{
							triangle.push_back(Edge(current_edge));
							triangle_collection.push_back(triangle);
						}
			}

		// Write some comments
		m_Stream << "# object: " << Object->name() << std::endl;
		m_Stream << "# num points [" << points.size() << "] num edges [" << edges.size() << "] num triangles [" << triangle_collection.size() << "]" << std::endl;

		// Data header
		m_Stream << points.size() << " " << edges.size() << " " << triangle_collection.size() << std::endl;

		// Write points
		m_Stream << "# points" << std::endl;
		for(PointCollection::iterator point = points.begin(); point != points.end(); ++point)
			{
				// Transform coords back to GTS convention
				const k3d::vector3 coords = (*point)->position;
				m_Stream << -coords[0] << " " << -coords[2] << " " << coords[1] << std::endl;
			}

		// Write edges
		m_Stream << "# edges" << std::endl;
		for(EdgeCollection::iterator edge = edges.begin(); edge != edges.end(); ++edge)
			m_Stream << pointmap[edge->first] << " " << pointmap[edge->second] << std::endl;

		// Write triangles
		m_Stream << "# triangles" << std::endl;
		for(TriangleCollection::iterator triangle = triangle_collection.begin(); triangle != triangle_collection.end(); ++triangle)
			{
				for(Triangle::iterator edge = triangle->begin(); edge != triangle->end(); ++edge)
					m_Stream << edgemap[*edge] << " ";
				m_Stream << std::endl;
			}
	}

private:
	k3d::idocument& m_Document;
	std::ostream& m_Stream;
};

/////////////////////////////////////////////////////////////////////////////
// gts_writer_implementation

class gts_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 "gts" == 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<gts_writer_implementation>, k3d::interface_list<k3d::igeometry_write_format> > factory(
			k3d::uuid(0xc6bdb531, 0x17a74c0a, 0x99db8c94, 0x38195da7),
			"GTSWriter",
			"GNU Triangulated Surface ( .gts )",
			"");

		return factory;
	}
};

bool gts_writer_implementation::write_file(k3d::idocument& Document, const boost::filesystem::path& FilePath)
{
	// (GTS format is not a real one : it has been designed to store surfaces
	// as sets of points, edges and triangles, only;
	// each surface item can have optional arbitrary values, but no other
	// scene information can be found in a native GTS file)

	// Try to open the output file ...
	boost::filesystem::ofstream file(FilePath);
	return_val_if_fail(file.good(), false);
	file << "# Written by K-3D" << std::endl;

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

	// Write meshes to a file ...
	std::for_each(meshes.begin(), meshes.end(), WriteMesh(Document, file));

	return true;
}

k3d::iplugin_factory& gts_writer_factory()
{
	return gts_writer_implementation::get_factory();
}

} // namespace libk3dgeometry


