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

#include "subdiv_algorithms.h"
#include <map>
#include <set>
#include <k3dsdk/result.h>

#define DEBUG_MESSAGE(Message)
//#define DEBUG_MESSAGE(Message) Message

namespace subdiv {

// Helper class to associate a vertex to an edge.
// Used to i.e. associate face vertices to edges
class vertex_edge_container {
	typedef std::map<k3d::split_edge*, k3d::point*> vertex_map;
	typedef vertex_map::value_type vertex_map_t;
	typedef vertex_map::iterator vertex_map_it;
public:
	// add a new edge-vertex pair, if the edge is not registered already
	void add(k3d::split_edge* edge, k3d::point* vertex) {
		if (!((m_vertices.insert(vertex_map_t(edge, vertex))).second)) {
			DEBUG_MESSAGE(std::cerr << error << "Edge already has a vertex associated" << std::endl;)
		}
	}
	// return the vertex associated with edge, if there is one.
	k3d::point* get(k3d::split_edge* edge) {
		vertex_map_it it = m_vertices.find(edge);
		if (it != m_vertices.end()) {
			return it->second;
		}
		return 0;
	}
private:
	vertex_map m_vertices;
};

// Helper class to associate 2 edges
class edge_container {
	typedef std::map<k3d::split_edge*, k3d::split_edge*> edge_map;
	typedef edge_map::value_type edge_map_t;
	typedef edge_map::iterator edge_map_it;

public:
	// add a new pair
	void add(k3d::split_edge* key_edge, k3d::split_edge* edge) {
		if (!((m_edges.insert(edge_map_t(key_edge, edge))).second)) {
			DEBUG_MESSAGE(std::cerr << error << "Edge already has an edge associated" << std::endl;)
		}
	}
	// return the edge associated to <edge>
	k3d::split_edge* get(k3d::split_edge* edge) {
		edge_map_it it = m_edges.find(edge);
		if (it != m_edges.end()) {
			return it->second;
		}
		return 0;
	}
private:
	edge_map m_edges;
};

// Extra data stored by the catmull-clark algorithm
class catmull_clark_data {
	typedef std::set<k3d::point*> point_set;
	typedef std::set<k3d::point*>::value_type point_set_t;
	typedef std::set<k3d::point*>::iterator point_set_it;
public:
	catmull_clark_data(k3d::polyhedron* polyhedron, k3d::mesh::points_t* output_points, bool ignore_selection) : m_ignore_selection(ignore_selection) {
		m_polyhedron = polyhedron;
		m_original_faces = m_polyhedron->faces;
		m_original_edges = m_polyhedron->edges;
		m_next_face = m_original_faces.begin();
		m_output_points = output_points;
	}
	// Return the companion of an edge
	k3d::split_edge* companion(k3d::split_edge* edge) {
		// return the old companion, if there is one
		k3d::split_edge* old_companion = m_saved_companions.get(edge);
		if (old_companion) {
			return old_companion;
		}
		// otherwise return the real companion
		return edge->companion;
	}
	// Return the face_clockwise of an edge
	k3d::split_edge* clockwise(k3d::split_edge* edge) {
		// return the old clockwise, if there is one
		k3d::split_edge* old_clockwise = m_saved_clockwise.get(edge);
		if (old_clockwise) {
			return old_clockwise;
		}
		// otherwise return the real face_clockwise
		return edge->face_clockwise;
	}
	
	// helper function to return the face vertex to the right of an edge
	k3d::point* face_vertex(k3d::split_edge* edge) {
		// If this face vertex was calculated earlier, return the pointer to it
		return_val_if_fail(edge,0);
		k3d::point* existing_face_vertex = m_face_vertices.get(edge);
		if (existing_face_vertex) {
			return existing_face_vertex;
		}
		// Else calculate the new face vertex and add it to the list of calculated vertices.
		k3d::point* vf = new k3d::point(edge->vertex->position);
		int n=1; // number of vertices in the face
		k3d::split_edge* ej = edge->face_clockwise;
		while (ej != edge) {
			// associate the new face vertex with all surrounding edges
			m_face_vertices.add(ej, vf);
			vf->position += ej->vertex->position;
			ej = ej->face_clockwise;
			n++;
		}
		m_face_vertices.add(edge, vf);
		vf->position /= n;
		m_output_points->push_back(vf);
		vf->selected = !m_ignore_selection;
		return vf;
	}
	
	// Return the edge vertex associated with a given edge and face vertex		
	k3d::point* edge_vertex(k3d::split_edge* edge, k3d::point* vf) {
		// If this edge vertex exists, return a pointer to it
		k3d::point* existing_edge_vertex = m_edge_vertices.get(edge);
		if (existing_edge_vertex) {
			return existing_edge_vertex;
		}
		// Else calculate a new one
		k3d::split_edge* edge_companion = companion(edge);
		if (!(edge_companion) || (!m_ignore_selection && edge_companion->selected == false)) { // if edge is a boundary edge or at the edge of the selection
			k3d::point* ev = new k3d::point((edge->vertex->position
					+ edge->face_clockwise->vertex->position)/2);
			m_output_points->push_back(ev);
			if (edge_companion) {
				m_edge_vertices.add(edge_companion, ev);
			}
			ev->selected = !m_ignore_selection;
			return ev;
		}
		// For a normal edge:
		k3d::point* ev = new k3d::point((edge->vertex->position
												+ edge->face_clockwise->vertex->position
												+ vf->position
												+ face_vertex(edge_companion)->position)/4);
		// The companion edge has the same edge vertex, so store it:
		m_edge_vertices.add(edge_companion, ev);
		m_output_points->push_back(ev);
		ev->selected = !m_ignore_selection;
		return ev;
	}
	// return the next edge to be treated, or 0 if there is none
	k3d::split_edge* next_edge() {
		if (m_next_face != m_original_faces.end()) {
			while (!m_ignore_selection) {
				if ((*m_next_face)->selected) {
					return (*(m_next_face++))->first_edge;
				} else {
					++m_next_face;
				}
				if (m_next_face == m_original_faces.end())
					return 0;
			}
			return (*(m_next_face++))->first_edge;
		}
		return 0;
	}
	// Join 2 edges, and store the old companion of the first edge
	void save_join(k3d::split_edge* first, k3d::split_edge* second) {
		if (first == 0 || second == 0 || first->companion == 0) {
			return;
		}
		m_saved_companions.add(first, first->companion);
		k3d::join_edges(*first, *second);
	}
	// set the clockwise on an edge, saving the old one
	void save_clockwise(k3d::split_edge* edge, k3d::split_edge* clockwise) {
		if (edge == 0 || clockwise == 0 || edge->face_clockwise == 0) {
			return;
		}
		m_saved_clockwise.add(edge, edge->face_clockwise);
		edge->face_clockwise = clockwise;
	}

	// reposition a point
	void reposition_point(k3d::split_edge* edge) {
		int n = 1; //valence of the vertex
		if (!(edge->companion)) { // boundary edge
			k3d::split_edge* ecl = edge->face_clockwise->face_clockwise->face_clockwise;
			if (!m_ignore_selection && ecl->selected == false)
				return;
			if (!m_ignore_selection && edge->face_clockwise->selected == false)
				return;
			k3d::vector3 v = 0.25*edge->face_clockwise->vertex->position;
			if (!(ecl->companion)) {
				v += 0.25*ecl->vertex->position;
			} else {
				v += 0.25*ecl->companion->face_clockwise->face_clockwise->face_clockwise->vertex->position;
			}
			edge->vertex->position = v + 0.5*edge->vertex->position;
			return;
		}
		k3d::split_edge* ecl1 = edge->face_clockwise;
		if (!m_ignore_selection && ecl1->selected == false)
			return;
		k3d::split_edge* ecl2 = ecl1->face_clockwise;
		if (!m_ignore_selection && ecl2->selected == false)
			return;
		k3d::split_edge* ecl3 = ecl2->face_clockwise;
		if (!m_ignore_selection && ecl3->selected == false)
			return;
		k3d::vector3 v = ecl1->vertex->position; // first edge vertex
		v += ecl2->vertex->position; // first face vertex
		k3d::split_edge* ei = ecl3->companion;
		if (!ei) {
			// we started on the wrong edge around a valence 3 vertex
			reposition_point(edge->companion->face_clockwise);
			return;
		}
		while (ei != edge) {
			n++;
			ecl1 = ei->face_clockwise;
			if (!m_ignore_selection && ecl1->selected == false)
				return;
			ecl2 = ecl1->face_clockwise;
			if (!m_ignore_selection && ecl2->selected == false)
				return;
			ecl3 = ecl2->face_clockwise;
			if (!m_ignore_selection && ecl3->selected == false)
				return;
			v += ecl1->vertex->position;
			v += ecl2->vertex->position;
			ei = ecl3->companion;
			if (!ei) {
				// we started on the wrong edge around a valence 3 vertex
				reposition_point(edge->companion->face_clockwise);
				return;
			}
		}
		edge->vertex->position = (n-2.0)/n * edge->vertex->position + 1.0/(n*n) * v;
	}
		
	// reposition all the original points
	void reposition_points() {
		point_set visited_points;
		k3d::polyhedron::edges_t::iterator it = m_original_edges.begin();
		while (it != m_original_edges.end()) {
			if (*it && (m_ignore_selection || (!m_ignore_selection && (*it)->selected == true)) && visited_points.find((*it)->vertex) == visited_points.end()) {
				reposition_point(*it);
				visited_points.insert(point_set_t((*it)->vertex));
			}
			++it;
		}
	}
private:
	vertex_edge_container m_face_vertices;
	vertex_edge_container m_edge_vertices;
	edge_container m_saved_companions;
	edge_container m_saved_clockwise;
	k3d::polyhedron* m_polyhedron;
	k3d::polyhedron::faces_t m_original_faces;
	k3d::polyhedron::faces_t::iterator m_next_face;
	k3d::mesh::points_t* m_output_points;
	k3d::polyhedron::edges_t m_original_edges;
	const bool m_ignore_selection;
};

void catmull_clark(const unsigned long Levels, const k3d::mesh& Input, k3d::mesh& Output, const bool ignore_selection) {
	// clear the output
	Output.polyhedra.clear();
	k3d::deep_copy(Input, Output);
	for (k3d::mesh::polyhedra_t::iterator it = Output.polyhedra.begin(); it != Output.polyhedra.end(); ++it) {
		if ((*it)->type == k3d::polyhedron::CATMULL_CLARK_SUBDIVISION_MESH || !ignore_selection) {
			k3d::polyhedron* output_polyhedron = *(it);
			for (unsigned long i = 1; i <= Levels; i++) {
				assert(output_polyhedron);
				return_if_fail(k3d::is_valid(*output_polyhedron));
				// extra containers and functions we need:
				catmull_clark_data data(output_polyhedron, &(Output.points), ignore_selection);
				DEBUG_MESSAGE(std::cerr << debug << "Going to SDS level " << i << std::endl;)
				// clear the faces of the output:
				if (ignore_selection) {
					output_polyhedron->faces.clear();
				} else {
					k3d::polyhedron::faces_t original_faces = output_polyhedron->faces;
					output_polyhedron->faces.clear();
					for (k3d::polyhedron::faces_t::iterator f = original_faces.begin(); f != original_faces.end(); ++f) {
						if (!((*f)->selected)) {
							output_polyhedron->faces.push_back(*f);
						}
					}
				}
				k3d::split_edge* e_start = 0;
				k3d::split_edge* ei = 0;
				k3d::split_edge* ei_old_clockwise = 0;
				k3d::split_edge* ev_edge = 0;
				k3d::split_edge* ev_center = 0;
				k3d::split_edge* ev_edge_last = 0;
				k3d::split_edge* vf_center = 0;
				k3d::split_edge* vf_center_last = 0;
				k3d::split_edge* vf_center_first = 0;
				k3d::point* vf = 0;
				k3d::point* ev_last = 0;
				k3d::point* ev = 0;
				k3d::point* ev_first = 0;
				// loop through all original faces:
				e_start = data.next_edge();
				while (e_start) {
					// face loop initialisation:
					ei = data.clockwise(e_start);
					vf = data.face_vertex(e_start);
					ev_last = data.edge_vertex(e_start, vf);
					// fix the neighbour face, to allow local subdivision
					k3d::split_edge* e_start_companion = data.companion(e_start);
					if (e_start_companion && (ignore_selection || (!ignore_selection && e_start_companion->selected == true))) {
						data.face_vertex(e_start_companion);
					}
					if (!(e_start->face_clockwise->vertex == ev_last)) {
						ev_edge_last = new k3d::split_edge(ev_last, ei, 0);
						ev_edge_last->selected = !ignore_selection;
						output_polyhedron->edges.push_back(ev_edge_last);
						data.save_join(e_start_companion, ev_edge_last);
						if (e_start_companion) {
							k3d::split_edge* ev_comp_edge = new k3d::split_edge(ev_last, (e_start_companion)->face_clockwise);
							ev_comp_edge->selected = (!ignore_selection && e_start_companion->selected);
							output_polyhedron->edges.push_back(ev_comp_edge);
							data.save_clockwise(e_start_companion,ev_comp_edge);
							data.save_join(e_start, ev_comp_edge);
						}
					} else {
						ev_edge_last = e_start->face_clockwise;
					}
					vf_center = new k3d::split_edge(vf, ev_edge_last, 0);
					vf_center->selected = !ignore_selection;
					output_polyhedron->edges.push_back(vf_center);
					vf_center_first = vf_center;
					vf_center_last = vf_center;
					ev_first = ev_last;
					// the loop for one face:
					while (ei != e_start) {
						ei_old_clockwise = data.clockwise(ei);
						ev = data.edge_vertex(ei, vf);
						// fix the neighbour face, to allow local subdivision
						k3d::split_edge* ei_companion = data.companion(ei);
						if (ei_companion && (ignore_selection || (!ignore_selection && ei_companion->selected == true))) {
							data.face_vertex(ei_companion);
						}
						if (!(ei->face_clockwise->vertex == ev)) {
							ev_edge = new k3d::split_edge(ev, ei->face_clockwise, 0);
							ev_edge->selected = !ignore_selection;
							output_polyhedron->edges.push_back(ev_edge);
							data.save_join(ei_companion, ev_edge);
							if (ei_companion) {
								k3d::split_edge* ev_comp_edge = new k3d::split_edge(ev, ei_companion->face_clockwise);
								ev_comp_edge->selected = (!ignore_selection && ei_companion->selected);
								output_polyhedron->edges.push_back(ev_comp_edge);
								data.save_clockwise(ei_companion, ev_comp_edge);
								data.save_join(ei, ev_comp_edge);
							}
						} else {
							ev_edge = ei->face_clockwise;
						}
						vf_center = new k3d::split_edge(vf, ev_edge, 0);
						vf_center->selected = !ignore_selection;
						output_polyhedron->edges.push_back(vf_center);
						ev_center = new k3d::split_edge(ev, vf_center_last, 0);
						ev_center->selected = !ignore_selection;
						output_polyhedron->edges.push_back(ev_center);
						k3d::join_edges(*vf_center, *ev_center);
						ei->face_clockwise = ev_center;
						vf_center_last = vf_center;
						output_polyhedron->faces.push_back(new k3d::face(ev_center));
						output_polyhedron->faces.back()->selected = !ignore_selection;
						ei = ei_old_clockwise;
					}
					// finish up the loose edges
					ev_center = new k3d::split_edge(ev_first, vf_center_last, 0);
					ev_center->selected = !ignore_selection;
					output_polyhedron->edges.push_back(ev_center);
					e_start->face_clockwise = ev_center;
					k3d::join_edges(*vf_center_first, *ev_center);
					output_polyhedron->faces.push_back(new k3d::face(ev_center));
					output_polyhedron->faces.back()->selected = !ignore_selection;
					e_start = data.next_edge();
				}
				data.reposition_points();
			}
			return_if_fail(k3d::is_valid(*output_polyhedron));
		}
	}
}
} //end namespace subdiv
