// 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 PLIB Reader that reads PLIB .* files
		\author Romain Behar (romainbehar@yahoo.com)
*/

#include <k3dsdk/idag.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/ifile_format.h>
#include <k3dsdk/igeometry_read_format.h>
#include <k3dsdk/imaterial_collection.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/imesh_sink.h>
#include <k3dsdk/imesh_source.h>
#include <k3dsdk/imouse_event_observer.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/iobject_collection.h>
#include <k3dsdk/classes.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 <plib/ssg.h>

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

namespace libk3dplib
{

// Creates a new mesh, put it in a FrozenMesh() and connect it a MeshInstance
k3d::mesh* create_mesh(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;
}

void LoadSSG(ssgEntity* node, std::vector<k3d::vector3>& points, std::vector<unsigned long>& triangles)
{
	// Recursive function
	if(node->isAKindOf(ssgTypeBranch()))
	{
		// Branch node, check kids
		const int nkids = ((ssgBranch*)node)->getNumKids();

		for(int i = 0; i < nkids; ++i)
			LoadSSG(((ssgBranch*)node)->getKid(i), points, triangles);
	}

	if(!node->isAKindOf(ssgTypeLeaf()))
		return;


	ssgLeaf* leaf = static_cast<ssgLeaf*>(node);
	const int trianglenb = leaf->getNumTriangles();

	short max = 0;
	for(int i = 0; i < trianglenb; ++i)
	{
		short v1, v2, v3;
		leaf->getTriangle(i, &v1, &v2, &v3);
		triangles.push_back(static_cast<unsigned long>(v1));
		triangles.push_back(static_cast<unsigned long>(v2));
		triangles.push_back(static_cast<unsigned long>(v3));

		if(v1 > max) max = v1;
		if(v2 > max) max = v2;
		if(v3 > max) max = v3;
	}

	for(short i = 0; i <= max; ++i)
	{
		float* const vertex = leaf->getVertex(i);
		points.push_back(k3d::vector3(static_cast<double>(vertex[0]),
					    static_cast<double>(vertex[1]),
					    static_cast<double>(vertex[2])));
	}
}

/////////////////////////////////////////////////////////////////////////////
// plib_reader

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

	bool query_can_handle(const boost::filesystem::path& FilePath)
	{
		// Sanity checks ...
		return_val_if_fail(!FilePath.empty(), false);

		const std::string ext = k3d::file_extension(FilePath);

		if("3ds" == ext)
			return true;
		if("ac" == ext)
			return true;
		if("ase" == ext)
			return true;
		if("atg" == ext)
			return true;
		if("dxf" == ext)
			return true;
		if("flt" == ext)
			return true;
		if("iv" == ext)
			return true;
		if("m" == ext)
			return true;
		if("md2" == ext)
			return true;
		if("mdl" == ext)
			return true;
		if("obj" == ext)
			return true;
		if("off" == ext)
			return true;
		if("ssg" == ext)
			return true;
		if("strip" == ext)
			return true;
		if("tri" == ext)
			return true;
		if("vrml" == ext)
			return true;
		if("x" == ext)
			return true;

		return false;
	}

	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<plib_reader>, k3d::interface_list<k3d::igeometry_read_format> > factory(
			k3d::uuid(0x9dbd3531, 0x9a4149f6, 0x9b374476, 0xb1c4990c),
			"PLIBReader",
			"PLIB-supported formats ( various )",
			"");

		return factory;
	}
};

bool plib_reader::read_file(k3d::idocument& Document, const boost::filesystem::path& FilePath)
{
	// Sanity checks ...
	return_val_if_fail(!FilePath.empty(), false);

	// Init SSG library loader
	ssgLoaderOptions *loader_opts = new ssgLoaderOptions();
	ssgSetCurrentOptions(loader_opts);

	boost::filesystem::path file(FilePath);
	std::string dirname = file.branch_path().native_file_string();
	std::string filename = file.leaf();

	// Setup model and texture paths
	ssgModelPath(dirname.c_str());
	ssgTexturePath(dirname.c_str());

	// Load file; file type automatically detected by plib based on its extension
	ssgEntity* ssg_object = ssgLoadOBJ(FilePath.native_file_string().c_str(), loader_opts);
	// It seems not to work using PLIB 1.6 on Debian, do it manually in case it fails
	if(!ssg_object)
	{
		std::string extension = k3d::file_extension(filename);
		if(extension == "ssg")
			ssg_object = ssgLoadSSG(FilePath.native_file_string().c_str(), loader_opts);
		if(extension == "ac")
			ssg_object = ssgLoadAC(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "3ds")
			ssg_object = ssgLoad3ds(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "ase")
			ssg_object = ssgLoadASE(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "dxf")
			ssg_object = ssgLoadDXF(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "flt")
			ssg_object = ssgLoadFLT(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "md2")
			ssg_object = ssgLoadMD2(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "mdl")
			ssg_object = ssgLoadMDL(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "obj")
			ssg_object = ssgLoadOBJ(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "tri")
			ssg_object = ssgLoadTRI(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "x")
			ssg_object = ssgLoadX(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "off")
			ssg_object = ssgLoadOFF(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "m")
			ssg_object = ssgLoadM(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "atg")
			ssg_object = ssgLoadATG(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "vrml" || extension == "vrml1")
			ssg_object = ssgLoadVRML1(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "iv")
			ssg_object = ssgLoadIV(FilePath.native_file_string().c_str(), loader_opts);
		else if(extension == "strip")
			ssg_object = ssgLoadStrip(FilePath.native_file_string().c_str(), loader_opts);
	}
	return_val_if_fail(ssg_object, false);

	ssgFlatten(ssg_object);

	// Create mesh ...
	k3d::iobject* mesh_instance;
	k3d::iobject* mesh_object;
	k3d::mesh* const mesh = create_mesh(Document, "PLIB import", mesh_object, mesh_instance);
	return_val_if_fail(mesh, false);

	// Always use the default material
	k3d::imaterial* default_material = dynamic_cast<k3d::imaterial*>(k3d::default_material(Document));

	// Load geometry
	std::vector<k3d::vector3> points;
	std::vector<unsigned long> triangles;
	LoadSSG(ssg_object, points, triangles);

	// Create points ...
	std::vector<k3d::point*> k3dpoints;
	for(unsigned long n = 0; n < points.size(); ++n)
	{
		k3d::vector3 coords = points[n];
		k3d::point* const point = new k3d::point(coords[0], coords[1], coords[2]);
		return_val_if_fail(point, false);
		k3dpoints.push_back(point);
		mesh->points.push_back(point);
	}

	// Create paths ...
	k3d::polyhedron* const polyhedron = new k3d::polyhedron();
	return_val_if_fail(polyhedron, false);
	mesh->polyhedra.push_back(polyhedron);
	polyhedron->material = default_material;
	for(unsigned long face = 0; face < triangles.size()/3; face++)
	{
		// Create a path ...
		std::vector<k3d::split_edge*> edges;
		for(unsigned long i = 0; i < 3; ++i)
		{
			k3d::split_edge* edge = new k3d::split_edge(k3dpoints[triangles[face*3+i]]);
			return_val_if_fail(edge, false);

			edges.push_back(edge);
		}

		for(std::vector<k3d::split_edge*>::iterator e = edges.begin(); e != edges.end(); e++)
		{
			if(e == edges.begin())
				(*(edges.end() - 1))->face_clockwise = *e;
			else
				(*(e - 1))->face_clockwise = *e;

			polyhedron->edges.push_back(*e);
		}

		k3d::face* const face = new k3d::face(*edges.begin());
		return_val_if_fail(face, false);
		polyhedron->faces.push_back(face);
	}

	return true;
}

} // namespace libk3dplib

K3D_MODULE_START(k3d::uuid(0x8991f963, 0x6c8b4408, 0x8a515eb9, 0x8eb0811b), Registry)
	Registry.register_factory(libk3dplib::plib_reader::get_factory());
K3D_MODULE_END


