/* pygts - python package for the manipulation of triangulated surfaces
 *
 *   Copyright (C) 1999 Stéphane Popinet
 *   Copyright (C) 2009 Thomas J. Duck
 *   All rights reserved.
 *
 *   Thomas J. Duck <tom.duck@dal.ca>
 *   Department of Physics and Atmospheric Science,
 *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
 *
 * NOTICE
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the
 *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *   Boston, MA 02111-1307, USA.
 */


/*
 * Below are functions for cleaning up duplicated edges and faces on
 * a surface.  This file contains modified functions from the GTS 
 * distribution.
 */

#include "pygts.h"

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wmisleading-indentation"
#pragma GCC diagnostic ignored "-Wcast-function-type"


/**
 * Original documentation from GTS's vertex.c:
 *
 * gts_vertices_merge:
 * @vertices: a list of #GtsVertex.
 * @epsilon: half the size of the bounding box to consider for each vertex.
 * @check: function called for each pair of vertices about to be merged
 * or %NULL.
 *
 * For each vertex v in @vertices look if there are any vertex of
 * @vertices contained in a box centered on v of size 2*@epsilon. If
 * there are and if @check is not %NULL and returns %TRUE, replace
 * them with v (using gts_vertex_replace()), destroy them and remove
 * them from list.  This is done efficiently using Kd-Trees.
 *
 * Returns: the updated list of vertices.  
 */
/* This function is modified from the original in GTS in order to avoid 
 * deallocating any objects referenced by the live-objects table.  The 
 * approach is similar to what is used for replace() in vertex.c.
 */
GList* pygts_vertices_merge(GList* vertices, gdouble epsilon, gboolean (*check)(GtsVertex*, GtsVertex*))
{
	GPtrArray*   array;
	GList *      i, *next;
	GNode*       kdtree;
	GtsVertex*   v;
	GtsBBox*     bbox;
	GSList *     selected, *j;
	GtsVertex*   sv;
	PygtsObject* obj;
	PygtsVertex* vertex  = NULL;
	GSList *     parents = NULL, *ii, *cur;

	g_return_val_if_fail(vertices != NULL, 0);

	array = g_ptr_array_new();
	i     = vertices;
	while (i) {
		g_ptr_array_add(array, i->data);
		i = g_list_next(i);
	}
	kdtree = gts_kdtree_new(array, NULL);
	g_ptr_array_free(array, TRUE);

	i = vertices;
	while (i) {
		v = (GtsVertex*)i->data;
		if (!GTS_OBJECT(v)->reserved) { /* Do something only if v is active */

			/* build bounding box */
			bbox = gts_bbox_new(
			        gts_bbox_class(),
			        v,
			        GTS_POINT(v)->x - epsilon,
			        GTS_POINT(v)->y - epsilon,
			        GTS_POINT(v)->z - epsilon,
			        GTS_POINT(v)->x + epsilon,
			        GTS_POINT(v)->y + epsilon,
			        GTS_POINT(v)->z + epsilon);

			/* select vertices which are inside bbox using kdtree */
			j = selected = gts_kdtree_range(kdtree, bbox, NULL);
			while (j) {
				sv = (GtsVertex*)j->data;
				if (sv != v && !GTS_OBJECT(sv)->reserved && (!check || (*check)(sv, v))) {
					/* sv is not v and is active */
					if ((obj = (PygtsObject*)g_hash_table_lookup(obj_table, GTS_OBJECT(sv))) != NULL) {
						vertex = PYGTS_VERTEX(obj);
						/* Detach and save any parent segments */
						ii = sv->segments;
						while (ii != NULL) {
							cur = ii;
							ii  = g_slist_next(ii);
							if (PYGTS_IS_PARENT_SEGMENT(cur->data)) {
								sv->segments = g_slist_remove_link(sv->segments, cur);
								parents      = g_slist_prepend(parents, cur->data);
								g_slist_free_1(cur);
							}
						}
					}

					gts_vertex_replace(sv, v);
					GTS_OBJECT(sv)->reserved = sv; /* mark sv as inactive */

					/* Reattach the parent segments */
					if (vertex != NULL) {
						ii = parents;
						while (ii != NULL) {
							sv->segments = g_slist_prepend(sv->segments, ii->data);
							ii           = g_slist_next(ii);
						}
						g_slist_free(parents);
						parents = NULL;
					}
					vertex = NULL;
				}
				j = g_slist_next(j);
			}
			g_slist_free(selected);
			gts_object_destroy(GTS_OBJECT(bbox));
		}
		i = g_list_next(i);
	}
	gts_kdtree_destroy(kdtree);


	/* destroy inactive vertices and removes them from list */

	/* we want to control vertex destruction */
	gts_allow_floating_vertices = TRUE;

	i = vertices;
	while (i) {
		v    = (GtsVertex*)i->data;
		next = g_list_next(i);
		if (GTS_OBJECT(v)->reserved) { /* v is inactive */
			if (g_hash_table_lookup(obj_table, GTS_OBJECT(v)) == NULL) {
				gts_object_destroy(GTS_OBJECT(v));
			} else {
				GTS_OBJECT(v)->reserved = 0;
			}
			vertices = g_list_remove_link(vertices, i);
			g_list_free_1(i);
		}
		i = next;
	}
	gts_allow_floating_vertices = FALSE;

	return vertices;
}


static void build_list(gpointer data, GSList** list) { *list = g_slist_prepend(*list, data); }


static void build_list1(gpointer data, GList** list) { *list = g_list_prepend(*list, data); }


void pygts_vertex_cleanup(GtsSurface* s, gdouble threshold)
{
	GList* vertices = NULL;

	/* merge vertices which are close enough */
	/* build list of vertices */
	gts_surface_foreach_vertex(s, (GtsFunc)build_list1, &vertices);

	/* merge vertices: we MUST update the variable vertices because this function
     modifies the list (i.e. removes the merged vertices). */
	vertices = pygts_vertices_merge(vertices, threshold, NULL);

	/* free the list */
	g_list_free(vertices);
}


void pygts_edge_cleanup(GtsSurface* s)
{
	GSList*    edges = NULL;
	GSList *   i, *ii, *cur, *parents = NULL;
	PygtsEdge* edge;
	GtsEdge *  e, *duplicate;

	g_return_if_fail(s != NULL);

	/* build list of edges */
	gts_surface_foreach_edge(s, (GtsFunc)build_list, &edges);

	/* remove degenerate and duplicate edges.
     Note: we could use gts_edges_merge() to remove the duplicates and then
     remove the degenerate edges but it is more efficient to do everything 
     at once (and it's more pedagogical too ...) */

	/* We want to control manually the destruction of edges */
	gts_allow_floating_edges = TRUE;

	i = edges;
	while (i) {
		e = (GtsEdge*)i->data;
		if (GTS_SEGMENT(e)->v1 == GTS_SEGMENT(e)->v2) {
			/* edge is degenerate */
			if (!g_hash_table_lookup(obj_table, GTS_OBJECT(e))) {
				/* destroy e */
				gts_object_destroy(GTS_OBJECT(e));
			}
		} else {
			if ((duplicate = gts_edge_is_duplicate(e))) {
				/* Detach and save any parent triangles */
				if ((edge = PYGTS_EDGE(g_hash_table_lookup(obj_table, GTS_OBJECT(e)))) != NULL) {
					ii = e->triangles;
					while (ii != NULL) {
						cur = ii;
						ii  = g_slist_next(ii);
						if (PYGTS_IS_PARENT_TRIANGLE(cur->data)) {
							e->triangles = g_slist_remove_link(e->triangles, cur);
							parents      = g_slist_prepend(parents, cur->data);
							g_slist_free_1(cur);
						}
					}
				}

				/* replace e with its duplicate */
				gts_edge_replace(e, duplicate);

				/* Reattach the parent segments */
				if (edge != NULL) {
					ii = parents;
					while (ii != NULL) {
						e->triangles = g_slist_prepend(e->triangles, ii->data);
						ii           = g_slist_next(ii);
					}
					g_slist_free(parents);
					parents = NULL;
				}

				if (!g_hash_table_lookup(obj_table, GTS_OBJECT(e))) {
					/* destroy e */
					gts_object_destroy(GTS_OBJECT(e));
				}
			}
		}
		i = g_slist_next(i);
	}

	/* don't forget to reset to default */
	gts_allow_floating_edges = FALSE;

	/* free list of edges */
	g_slist_free(edges);
}


void pygts_face_cleanup(GtsSurface* s)
{
	GSList* triangles = NULL;
	GSList* i;

	g_return_if_fail(s != NULL);

	/* build list of triangles */
	gts_surface_foreach_face(s, (GtsFunc)build_list, &triangles);

	/* remove duplicate and degenerate triangles */
	i = triangles;
	while (i) {
		GtsTriangle* t = (GtsTriangle*)i->data;
		if (!gts_triangle_is_ok(t)) {
			/* destroy t, its edges (if not used by any other triangle)
	 and its corners (if not used by any other edge) */
			if (g_hash_table_lookup(obj_table, GTS_OBJECT(t)) == NULL) {
				gts_object_destroy(GTS_OBJECT(t));
			} else {
				gts_surface_remove_face(PYGTS_SURFACE_AS_GTS_SURFACE(s), GTS_FACE(t));
			}
		}
		i = g_slist_next(i);
	}

	/* free list of triangles */
	g_slist_free(triangles);
}


/* old main program (below) - retained as an example of how to use the
 * functions above.
 */

/* cleanup - using a given threshold merge vertices which are too close.
   Eliminate degenerate and duplicate edges.
   Eliminate duplicate triangles . */
/* static int  */
/* main (int argc, char * argv[]) */
/* { */
/*   GtsSurface * s, * m; */
/*   GList * vertices = NULL; */
/*   gboolean verbose = FALSE, sever = FALSE, boundary = FALSE; */
/*   gdouble threshold; */
/*   int c = 0; */
/*   GtsFile * fp; */
/*   gboolean (* check) (GtsVertex *, GtsVertex *) = NULL; */

/*   if (!setlocale (LC_ALL, "POSIX")) */
/*     g_warning ("cannot set locale to POSIX"); */

/*   s = gts_surface_new (gts_surface_class (), */
/* 		       gts_face_class (), */
/* 		       gts_edge_class (), */
/* 		       gts_vertex_class ()); */

/*   /\* parse options using getopt *\/ */
/*   while (c != EOF) { */
/* #ifdef HAVE_GETOPT_LONG */
/*     static struct option long_options[] = { */
/*       {"2D", no_argument, NULL, 'c'}, */
/*       {"boundary", no_argument, NULL, 'b'}, */
/*       {"merge", required_argument, NULL, 'm'}, */
/*       {"sever", no_argument, NULL, 's'}, */
/*       {"help", no_argument, NULL, 'h'}, */
/*       {"verbose", no_argument, NULL, 'v'}, */
/*       { NULL } */
/*     }; */
/*     int option_index = 0; */
/*     switch ((c = getopt_long (argc, argv, "hvsm:bc", */
/* 			      long_options, &option_index))) { */
/* #else /\* not HAVE_GETOPT_LONG *\/ */
/*     switch ((c = getopt (argc, argv, "hvsm:bc"))) { */
/* #endif /\* not HAVE_GETOPT_LONG *\/ */
/*     case 'c': /\* 2D *\/ */
/*       check = check_boundaries; */
/*       break; */
/*     case 'b': /\* boundary *\/ */
/*       boundary = TRUE; */
/*       break; */
/*     case 's': /\* sever *\/ */
/*       sever = TRUE; */
/*       break; */
/*     case 'm': { /\* merge *\/ */
/*       FILE * fptr = fopen (optarg, "rt"); */
/*       GtsFile * fp; */

/*       if (fptr == NULL) { */
/* 	fprintf (stderr, "cleanup: cannot open file `%s' for merging\n", */
/* 		 optarg); */
/* 	return 1; /\* failure *\/ */
/*       } */
/*       m = gts_surface_new (gts_surface_class (), */
/* 			   gts_face_class (), */
/* 			   gts_edge_class (), */
/* 			   s->vertex_class); */
/*       fp = gts_file_new (fptr); */
/*       if (gts_surface_read (m, fp)) { */
/* 	fprintf (stderr, "cleanup: file `%s' is not a valid GTS file\n", */
/* 		 optarg); */
/* 	fprintf (stderr, "%s:%d:%d: %s\n", */
/* 		 optarg, fp->line, fp->pos, fp->error); */
/* 	return 1; /\* failure *\/ */
/*       } */
/*       gts_file_destroy (fp); */
/*       fclose (fptr); */
/*       gts_surface_merge (s, m); */
/*       gts_object_destroy (GTS_OBJECT (m)); */
/*       break; */
/*     } */
/*     case 'v': /\* verbose *\/ */
/*       verbose = TRUE; */
/*       break; */
/*     case 'h': /\* help *\/ */
/*       fprintf (stderr, */
/* 	       "Usage: cleanup [OPTION] THRESHOLD < FILE\n" */
/* 	       "Merge vertices of the GTS surface FILE if they are closer than THRESHOLD,\n" */
/* 	       "eliminate degenerate, duplicate edges and duplicate triangles.\n" */
/* 	       "\n" */
/* 	       "  -c      --2D        2D boundary merging\n" */
/* 	       "  -b      --boundary  only consider boundary vertices for merging\n" */
/* 	       "  -s      --sever     sever \"contact\" vertices\n" */
/* 	       "  -m FILE --merge     merge surface FILE\n" */
/* 	       "  -v      --verbose   print statistics about the surface\n" */
/* 	       "  -h      --help      display this help and exit\n" */
/* 	       "\n" */
/* 	       "Report bugs to %s\n", */
/* 	       GTS_MAINTAINER); */
/*       return 0; /\* success *\/ */
/*       break; */
/*     case '?': /\* wrong options *\/ */
/*       fprintf (stderr, "Try `cleanup --help' for more information.\n"); */
/*       return 1; /\* failure *\/ */
/*     } */
/*   } */

/*   if (optind >= argc) { /\* missing threshold *\/ */
/*     fprintf (stderr, */
/* 	     "cleanup: missing THRESHOLD\n" */
/* 	     "Try `cleanup --help' for more information.\n"); */
/*     return 1; /\* failure *\/ */
/*   } */

/*   threshold = atof (argv[optind]); */

/*   if (threshold < 0.0) { /\* threshold must be positive *\/ */
/*      fprintf (stderr, */
/* 	     "cleanup: THRESHOLD must be >= 0.0\n" */
/* 	     "Try `cleanup --help' for more information.\n"); */
/*     return 1; /\* failure *\/ */
/*   } */

/*   /\* read surface in *\/ */
/*   m = gts_surface_new (gts_surface_class (), */
/* 		       gts_face_class (), */
/* 		       gts_edge_class (), */
/* 		       s->vertex_class); */
/*   fp = gts_file_new (stdin); */
/*   if (gts_surface_read (m, fp)) { */
/*     fputs ("cleanup: file on standard input is not a valid GTS file\n", */
/* 	   stderr); */
/*     fprintf (stderr, "stdin:%d:%d: %s\n", fp->line, fp->pos, fp->error); */
/*     return 1; /\* failure *\/ */
/*   } */
/*   gts_surface_merge (s, m); */
/*   gts_object_destroy (GTS_OBJECT (m)); */

/*   /\* if verbose on print stats *\/ */
/*   if (verbose) */
/*     gts_surface_print_stats (s, stderr); */

/*   /\* merge vertices which are close enough *\/ */
/*   /\* build list of vertices *\/ */
/*   gts_surface_foreach_vertex (s, boundary ? (GtsFunc) build_list2 : (GtsFunc) build_list1, */
/* 			      &vertices); */
/*   /\* merge vertices: we MUST update the variable vertices because this function */
/*      modifies the list (i.e. removes the merged vertices). *\/ */
/*   vertices = gts_vertices_merge (vertices, threshold, check); */

/*   /\* free the list *\/ */
/*   g_list_free (vertices); */

/*   /\* eliminate degenerate and duplicate edges *\/ */
/*   edge_cleanup (s); */
/*   /\* eliminate duplicate triangles *\/ */
/*   triangle_cleanup (s); */

/*   if (sever) */
/*     gts_surface_foreach_vertex (s, (GtsFunc) vertex_cleanup, NULL); */

/*   /\* if verbose on print stats *\/ */
/*   if (verbose) { */
/*     GtsBBox * bb = gts_bbox_surface (gts_bbox_class (), s); */
/*     gts_surface_print_stats (s, stderr); */
/*     fprintf (stderr, "# Bounding box: [%g,%g,%g] [%g,%g,%g]\n", */
/* 	     bb->x1, bb->y1, bb->z1, */
/* 	     bb->x2, bb->y2, bb->z2); */
/*   } */

/*   /\* write surface *\/ */
/*   gts_surface_write (s, stdout); */

/*   return 0; /\* success *\/ */
/* } */

#pragma GCC diagnostic pop
