// 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 Quake MD3 file reader.
		\author Ed Millard (emillard@direcway.com)
*/

#include <k3dsdk/classes.h>
#include <k3dsdk/file_helpers.h>
#include <k3dsdk/high_res_timer.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/ifile_format.h>
#include <k3dsdk/igeometry_read_format.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/imaterial_collection.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/k3dilocation.h>
#include <k3dsdk/k3dipathpoint.h>
#include <k3dsdk/k3dipathpointcollection.h>
#include <k3dsdk/k3dmesh.h>
#include <k3dsdk/material.h>
#include <k3dsdk/module.h>
#include <k3dsdk/property.h>
#include <k3dsdk/selection.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/transform.h>
#include <k3dsdk/utility.h>
#include <k3dsdk/vectors.h>

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

#include <iostream>
#include <map>

namespace
{

/////////////////////////////////////////////////////////////////////////////
// md3_reader_implementation

class md3_reader_implementation :
	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)
	{
		return "md3" == k3d::file_extension(FilePath);
	}

	bool pre_read(k3d::idocument&, 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<md3_reader_implementation>, k3d::interface_list<k3d::igeometry_read_format> > factory(
			k3d::uuid(0x43488899, 0x635f46d4, 0xa5a0f51d, 0x0c4d019a),
			"MD3Reader",
			"Quake MD3 ( .md3 )",
			"");

		return factory;
	}
};

/*
class material_factory
{
public:
	material_factory() :
		m_carbon(0),
		m_nitrogen(0),
		m_oxygen(0),
		m_hydrogen(0),
		m_sulphur(0),
		m_iron(0)
	{
	}

	k3d::imaterial* carbon(k3d::idocument& Document)
	{
		if(!m_carbon)
			m_carbon = create_material(Document, "Carbon", k3d::vector3(0, 1, 0));
		return m_carbon;
	}

	k3d::imaterial* nitrogen(k3d::idocument& Document)
	{
		if(!m_nitrogen)
			m_nitrogen = create_material(Document, "Nitrogen", k3d::vector3(0, 0, 1));
		return m_nitrogen;
	}

	k3d::imaterial* oxygen(k3d::idocument& Document)
	{
		if(!m_oxygen)
			m_oxygen = create_material(Document, "Oxygen", k3d::vector3(1, 0, 0));
		return m_oxygen;
	}

	k3d::imaterial* hydrogen(k3d::idocument& Document)
	{
		if(!m_hydrogen)
			m_hydrogen = create_material(Document, "Hydrogen", k3d::vector3(1, 1, 1));
		return m_hydrogen;
	}

	k3d::imaterial* sulphur(k3d::idocument& Document)
	{
		if(!m_sulphur)
			m_sulphur = create_material(Document, "Sulphur", k3d::vector3(1, 1, 0));
		return m_sulphur;
	}

	k3d::imaterial* iron(k3d::idocument& Document)
	{
		if(!m_iron)
			m_iron = create_material(Document, "Iron", k3d::vector3(1, 0, 1));
		return m_iron;
	}


private:
	k3d::imaterial* create_material(k3d::idocument& Document, const std::string& Name, const k3d::vector3 Color)
	{
		static k3d::idocument_plugin_factory* const material_factory = dynamic_cast<k3d::idocument_plugin_factory*>(k3d::plugin(k3d::classes::RenderManMaterial()));
		return_val_if_fail(material_factory, 0);

		k3d::iobject* const material_object = material_factory->create_plugin(Document);
		return_val_if_fail(material_object, 0);

		material_object->set_name(Name);
		k3d::set_property_value(*material_object, "color", Color);

		Document.objects().add_objects(k3d::iobject_collection::objects_t(1, material_object));

		return dynamic_cast<k3d::imaterial*>(material_object);
	}

	k3d::imaterial* m_carbon;
	k3d::imaterial* m_nitrogen;
	k3d::imaterial* m_oxygen;
	k3d::imaterial* m_hydrogen;
	k3d::imaterial* m_sulphur;
	k3d::imaterial* m_iron;
};
*/

#define MAX_QPATH	64
#define MD3_XYZ_SCALE 1.0/64.0

typedef struct {
	char	id[4];
	int		version;
	char	fileName[MAX_QPATH];
	int		flags;
	int		numFrames;
	int		numTags;
	int		numSurfaces;
	int		numSkins;
	int		offsetFrames;
	int		offsetTags;
	int		offsetSurfaces;
	int		offsetEnd;
} MD3Header;

typedef struct {
	float			minBounds[3];
	float			maxBounds[3];
	float			localOrigin[3];
	float			scale;
	char			name[16];
} Frame;

typedef struct {
	char			name[MAX_QPATH];
	float			origin[3];
	float			axis[3][3];
} Tag;

typedef struct {
	char	id[4];
	char	name[MAX_QPATH];
	int		flags;
	int		numFrames;
	int		numShaders;
	int		numVertexes;
	int		numTriangles;
	int		offsetTriangles;
	int		offsetShaders;
	int		offsetTextureST;
	int		offsetVertexes;
	int		offsetEnd;
} Surface;

typedef struct {
	char	name[MAX_QPATH];
	int		index;
} Shader;

typedef struct {
	int		index[3];
} Triangle;

typedef struct {
	short	xyz[3];
	unsigned char	normal[2];
} Vertex;

typedef struct {
	float	st[2];
} TexCoord;

k3dIPoint* CreatePoint(k3dIMesh* const Mesh, const k3d::vector3 Coords)
{
	// Sanity checks ...
	assert_warning(Mesh);

	k3dIPoint* const point = Mesh->CreatePoint();
	return_val_if_fail(point, 0);

	point->Location()->SetLocalXYZ(Coords);

	return point;
}

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

	// Lookup required plugin factories in advance ...
	k3d::idocument_plugin_factory* const mesh_factory =
		dynamic_cast<k3d::idocument_plugin_factory*>(k3d::plugin(k3d::classes::FrozenMesh()));

/*
	// Get ready to create materials as-needed
	material_factory materials;
*/

	// Keep track of objects as they're created ...
	k3d::iobject_collection::objects_t objects;

	MD3Header header;
	file.read(reinterpret_cast<char*>(&header), sizeof(header));

	if(strncmp(header.id, "IDP3", 4)) {
		std::cerr << __PRETTY_FUNCTION__ << ": not an MD3 file [" << FilePath.native_file_string() << "]" << std::endl;
		return_val_if_fail(0, false);
	}


	// Build normal lookup table
	static const int kMaxNormal = 256;
	k3d::vector3 normalTable[256][256];

	for(int i = 0 ; i != kMaxNormal ; ++i) {
		for(int j = 0 ; j != kMaxNormal ; ++j) {
			double alpha	= 2.0 * static_cast<double>(i) * M_PI / 255.0;
			double beta		= 2.0 * static_cast<double>(j) * M_PI / 255.0;
			normalTable[i][j] = k3d::vector3(cos(beta) * sin(alpha), sin(beta) * sin(alpha), cos(alpha));
		}
	}

	// Frames
	file.seekg(header.offsetFrames);
	for(int i = 0 ; i < header.numFrames ; ++i) {
		Frame frame;
		//printf("frame %d %s\nscale\t%8.3f\norigin\t%8.3f %8.3f %8.3f\n",i,
			//frame.name, frame.scale,
			//frame.localOrigin[0], frame.localOrigin[1], frame.localOrigin[2]);
		file.read(reinterpret_cast<char*>(&frame), sizeof(Frame));
	}

	// Tags
	file.seekg(header.offsetTags);
	std::vector<Tag> tags(header.numTags);
	for(unsigned int i = 0 ; i != tags.size() ; ++i) {
		Tag* tag = &tags[i];
		file.read(reinterpret_cast<char*>(tag), sizeof(Tag));
		printf("tag %d %s\n\t%8.3f %8.3f %8.3f rotation\n\t%8.3f %8.3f %8.3f\n\t%8.3f %8.3f %8.3f\n\t%8.3f %8.3f %8.3f translation\n", i, tag->name,
			tag->axis[0][0], tag->axis[0][1], tag->axis[0][2],
			tag->axis[1][0], tag->axis[1][1], tag->axis[1][2],
			tag->axis[2][0], tag->axis[2][1], tag->axis[2][2],
			tag->origin[0], tag->origin[1], tag->origin[2]);
		k3d::matrix3 m( k3d::vector3(tag->axis[0][0], tag->axis[0][1], tag->axis[0][2]),
						k3d::vector3(tag->axis[1][0], tag->axis[1][1], tag->axis[1][2]),
						k3d::vector3(tag->axis[2][0], tag->axis[2][1], tag->axis[2][2]));
		double x,y,z;
		m.Decompose(x, y, z);
		printf("decompose %f %f %f\n",x,y,z);
	}

	// Surfaces
	printf("numSurfaces %d\n",header.numSurfaces);
	file.seekg(header.offsetSurfaces);
	for(int i = 0 ; i < header.numSurfaces ; ++i) {

		// Create a mesh for the surface
		k3d::iobject* const object = mesh_factory->create_plugin(Document);
	    return_val_if_fail(object, false);

	    k3dIMesh* mesh = dynamic_cast<k3dIMesh*>(object);
	    return_val_if_fail(mesh, false);

		// Load tag matrix if there is only one tag
		if(header.numTags == 1) {
			Tag* tag = &tags[0];
			set_property_value(*mesh, "position", k3d::vector3(tag->origin[0], tag->origin[1], tag->origin[2]));
		}

		// Read surface header
		Surface surface;
		file.read(reinterpret_cast<char*>(&surface), sizeof(Surface));

		object->set_name(surface.name);
		Document.objects().add_objects(k3d::make_collection<k3d::iobject_collection::objects_t>(object));

		// Triangles
		std::vector<Triangle> triangles(surface.numTriangles);
		file.read(reinterpret_cast<char*>(&triangles[0]), sizeof(Triangle) * triangles.size());

		// Shaders
		std::vector<Shader> shaders(surface.numShaders);
		file.read(reinterpret_cast<char*>(&shaders[0]), sizeof(Shader) * shaders.size());

		// Texture Coordinates
		std::vector<TexCoord> texCoords(surface.numVertexes);
		file.read(reinterpret_cast<char*>(&texCoords[0]), sizeof(TexCoord) * texCoords.size());

		// Vertexes
		std::vector<Vertex> vertexes(surface.numVertexes);
		std::vector<k3dIPoint*> points;
		for(int frame = 0 ; frame < surface.numFrames ; ++frame)
			{
				if(frame == 0)
					{
						file.read(reinterpret_cast<char*>(&vertexes[0]), sizeof(Vertex) * vertexes.size());
					}
				for(int i = 0 ; i < surface.numVertexes ; ++i)
					{
						if(frame == 0)
							{
								k3d::vector3 v(
									vertexes[i].xyz[0] * MD3_XYZ_SCALE,
									vertexes[i].xyz[1] * MD3_XYZ_SCALE,
									vertexes[i].xyz[2] * MD3_XYZ_SCALE);
								k3dIPoint* const point = CreatePoint(mesh, v);
								points.push_back(point);
							}
						else
							{
								Vertex vertex;
								file.read(reinterpret_cast<char*>(&vertex), sizeof(Vertex));
							}
					}
			}

		// Build pathes for first frame,  for now read following frames
		// and discard them
		// Also set normals and texture coordinates
		for(int i = 0 ; i < surface.numTriangles ; ++i) {
			k3dIPath* const path = mesh->CreatePath();
			for(int j = 0 ; j < 3 ; ++j) {
				int vertex = triangles[i].index[j];
				k3dIPathPoint* const pathpoint = path->Points()->AddPoint(points[vertex]);
				k3dILocation* location = pathpoint->Location();

				unsigned char normalU = vertexes[vertex].normal[0];
				unsigned char normalV = vertexes[vertex].normal[1];
				//printf("%d - u %d v %d - ",vertex,normalU, normalV);
				k3d::vector3& normal = normalTable[normalU][normalV];
				//printf("%p %f %f %f\n",location,normal[0],normal[1],normal[2]);
				location->SetNormalXYZ(normal);
				//location->SetTextureXYZ(TextureVertex(texture_vertices, texture_index));
				//location->SetImplicitTextureXYZ(TextureVertex(texture_vertices, texture_index));
			}
		}
	}
	printf("\n");

	return true;
}

} // namespace

namespace libk3dgeometry
{

k3d::iplugin_factory& md3_reader_factory()
{
	return md3_reader_implementation::get_factory();
}

} // namespace libk3dgeometry


