/* GTS - Library for the manipulation of triangulated surfaces
 * Copyright (C) 1999 Stéphane Popinet
 *
 * 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.
 */

#include <stdlib.h>
#include <locale.h>
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#ifdef HAVE_GETOPT_H
#  include <getopt.h>
#endif /* HAVE_GETOPT_H */
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include "gts.h"

/* the file format is the classic GTS file format but only the vertex and 
 * edge sections are read. */
static guint read_list (GPtrArray * vertices,
			GtsFifo * constraints,
			GtsEdgeClass * edge_class,
			FILE * fptr)
{
  guint nv, ne, nt, i;
  guint line = 1;

  g_return_val_if_fail (vertices != NULL, 1);
  g_return_val_if_fail (constraints != NULL, 1);
  g_return_val_if_fail (edge_class != NULL, 1);
  g_return_val_if_fail (fptr != NULL, 1);

  if (fscanf (fptr, "%u %u %u", &nv, &ne, &nt) != 3)
    return line;
  line++;

  g_ptr_array_set_size (vertices, nv);
  i = 1;
  while (i <= nv) {
    gdouble x, y, z;

    if (fscanf (fptr, "%lf %lf %lf", &x, &y, &z) != 3)
      return line;
    line++;

    g_ptr_array_index (vertices, i++ - 1) = 
      gts_vertex_new (gts_vertex_class (), x, y, z);
  }
  
  i = 1;
  while (i <= ne) {
    guint iv1, iv2;

    if (fscanf (fptr, "%u %u", &iv1, &iv2) != 2 ||
	iv1 <= 0 || iv1 > nv || iv2 <= 0 || iv2 > nv)
      return line;
    line++;

    gts_fifo_push (constraints, 
		   gts_edge_new (edge_class,
				 g_ptr_array_index (vertices, iv1 - 1),
				 g_ptr_array_index (vertices, iv2 - 1)));
    i++;
  }

  return 0;
}

static gdouble triangle_cost (GtsTriangle * t, gpointer * data)
{
  gdouble * min_quality = data[0];
  gdouble * max_area = data[1];
  gdouble quality = gts_triangle_quality (t);
  gdouble area = gts_triangle_area (t);
  
  if (quality < *min_quality || area > *max_area)
    return quality;
  return 0.;
}

static gboolean triangle_is_hole (GtsTriangle * t)
{
  GtsEdge * e1, * e2, * e3;
  GtsVertex * v1, * v2, * v3;

  gts_triangle_vertices_edges (t, NULL, &v1, &v2, &v3, &e1, &e2, &e3);

  if ((GTS_IS_CONSTRAINT (e1) && GTS_SEGMENT (e1)->v1 != v1) ||
      (GTS_IS_CONSTRAINT (e2) && GTS_SEGMENT (e2)->v1 != v2) ||
      (GTS_IS_CONSTRAINT (e3) && GTS_SEGMENT (e3)->v1 != v3))
    return TRUE;
  return FALSE;
}

static guint delaunay_remove_holes (GtsSurface * surface)
{
  g_return_val_if_fail (surface != NULL, 0);

  return gts_surface_foreach_face_remove (surface, 
					  (GtsFunc) triangle_is_hole, NULL);
}

static void gts_constraint_split (GtsConstraint * c, 
				  GtsSurface * s,
				  GtsFifo * fifo)
{
  GSList * i;
  GtsVertex * v1, * v2;
  GtsEdge * e;

  g_return_if_fail (c != NULL);
  g_return_if_fail (s != NULL);

  v1 = GTS_SEGMENT (c)->v1;
  v2 = GTS_SEGMENT (c)->v2;
  e = GTS_EDGE (c);

  i = e->triangles;
  while (i) {
    GtsFace * f = i->data;
    if (GTS_IS_FACE (f) && gts_face_has_parent_surface (f, s)) {
      GtsVertex * v = gts_triangle_vertex_opposite (GTS_TRIANGLE (f), e);
      if (gts_point_orientation (GTS_POINT (v1), 
				 GTS_POINT (v2), 
				 GTS_POINT (v)) == 0.) {
	GSList * j = e->triangles;
	GtsFace * f1 = NULL;
	GtsEdge * e1, * e2;

	/* replaces edges with constraints */
	gts_triangle_vertices_edges (GTS_TRIANGLE (f), e,
				     &v1, &v2, &v, &e, &e1, &e2);
	if (!GTS_IS_CONSTRAINT (e1)) {
	  GtsEdge * ne1 = 
	    gts_edge_new (GTS_EDGE_CLASS (GTS_OBJECT (c)->klass), v2, v);
	  gts_edge_replace (e1, ne1);
	  gts_object_destroy (GTS_OBJECT (e1));
	  e1 = ne1;
	  if (fifo) gts_fifo_push (fifo, e1);
	}
	if (!GTS_IS_CONSTRAINT (e2)) {
	  GtsEdge * ne2 = 
	    gts_edge_new (GTS_EDGE_CLASS (GTS_OBJECT (c)->klass), v, v1);
	  gts_edge_replace (e2, ne2);
	  gts_object_destroy (GTS_OBJECT (e2));
	  e2 = ne2;
	  if (fifo) gts_fifo_push (fifo, e2);
	}

	/* look for face opposite */
	while (j && !f1) {
	  if (GTS_IS_FACE (j->data) && 
	      gts_face_has_parent_surface (j->data, s))
	    f1 = j->data;
	  j = j->next;
	}
	if (f1) { /* c is not a boundary of s */
	  GtsEdge * e3, * e4, * e5;
	  GtsVertex * v3;
	  gts_triangle_vertices_edges (GTS_TRIANGLE (f1), e,
				       &v1, &v2, &v3, &e, &e3, &e4);
	  e5 = gts_edge_new (s->edge_class, v, v3);
	  gts_surface_add_face (s, gts_face_new (s->face_class, e5, e2, e3));
	  gts_surface_add_face (s, gts_face_new (s->face_class, e5, e4, e1));
	  gts_object_destroy (GTS_OBJECT (f1));
	}
	gts_object_destroy (GTS_OBJECT (f));
	return;
      }
    }
    i = i->next;
  }
}

static void add_constraint (GtsConstraint * c, GtsSurface * s)
{
  GSList * list = gts_delaunay_add_constraint (s, c);
  if (list) {
    g_warning ("constraint conflicted with %d other constraints", g_slist_length (list));
    g_slist_free (list);
  }
}

static void split_constraint (GtsConstraint * c, gpointer * data)
{
  GtsSurface * s = data[0];
  GtsFifo * fifo = data[1];

  gts_constraint_split (c, s, fifo);
}

static void shuffle_array (GPtrArray * a)
{
  guint i;

  for (i = 0; i < a->len; i++) {
    guint j = (gdouble) rand ()*(a->len - 1)/(gdouble) RAND_MAX;
    guint k = (gdouble) rand ()*(a->len - 1)/(gdouble) RAND_MAX;
    gpointer tmp;

    if (j >= a->len) j = a->len - 1;
    if (k >= a->len) k = a->len - 1;

    tmp = g_ptr_array_index (a, j);
    g_ptr_array_index (a, j) = g_ptr_array_index (a, k);
    g_ptr_array_index (a, k) = tmp;
  }
}

static double slope (GtsVertex * v1, GtsVertex * v2, GtsVertex * v3)
{
  GtsPoint * p1 = GTS_POINT (v1);
  GtsPoint * p2 = GTS_POINT (v2);
  GtsPoint * p3 = GTS_POINT (v3);

  double x1 = p2->x - p1->x;
  double y1 = p2->y - p1->y;
  double z1 = p2->z - p1->z;

  double x2 = p3->x - p1->x;
  double y2 = p3->y - p1->y;
  double z2 = p3->z - p1->z;

  double nx = y1*z2 - z1*y2;
  double ny = z1*x2 - x1*z2;
  double nz = x1*y2 - y1*x2;

  return 1. - nz*nz/(nx*nx + ny*ny + nz*nz);
}

/* swap edge if it maximises the minimum slope */
static gboolean edge_slope_swap (GtsEdge * e, GtsSurface * s)
{
  if (!GTS_IS_CONSTRAINT (e)) {
    GtsTriangle * t1 = NULL, * t2 = NULL;
    GSList * i = e->triangles;
    while (i) {
      if (GTS_IS_FACE (i->data) && gts_face_has_parent_surface (i->data, s)) {
	if (!t1)
	  t1 = i->data;
	else if (!t2)
	  t2 = i->data;
	else
	  g_return_val_if_fail (gts_edge_face_number (e, s) == 2, FALSE);
      }
      i = i->next;
    }
    if (!t1 || !t2)
      return FALSE;
    GtsVertex * v1, * v2, * v3, * v4, * v5, * v6;
    GtsEdge * e1, * e2, * e3, * e4;
    gts_triangle_vertices_edges (t1, e, &v1, &v2, &v3, &e, &e1, &e2);
    gts_triangle_vertices_edges (t2, e, &v4, &v5, &v6, &e, &e3, &e4);
    g_assert (v2 == v4 && v1 == v5);
    if (gts_point_orientation (GTS_POINT (v3), GTS_POINT (v6), GTS_POINT (v2)) > 0. &&
	gts_point_orientation (GTS_POINT (v6), GTS_POINT (v3), GTS_POINT (v1)) > 0.) {
      /* edge swap is possible */
      double slope1 = slope (v1, v2, v3), slope2 = slope (v4, v5, v6);
      double slopemin1 = MIN (slope1, slope2);
      slope1 = slope (v3, v6, v2); slope2 = slope (v6, v3, v1);
      double slopemin2 = MIN (slope1, slope2);
      if (slopemin2 > slopemin1) {
	/* edge swap */
	GtsSegment * v3v6 = gts_vertices_are_connected (v3, v6);
	if (!GTS_IS_EDGE (v3v6))
	  v3v6 = GTS_SEGMENT (gts_edge_new (s->edge_class, v3, v6));
	GtsFace * f = gts_face_new (s->face_class, e1, GTS_EDGE (v3v6), e4);
	GtsTriangle * t;
	if ((t = gts_triangle_is_duplicate (GTS_TRIANGLE (f))) &&
	    GTS_IS_FACE (t)) {
	  gts_object_destroy (GTS_OBJECT (f));
	  f = GTS_FACE (t);
	}
	gts_surface_add_face (s, f);
	
	f = gts_face_new (s->face_class, GTS_EDGE (v3v6), e2, e3);
	if ((t = gts_triangle_is_duplicate (GTS_TRIANGLE (f))) &&
	    GTS_IS_FACE (t)) {
	  gts_object_destroy (GTS_OBJECT (f));
	  f = GTS_FACE (t);
	}
	gts_surface_add_face (s, f);

	gts_surface_remove_face (s, GTS_FACE (t1));
	gts_surface_remove_face (s, GTS_FACE (t2));
	return TRUE;
      }
    }
  }
  return FALSE;
}

static void add_edge (GtsEdge * e, GtsFifo * fifo)
{
  gts_fifo_push (fifo, e);
}

int main (int argc, char * argv[])
{
  GPtrArray * vertices;
  GtsFifo * edges;
  guint i, line;
  GtsTriangle * t;
  GtsVertex * v1, * v2, * v3;
  GtsSurface * surface;
  gboolean keep_hull = TRUE;
  gboolean verbose = FALSE;
  gboolean add_constraints = TRUE;
  gboolean remove_holes = FALSE;
  gboolean check_delaunay = FALSE;
  gboolean conform = FALSE;
  gboolean refine = FALSE;
  gboolean split_constraints = FALSE;
  gboolean randomize = FALSE;
  gboolean remove_duplicates = FALSE;
  gboolean slope = FALSE;
  gint steiner_max = -1;
  gdouble quality = 0., area = G_MAXDOUBLE;
  int c = 0, status = 0;
  const char * fname = NULL;
  GTimer * timer;

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

  /* parse options using getopt */
  while (c != EOF) {
#ifdef HAVE_GETOPT_LONG
    static struct option long_options[] = {
      {"duplicates", no_argument, NULL, 'd'},
      {"help", no_argument, NULL, 'h'},
      {"verbose", no_argument, NULL, 'v'},
      {"randomize", no_argument, NULL, 'r'},
      {"hull", no_argument, NULL, 'b'},
      {"noconst", no_argument, NULL, 'e'},
      {"holes", no_argument, NULL, 'H'},
      {"split", no_argument, NULL, 'S'},
      {"check", no_argument, NULL, 'c'},
      {"slope", no_argument, NULL, 't'},
      {"files", required_argument, NULL, 'f'},
      {"conform", no_argument, NULL, 'o'},
      {"steiner", required_argument, NULL, 's'},
      {"quality", required_argument, NULL, 'q'},
      {"area", required_argument, NULL, 'a'},
      { NULL }
    };
    int option_index = 0;
    switch ((c = getopt_long (argc, argv, "hvbecf:os:q:a:HSrdt",
			      long_options, &option_index))) {
#else /* not HAVE_GETOPT_LONG */
    switch ((c = getopt (argc, argv, "hvbecf:os:q:a:HSrdt"))) {
#endif /* not HAVE_GETOPT_LONG */
    case 'd': /* duplicates */
      remove_duplicates = TRUE;
      break;
    case 'b': /* do not keep convex hull */
      keep_hull = FALSE;
      break;
    case 'e': /* do not add constrained edges */
      add_constraints = FALSE;
      break;
    case 'H': /* remove holes */
      remove_holes = TRUE;
      break;
    case 'S': /* split constraints */
      split_constraints = TRUE;
      break;
    case 'r': /* randomize */
      randomize = TRUE;
      break;
    case 't': /* slope optimisation */
      slope = TRUE;
      break;
    case 'c': /* check Delaunay property */
      check_delaunay = TRUE;
      break;
    case 'f': /* generates files */
      fname = optarg;
      break;      
    case 'v': /* verbose */
      verbose = TRUE;
      break;
    case 'o': /* conform */
      conform = TRUE;
      break;
    case 's': /* steiner */
      steiner_max = atoi (optarg);
      break;
    case 'q': /* quality */
      conform = TRUE;
      refine = TRUE;
      quality = atof (optarg);
      break;
    case 'a': /* area */
      conform = TRUE;
      refine = TRUE;
      area = atof (optarg);
      break;
    case 'h': /* help */
      fprintf (stderr,
             "Usage: delaunay [OPTION] < file.gts\n"
	     "Construct the constrained Delaunay triangulation of the input\n"
	     "\n"
	     "  -b       --hull         do not keep convex hull\n"
	     "  -e       --noconst      do not add constrained edges\n"
	     "  -S       --split        split constraints (experimental)\n"
	     "  -H       --holes        remove holes from the triangulation\n"
	     "  -d       --duplicates   remove duplicate vertices\n"
	     "  -r       --randomize    shuffle input vertex list\n"
	     "  -c       --check        check Delaunay property\n"
	     "  -f FNAME --files=FNAME  generate evolution files\n"
	     "  -o       --conform      generate conforming triangulation\n"
	     "  -s N     --steiner=N    maximum number of Steiner points for\n"
	     "                          conforming triangulation (default is no limit)\n"
             "  -t       --slope        maximisation of minimum slope\n"
	     "  -q Q     --quality=Q    Set the minimum acceptable face quality\n"
	     "  -a A     --area=A       Set the maximum acceptable face area\n"
	     "  -v       --verbose      print statistics about the triangulation\n"
	     "  -h       --help         display this help and exit\n"
	     "\n"
	     "Reports bugs to %s\n",
	     GTS_MAINTAINER);
      return 0; /* success */
      break;
    case '?': /* wrong options */
      fprintf (stderr, "Try `delaunay --help' for more information.\n");
      return 1; /* failure */
    }
  }

  /* read file => two lists: vertices and constraints */

  edges = gts_fifo_new ();
  vertices = g_ptr_array_new ();
  if (add_constraints) /* the edge class is a GtsConstraintClass */
    line = read_list (vertices, edges, 
		      GTS_EDGE_CLASS (gts_constraint_class ()),
		      stdin);
  else /* the edge class is a "normal" edge: GtsEdgeClass */
    line = read_list (vertices, edges, 
		      gts_edge_class (), 
		      stdin);

  if (line > 0) {
    fprintf (stderr, "delaunay: error in input file at line %u\n", line);
    return 1;
  }

  timer = g_timer_new ();
  g_timer_start (timer);

  if (randomize)
    shuffle_array (vertices);

  /* create triangle enclosing all the vertices */
  {
    GSList * list = NULL;
    for (i = 0; i < vertices->len; i++)
      list = g_slist_prepend (list, g_ptr_array_index (vertices, i));
    t = gts_triangle_enclosing (gts_triangle_class (), list, 100.);
    g_slist_free (list);
  }
  gts_triangle_vertices (t, &v1, &v2, &v3);

  /* create surface with one face: the enclosing triangle */
  surface = gts_surface_new (gts_surface_class (),
			     gts_face_class (),
			     gts_edge_class (),
			     gts_vertex_class ());
  gts_surface_add_face (surface, gts_face_new (gts_face_class (),
					       t->e1, t->e2, t->e3));

  /* add vertices */
  for (i = 0; i < vertices->len; i++) {
    GtsVertex * v1 = g_ptr_array_index (vertices, i);
    GtsVertex * v = gts_delaunay_add_vertex (surface, v1, NULL);

    g_assert (v != v1);
    if (v != NULL) {
      if (!remove_duplicates) {
	fprintf (stderr, "delaunay: duplicate vertex (%g,%g) in input file\n",
		 GTS_POINT (v)->x, GTS_POINT (v)->y);
	return 1; /* Failure */
      }
      else
	gts_vertex_replace (v1, v);
    }
    if (fname) {
      static guint nf = 1;
      char s[80];
      FILE * fp;

      g_snprintf (s, 80, "%s.%u", fname, nf++);
      fp = fopen (s, "wt");
      gts_surface_write_oogl (surface, fp);
      fclose (fp);

      if (check_delaunay && gts_delaunay_check (surface)) {
	fprintf (stderr, "delaunay: triangulation is not Delaunay\n");
	return 1;
      }
    }
  }
  g_ptr_array_free (vertices, TRUE);

  /* add remaining constraints */
  if (add_constraints)
    gts_fifo_foreach (edges, (GtsFunc) add_constraint, surface);

  /* destroy enclosing triangle */
  gts_allow_floating_vertices = TRUE;
  gts_object_destroy (GTS_OBJECT (v1));
  gts_object_destroy (GTS_OBJECT (v2));
  gts_object_destroy (GTS_OBJECT (v3));
  gts_allow_floating_vertices = FALSE;

  if (!keep_hull)
    gts_delaunay_remove_hull (surface);

  if (remove_holes)
    delaunay_remove_holes (surface);

  if (split_constraints) {
    gpointer data[2];

    data[0] = surface;
    data[1] = edges;
    gts_fifo_foreach (edges, (GtsFunc) split_constraint, data);
  }

  if (conform) {
    guint encroached_number = 
      gts_delaunay_conform (surface, 
			    steiner_max,
			    (GtsEncroachFunc) gts_vertex_encroaches_edge,
			    NULL);
    if (encroached_number == 0 && refine) {
      guint unrefined_number;
      gpointer data[2];
      
      data[0] = &quality;
      data[1] = &area;
      unrefined_number = 
	gts_delaunay_refine (surface, 
			     steiner_max,
			     (GtsEncroachFunc) gts_vertex_encroaches_edge,
			     NULL,
			     (GtsKeyFunc) triangle_cost,
			     data);
      if (verbose && unrefined_number > 0)
	fprintf (stderr, 
		 "delaunay: ran out of Steiner points (max: %d) during refinement\n"
		 "%d unrefined faces left\n",
		 steiner_max, unrefined_number);
    }
    else if (verbose && encroached_number > 0)
      fprintf (stderr, 
	       "delaunay: ran out of Steiner points (max: %d) during conforming\n"
	       "Delaunay triangulation: %d encroached constraints left\n",
	       steiner_max, encroached_number);
  }

  if (slope) {
    guint swapped;
    do {
      swapped = 0;
      GtsFifo * fifo = gts_fifo_new ();
      gts_surface_foreach_edge (surface, (GtsFunc) add_edge, fifo);
      GtsEdge * e;
      while ((e = gts_fifo_pop (fifo)))
	if (edge_slope_swap (e, surface))
	  swapped++;
      gts_fifo_destroy (fifo);
      if (verbose)
	fprintf (stderr, "swapped %d\n", swapped);
    } while (swapped > 0);
  }

  g_timer_stop (timer);

  if (verbose) {
    gts_surface_print_stats (surface, stderr);
    fprintf (stderr, "# Triangulation time: %g s speed: %.0f vertex/s\n", 
	  g_timer_elapsed (timer, NULL),
	  gts_surface_vertex_number (surface)/g_timer_elapsed (timer, NULL));
  }

  if (check_delaunay && gts_delaunay_check (surface)) {
    fprintf (stderr, "delaunay: triangulation is not Delaunay\n");
    status = 1; /* failure */
  }

  /* write triangulation */
  gts_surface_write (surface, stdout);

  return status;
}
