#ifndef K3DSDK_MESH_TRIANGULATE_DETAIL_H
#define K3DSDK_MESH_TRIANGULATE_DETAIL_H

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

namespace detail
{

template<typename OutputFaceIterator, typename OutputEdgeIterator, typename OutputPointIterator>
class glu_triangulator_t
{
public:
	glu_triangulator_t(OutputFaceIterator OutputFaces, OutputEdgeIterator OutputEdges, OutputPointIterator OutputPoints) :
		tessellator(gluNewTess()),
		output_faces(OutputFaces),
		output_edges(OutputEdges),
		output_points(OutputPoints)
	{
		gluTessCallback(tessellator, GLU_TESS_BEGIN_DATA, reinterpret_cast<_GLUfuncptr>(&raw_begin));
		gluTessCallback(tessellator, GLU_TESS_VERTEX_DATA, reinterpret_cast<_GLUfuncptr>(&raw_vertex));
		gluTessCallback(tessellator, GLU_TESS_COMBINE_DATA, reinterpret_cast<_GLUfuncptr>(&raw_combine));
		gluTessCallback(tessellator, GLU_TESS_END_DATA, reinterpret_cast<_GLUfuncptr>(&raw_end));
		gluTessCallback(tessellator, GLU_TESS_ERROR_DATA, reinterpret_cast<_GLUfuncptr>(&raw_error));
	}

	~glu_triangulator_t()
	{
		gluDeleteTess(tessellator);
	}

	void operator()(face& Face)
	{
		save_uniform_data(Face);

		gluTessBeginPolygon(tessellator, this);

		// Handle the main contour for the face ...
		gluTessBeginContour(tessellator);
		for(split_edge* edge = Face.first_edge; edge && edge->face_clockwise; edge = edge->face_clockwise)
			{
				gluTessVertex(tessellator, edge->vertex->position, edge->vertex);
				if(edge->face_clockwise == Face.first_edge)
					break;
			}
		gluTessEndContour(tessellator);

		// Handle holes ...
		for(face::holes_t::const_iterator hole = Face.holes.begin(); hole != Face.holes.end(); ++hole)
			{
				gluTessBeginContour(tessellator);
				for(split_edge* edge = *hole; edge && edge->face_clockwise; edge = edge->face_clockwise)
					{
						gluTessVertex(tessellator, edge->vertex->position, edge->vertex);
						if(edge->face_clockwise == *hole)
							break;
					}
				gluTessEndContour(tessellator);
			}

		gluTessEndPolygon(tessellator);
	}

private:
	void begin(GLenum Mode)
	{
		mode = Mode;
		v0 = v1 = v2 = 0;
		flip_strip = false;
	}

	void vertex(void* VertexData)
	{
		if(!v0)
			{
				v0 = v1;
				v1 = v2;
			}
		v2 = reinterpret_cast<point*>(VertexData);

		if(!v0)
			return;

		std::vector<split_edge*> new_edges;
		switch(mode)
			{
				case GL_TRIANGLE_FAN:
					new_edges.push_back(new split_edge(v0));
					new_edges.push_back(new split_edge(v1));
					new_edges.push_back(new split_edge(v2));
					break;
				case GL_TRIANGLE_STRIP:
					new_edges.push_back(new split_edge(v0));
					new_edges.push_back(new split_edge(v1));
					new_edges.push_back(new split_edge(v2));
					if(flip_strip)
						std::reverse(new_edges.begin(), new_edges.end());
					break;
				case GL_TRIANGLES:
					new_edges.push_back(new split_edge(v0));
					new_edges.push_back(new split_edge(v1));
					new_edges.push_back(new split_edge(v2));
					break;
			}
		loop_edges(new_edges.begin(), new_edges.end());
		face* new_face = new face(new_edges.front());
		set_uniform_data(*new_face);
		*output_faces++ = new_face;
		std::copy(new_edges.begin(), new_edges.end(), output_edges);

		switch(mode)
			{
				case GL_TRIANGLE_FAN:
					v1 = v2;
					break;
				case GL_TRIANGLE_STRIP:
					v0 = v1;
					v1 = v2;
					flip_strip = !flip_strip;
					break;
				case GL_TRIANGLES:
					v0 = v1 = v2 = 0;
					break;
			}
	}

	void combine(GLdouble Coords[3], void* VertexData[4], GLfloat Weight[4], void** OutputData)
	{
		point* const new_point = new point(vector3(Coords[0], Coords[1], Coords[2]));
		*OutputData = new_point;
		*output_points++ = new_point;
	}

	void end()
	{
	}

	void error(GLenum ErrorNumber)
	{
		std::cerr << k3d::error << k3d_file_reference() << " " << gluErrorString(ErrorNumber) << std::endl;
	}

	static glu_triangulator_t* instance(void* UserData)
	{
		return reinterpret_cast<glu_triangulator_t*>(UserData);
	}

	static void raw_begin(GLenum Mode, void* UserData)
	{
		instance(UserData)->begin(Mode);
	}

	static void raw_vertex(void* VertexData, void* UserData)
	{
		instance(UserData)->vertex(VertexData);
	}

	static void raw_combine(GLdouble Coords[3], void* VertexData[4], GLfloat Weight[4], void** OutputData, void* UserData)
	{
		instance(UserData)->combine(Coords, VertexData, Weight, OutputData);
	}

	static void raw_end(void* UserData)
	{
		instance(UserData)->end();
	}

	static void raw_error(GLenum ErrorNumber, void* UserData)
	{
		instance(UserData)->error(ErrorNumber);
	}

	GLUtesselator* const tessellator;
	OutputFaceIterator output_faces;
	OutputEdgeIterator output_edges;
	OutputPointIterator output_points;

	GLenum mode;
	point* v0;
	point* v1;
	point* v2;
	bool flip_strip;

	// Save face parameters to transmit them to child triangles
	parameters_t uniform_data;

	void save_uniform_data(const face& Face)
	{
		uniform_data = Face.uniform_data;
	}

	void set_uniform_data(face& Face)
	{
		Face.uniform_data = uniform_data;
	}
};

} // namespace detail

#endif // K3DSDK_MESH_TRIANGULATE_DETAIL_H

