/* 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 <string.h>
#include "gts.h"

#define DYNAMIC_SPLIT
#define NEW

/* #define DEBUG
   #define DEBUG_HEXPAND
   #define DEBUG_EXPAND */

struct _GtsSplitCFace {
  GtsFace * f;
  GtsTriangle ** a1, ** a2;
};

typedef struct _CFace      CFace;
typedef struct _CFaceClass CFaceClass;

struct _CFace {
  GtsObject object; 

  GtsSplit * parent_split;
  GtsTriangle * t;
  guint flags;
};
/* the size of the CFace structure must be smaller or equal to the size
   of the GtsFace structure as both structures use the same memory location */

struct _CFaceClass {
  GtsObjectClass parent_class;
};

#define IS_CFACE(obj) (gts_object_is_from_class (obj, cface_class ()))
#define CFACE(obj)    ((CFace *) obj)
#define CFACE_ORIENTATION(cf) ((cf)->flags & 0x1)
#define CFACE_ORIENTATION_DIRECT(cf) ((cf)->flags |= 0x1)
#define CFACE_VVS(cf)                ((cf)->flags & 0x2)
#define CFACE_VVS_DIRECT(cf)         ((cf)->flags |= 0x2)
#define CFACE_E1                     0x4
#define CFACE_E2                     0x8
#define CFACE_KEEP_VVS               0x10

#define ROTATE_ORIENT(e, e1, e2, e3)  { if (e1 == e) { e1 = e2; e2 = e3; }\
                                 else if (e2 == e) { e2 = e1; e1 = e3; }\
                                 else g_assert (e3 == e); }
#define SEGMENT_USE_VERTEX(s, v) ((s)->v1 == v || (s)->v2 == v)
#define TRIANGLE_REPLACE_EDGE(t, e, with) { if ((t)->e1 == e)\
					      (t)->e1 = with;\
					    else if ((t)->e2 == e)\
					      (t)->e2 = with;\
					    else {\
					      g_assert ((t)->e3 == e);\
					      (t)->e3 = with;\
					    }\
                                          }

#define HEAP_INSERT_OBJECT(h, e) (GTS_OBJECT (e)->reserved =\
				  gts_eheap_insert (h, e))
#define HEAP_REMOVE_OBJECT(h, e) (gts_eheap_remove (h, GTS_OBJECT (e)->reserved),\
				   GTS_OBJECT (e)->reserved = NULL)

static GtsObjectClass * cface_class (void)
{
  static GtsObjectClass * klass = NULL;

  if (klass == NULL) {
    GtsObjectClassInfo cface_info = {
      "GtsCFace",
      sizeof (CFace),
      sizeof (CFaceClass),
      (GtsObjectClassInitFunc) NULL,
      (GtsObjectInitFunc) NULL,
      (GtsArgSetFunc) NULL,
      (GtsArgGetFunc) NULL
    };
    klass = gts_object_class_new (gts_object_class (), &cface_info);
    g_assert (sizeof (CFace) <= sizeof (GtsFace));
  }

  return klass;
}

/* Replace @e with @with for all the triangles using @e but @f.
   Destroys @e and removes it from @heap (if not %NULL). 
   Returns a triangle using e different from f or %NULL. */
static GtsTriangle * replace_edge_collapse (GtsEdge * e, 
					    GtsEdge * with, 
					    CFace * cf,
					    GtsEHeap * heap
#ifdef DYNAMIC_SPLIT
					    , GtsTriangle *** a1
#endif
#ifdef NEW
					    , guint edge_flag
#endif
					    )
{
  GSList * i;
  GtsTriangle * rt = NULL;
#ifdef DYNAMIC_SPLIT
  guint size;
  GtsTriangle ** a;
#endif

#ifdef NEW
  i = e->triangles;
  e->triangles = NULL;
  size = g_slist_length (i)*sizeof (GtsTriangle *);
  *a1 = a = g_malloc (size > 0 ? size : sizeof (GtsTriangle *));
  while (i) {
    GtsTriangle * t = i->data;
    GSList * next = i->next;
    if (t != ((GtsTriangle *) cf)) {
      if (IS_CFACE (t)) {
	i->next = e->triangles;
	e->triangles = i;
	/* set the edge given by edge_flag (CFACE_E1 or CFACE_E2) */
	GTS_OBJECT (t)->reserved = GUINT_TO_POINTER (edge_flag);
	cf->flags |= CFACE_KEEP_VVS;
      }
      else {
	TRIANGLE_REPLACE_EDGE (t, e, with);
	i->next = with->triangles;
	with->triangles = i;
	rt = t;
	*(a++) = t;
      }
    }
    else
      g_slist_free_1 (i);
    i = next;
  }
  *a = NULL;
  if (!e->triangles) {
    if (heap)
      HEAP_REMOVE_OBJECT (heap, e);
    gts_object_destroy (GTS_OBJECT (e));
  }
#else /* not NEW */
  i = e->triangles;
#ifdef DYNAMIC_SPLIT
  size = g_slist_length (i)*sizeof (GtsTriangle *);
  *a1 = a = g_malloc (size > 0 ? size : sizeof (GtsTriangle *));
#endif
  while (i) {
    GtsTriangle * t = i->data;
    GSList * next = i->next;
    if (t != ((GtsTriangle *) cf)) {
      TRIANGLE_REPLACE_EDGE (t, e, with);
      i->next = with->triangles;
      with->triangles = i;
      rt = t;
#ifdef DYNAMIC_SPLIT
      *(a++) = t;
#endif
    }
    else
      g_slist_free_1 (i);
    i = next;
  }
#ifdef DYNAMIC_SPLIT
  *a = NULL;
#endif
  if (heap)
    HEAP_REMOVE_OBJECT (heap, e);
  e->triangles = NULL;
  gts_object_destroy (GTS_OBJECT (e));
#endif /* NEW */

  return rt;
}

static CFace * cface_new (GtsFace * f,
			  GtsEdge * e,
			  GtsVertex * v1, 
			  GtsVertex * v2,
			  GtsSplit * vs,
			  GtsEHeap * heap,
			  GtsEdgeClass * klass
#ifdef DYNAMIC_SPLIT
			  , GtsSplitCFace * scf
#endif
			  )
{
  CFace * cf;
  GtsVertex * v;
  GtsEdge * e1, * e2, * e3, * vvs;
  GSList * i;
  GtsTriangle * t, * t1 = NULL, * t2 = NULL;
  guint flags;

  g_return_val_if_fail (f != NULL, NULL);
#ifndef NEW
  g_return_val_if_fail (GTS_IS_FACE (f), NULL);
#endif
  g_return_val_if_fail (e != NULL, NULL);
  g_return_val_if_fail (vs != NULL, NULL);

  t = ((GtsTriangle *) f);
  if (heap)
    g_return_val_if_fail (!gts_triangle_is_duplicate (t), NULL);

#ifdef NEW
  /* get CFACE_E1 and CFACE_E2 info */
  flags = GPOINTER_TO_UINT (GTS_OBJECT (f)->reserved);
#endif
  GTS_OBJECT_SET_FLAGS (f, GTS_DESTROYED);

  i = f->surfaces;
  while (i) {
    GSList * next = i->next;
    gts_surface_remove_face (i->data, f);
    i = next;
  }
  g_slist_free (f->surfaces);

  e1 = t->e1; e2 = t->e2; e3 = t->e3;
  ROTATE_ORIENT (e, e1, e2, e3);

  cf = (CFace *) f;
#ifndef NEW
  GTS_OBJECT (cf)->klass = cface_class ();
#else
  cf->flags = flags;
#endif
  gts_object_init (GTS_OBJECT (cf), cface_class ());
  cf->parent_split = vs;

  if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e1), v2)) {
    CFACE_ORIENTATION_DIRECT (cf); /* v1->v2->v */
    e3 = e1; e1 = e2; e2 = e3;
  }
  v = GTS_SEGMENT (e1)->v1 == v1 ?
    GTS_SEGMENT (e1)->v2 : GTS_SEGMENT (e1)->v1;
#ifdef NEW
  if ((cf->flags & CFACE_E1) || (cf->flags & CFACE_E2))
    g_assert ((vvs = GTS_EDGE (gts_vertices_are_connected (vs->v, v))));
  else
#endif
  vvs = gts_edge_new (klass, v, vs->v);

  t1 = replace_edge_collapse (e1, vvs, cf, heap
#ifdef DYNAMIC_SPLIT
			      , &scf->a1
#endif
#ifdef NEW
			      , CFACE_E1
#endif
			      );
  t2 = replace_edge_collapse (e2, vvs, cf, heap
#ifdef DYNAMIC_SPLIT
			      , &scf->a2
#endif
#ifdef NEW
			      , CFACE_E2
#endif
			      );
  t = cf->t = t1 ? t1 : t2;
  g_assert (t);

  /* set up flags necessary to find vvs */
  if (t->e1 == vvs) e2 = t->e2;
  else if (t->e2 == vvs) e2 = t->e3;
  else {
    g_assert (t->e3 == vvs);
    e2 = t->e1;
  }
  if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e2), v))
    CFACE_VVS_DIRECT (cf);

  return cf;
}

static void find_vvs (GtsVertex * vs,
		      GtsTriangle * t,
		      GtsVertex ** v, GtsEdge ** vvs,
		      gboolean orientation)
{
  GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3, * tmp;

  if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e2), vs)) {
    tmp = e1; e1 = e2; e2 = e3; e3 = tmp;
  }
  else if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e3), vs)) {
    tmp = e1; e1 = e3; e3 = e2; e2 = tmp;
  }
  else
    g_assert (SEGMENT_USE_VERTEX (GTS_SEGMENT (e1), vs));
  if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e2), vs) ||
      !gts_segments_touch (GTS_SEGMENT (e1), GTS_SEGMENT (e2))) {
    tmp = e1; e1 = e2; e2 = e3; e3 = tmp;
    g_assert (gts_segments_touch (GTS_SEGMENT (e1), GTS_SEGMENT (e2)));
  }

  *vvs = orientation ? e1 : e3;

  if (GTS_SEGMENT (*vvs)->v1 != vs) {
    g_assert (GTS_SEGMENT (*vvs)->v2 == vs);
    *v = GTS_SEGMENT (*vvs)->v1;
  }
  else
    *v = GTS_SEGMENT (*vvs)->v2;
}

static void replace_edge_expand (GtsEdge * e, 
				 GtsEdge * with,
				 GtsTriangle ** a,
				 GtsVertex * v)
{
  GtsTriangle ** i = a, * t;

  while ((t = *(i++))) {
#ifdef DEBUG_EXPAND
    g_assert (!IS_CFACE (t));
    fprintf (stderr, "replacing %p->%d: e: %p->%d with: %p->%d\n",
	     t, id (t), e, id (e), with, id (with));
#endif
    TRIANGLE_REPLACE_EDGE (t, e, with);
    with->triangles = g_slist_prepend (with->triangles, t);
    if (GTS_OBJECT (t)->reserved) {
      /* apart from the triangles having e as an edge, t is the only
	 triangle using v */
      g_assert (GTS_OBJECT (t)->reserved == v);
      GTS_OBJECT (t)->reserved = NULL;
    }
    else
      GTS_OBJECT (t)->reserved = v;
  }
}

static void cface_expand (CFace * cf,
			  GtsTriangle ** a1,
			  GtsTriangle ** a2,
			  GtsEdge * e,
			  GtsVertex * v1, 
			  GtsVertex * v2,
			  GtsVertex * vs,
			  GtsEdgeClass * klass)
{
  GtsVertex * v;
  GtsEdge * e1, * e2, * vvs;
  gboolean orientation;
  guint flags;

  g_return_if_fail (cf != NULL);
  g_return_if_fail (IS_CFACE (cf));
  g_return_if_fail (e != NULL);
  g_return_if_fail (vs != NULL);

  flags = cf->flags;
  orientation = CFACE_ORIENTATION (cf);

  find_vvs (vs, cf->t, &v, &vvs, CFACE_VVS (cf));

#ifdef NEW
  if (flags & CFACE_E1)
    e1 = GTS_EDGE (gts_vertices_are_connected (v1, v));
  else
    e1 = gts_edge_new (klass, v, v1);
  if (flags & CFACE_E2)
    e2 = GTS_EDGE (gts_vertices_are_connected (v2, v));
  else
    e2 = gts_edge_new (klass, v, v2);
#else
  e1 = gts_edge_new (v, v1);
  e2 = gts_edge_new (v, v2);
#endif

  replace_edge_expand (vvs, e1, a1, v1);
  replace_edge_expand (vvs, e2, a2, v2);

#ifdef NEW
  if (!(flags & CFACE_KEEP_VVS)) {
    g_slist_free (vvs->triangles);
    vvs->triangles = NULL;
    gts_object_destroy (GTS_OBJECT (vvs));
  }
#else
  g_slist_free (vvs->triangles);
  vvs->triangles = NULL;
  gts_object_destroy (GTS_OBJECT (vvs));
#endif

  /* gts_face_new : because I am "creating" a face */
  GTS_OBJECT (cf)->klass = GTS_OBJECT_CLASS (gts_face_class ());
  gts_object_init (GTS_OBJECT (cf), GTS_OBJECT (cf)->klass);
  
  if (orientation)
    gts_triangle_set (GTS_TRIANGLE (cf), e, e2, e1);
  else
    gts_triangle_set (GTS_TRIANGLE (cf), e, e1, e2);
}

static void split_destroy (GtsObject * object)
{
  GtsSplit * vs = GTS_SPLIT (object);
  guint i = vs->ncf;
  GtsSplitCFace * cf = vs->cfaces;

  while (i--) {
    if (IS_CFACE (cf->f))
      gts_object_destroy (GTS_OBJECT (cf->f));
    g_free (cf->a1);
    g_free (cf->a2);
    cf++;
  }
  g_free (vs->cfaces);

  if (!gts_allow_floating_vertices && vs->v && vs->v->segments == NULL)
    gts_object_destroy (GTS_OBJECT (vs->v));

  (* GTS_OBJECT_CLASS (gts_split_class ())->parent_class->destroy) (object);
}

static void split_class_init (GtsObjectClass * klass)
{
  klass->destroy = split_destroy;
}

static void split_init (GtsSplit * split)
{
  split->v1 = split->v2 = NULL;
  split->v = NULL;
  split->cfaces = NULL;
  split->ncf = 0;
}

/**
 * gts_split_class:
 *
 * Returns: the #GtsSplitClass.
 */
GtsSplitClass * gts_split_class (void)
{
  static GtsSplitClass * klass = NULL;

  if (klass == NULL) {
    GtsObjectClassInfo split_info = {
      "GtsSplit",
      sizeof (GtsSplit),
      sizeof (GtsSplitClass),
      (GtsObjectClassInitFunc) split_class_init,
      (GtsObjectInitFunc) split_init,
      (GtsArgSetFunc) NULL,
      (GtsArgGetFunc) NULL
    };
    klass = gts_object_class_new (gts_object_class (), 
				  &split_info);
  }

  return klass;
}

#ifdef DEBUG
static gboolean edge_collapse_is_valid (GtsEdge * e)
{
  GSList * i;

  g_return_val_if_fail (e != NULL, FALSE);
  
  if (gts_segment_is_duplicate (GTS_SEGMENT (e))) {
    g_warning ("collapsing duplicate edge");
    return FALSE;
  }
    
  i = GTS_SEGMENT (e)->v1->segments;
  while (i) {
    GtsEdge * e1 = i->data;
    if (e1 != e && GTS_IS_EDGE (e1)) {
      GtsEdge * e2 = NULL;
      GSList * j = GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e)->v1 ? 
	GTS_SEGMENT (e1)->v2->segments : GTS_SEGMENT (e1)->v1->segments;
      while (j && !e2) {
	GtsEdge * e1 = j->data;
	if (GTS_IS_EDGE (e1) && 
	    (GTS_SEGMENT (e1)->v1 == GTS_SEGMENT (e)->v2 || 
	     GTS_SEGMENT (e1)->v2 == GTS_SEGMENT (e)->v2))
	  e2 = e1;
	j = j->next;
      }
      if (e2 && !gts_triangle_use_edges (e, e1, e2)) {
	g_warning ("collapsing empty triangle");
	return FALSE;
      }
    }
    i = i->next;
  }

  if (gts_edge_is_boundary (e, NULL)) {
    GtsTriangle * t = e->triangles->data;
    if (gts_edge_is_boundary (t->e1, NULL) &&
	gts_edge_is_boundary (t->e2, NULL) &&
	gts_edge_is_boundary (t->e3, NULL)) {
      g_warning ("collapsing single triangle");
      return FALSE;
    }
  }
  else {
    if (gts_vertex_is_boundary (GTS_SEGMENT (e)->v1, NULL) &&
	gts_vertex_is_boundary (GTS_SEGMENT (e)->v2, NULL)) {
      g_warning ("collapsing two sides of a strip");
      return FALSE;    
    }
    if (gts_edge_belongs_to_tetrahedron (e)) {
      g_warning ("collapsing tetrahedron");
      return FALSE;
    }
  }

  return TRUE;
}
#endif /* DEBUG */

static void print_split (GtsSplit * vs, FILE * fptr)
{
  guint j;
  GtsSplitCFace * cf;

  g_return_if_fail (vs != NULL);
  g_return_if_fail (fptr != NULL);

  fprintf (fptr, "%p: v: %p v1: %p v2: %p ncf: %u cfaces: %p\n",
	   vs, vs->v, vs->v1, vs->v2, vs->ncf, vs->cfaces);
  cf = vs->cfaces;
  j = vs->ncf;
  while (j--) {
    fprintf (stderr, "  f: %p a1: %p a2: %p\n",
	     cf->f, cf->a1, cf->a2);
    cf++;
  }
}

/**
 * gts_split_collapse:
 * @vs: a #GtsSplit.
 * @klass: a #GtsEdgeClass.
 * @heap: a #GtsEHeap or %NULL.
 *
 * Collapses the vertex split @vs. Any new edge created during the process will
 * be of class @klass. If heap is not %NULL, the new edges will be inserted
 * into it and the destroyed edges will be removed from it.
 */
void gts_split_collapse (GtsSplit * vs, 
			 GtsEdgeClass * klass,
			 GtsEHeap * heap)
{
  GtsEdge * e;
  GtsVertex * v, * v1, * v2;
  GSList * i, * end;
#ifdef DYNAMIC_SPLIT
  GtsSplitCFace * cf;
  guint j;
#endif
#ifdef DEBUG
  gboolean invalid = FALSE;
  static guint ninvalid = 0;
#endif

  g_return_if_fail (vs != NULL);
  g_return_if_fail (klass != NULL);

  v = vs->v;

  g_return_if_fail (v->segments == NULL);
  
  /* we don't want to destroy vertices */
  gts_allow_floating_vertices = TRUE;

  v1 = GTS_SPLIT_V1 (vs);
  v2 = GTS_SPLIT_V2 (vs);
  g_assert ((e = GTS_EDGE (gts_vertices_are_connected (v1, v2))));

#ifdef DEBUG
  fprintf (stderr, "collapsing %p: v1: %p v2: %p v: %p\n", vs, v1, v2, v);
  if (!edge_collapse_is_valid (e)) {
    char fname[80];
    FILE * fptr;
    GSList * triangles, * i;

    g_warning ("invalid edge collapse");
    invalid = TRUE;
    sprintf (fname, "invalid.%d", ninvalid);
    fptr = fopen (fname, "wt");
    gts_write_segment (GTS_SEGMENT (e), GTS_POINT (v), fptr);
    triangles = gts_vertex_triangles (v1, NULL);
    triangles = gts_vertex_triangles (v2, triangles);
    i = triangles;
    while (i) {
      gts_write_triangle (i->data, GTS_POINT (v), fptr);
      i = i->next;
    }
    g_slist_free (triangles);
    fclose (fptr);
  }
#endif

  i = e->triangles;
#ifdef DYNAMIC_SPLIT
  cf = vs->cfaces;
  j = vs->ncf;
  while (j--) {
    g_free (cf->a1);
    g_free (cf->a2);
    cf++;
  }
  g_free (vs->cfaces);

  vs->ncf = g_slist_length (i);
  g_assert (vs->ncf > 0);
  cf = vs->cfaces = g_malloc (vs->ncf*sizeof (GtsSplitCFace));
#endif /* DYNAMIC_SPLIT */
#ifdef NEW
  while (i) {
    cf->f = i->data;
    g_assert (GTS_IS_FACE (cf->f));
    GTS_OBJECT (cf->f)->klass = GTS_OBJECT_CLASS (cface_class ());
    cf++;
    i = i->next;
  }
  i = e->triangles;
  cf = vs->cfaces;
  while (i) {
    cface_new (i->data, e, v1, v2, vs, heap, klass, cf);
#ifdef DEBUG
    fprintf (stderr, "cface: %p->%d t: %p->%d a1: ", 
	     cf->f, id (cf->f), CFACE (cf->f)->t, id (CFACE (cf->f)->t));
    {
      GtsTriangle * t, ** a;
      a = cf->a1;
      while ((t = *(a++)))
	fprintf (stderr, "%p->%d ", t, id (t));
      fprintf (stderr, "a2: ");
      a = cf->a2;
      while ((t = *(a++)))
	fprintf (stderr, "%p->%d ", t, id (t));
      fprintf (stderr, "\n");
    }
#endif
    cf++;
    i = i->next;
  }
#else /* not NEW */
  while (i) {
    cface_new (i->data, e, v1, v2, vs, heap
#ifdef DYNAMIC_SPLIT
	       , cf
#endif /* DYNAMIC_SPLIT */
	       );
#ifdef DYNAMIC_SPLIT
    cf->f = i->data;
    cf++;
#endif /* DYNAMIC_SPLIT */
    i = i->next;
  }
#endif /* NEW */
  g_slist_free (e->triangles);
  e->triangles = NULL;
  gts_object_destroy (GTS_OBJECT (e));

  gts_allow_floating_vertices = FALSE;

  end = NULL;
  i = v1->segments;
  while (i) {
    GtsSegment * s = i->data;
    if (s->v1 == v1)
      s->v1 = v;
    else
      s->v2 = v;
    end = i;
    i = i->next;
  }
  if (end) {
    end->next = v->segments;
    v->segments = v1->segments;
    v1->segments = NULL;
  }

  end = NULL;
  i = v2->segments;
  while (i) {
    GtsSegment * s = i->data;
    if (s->v1 == v2)
      s->v1 = v;
    else
      s->v2 = v;
    end = i;
    i = i->next;
  }
  if (end) {
    end->next = v->segments;
    v->segments = v2->segments;
    v2->segments = NULL;
  }

#ifdef DEBUG
  if (invalid) {
    char fname[80];
    FILE * fptr;
    GSList * triangles, * i;
    GtsSurface * surface = NULL;

    sprintf (fname, "invalid_after.%d", ninvalid);
    fptr = fopen (fname, "wt");
    triangles = gts_vertex_triangles (v, NULL);
    i = triangles;
    while (i) {
      GtsTriangle * t = i->data;
      fprintf (stderr, "checking %p->%d\n", t, id (t));
      g_assert (GTS_IS_FACE (t));
      gts_write_triangle (t, GTS_POINT (v), fptr);
      surface = GTS_FACE (t)->surfaces->data;
      if (gts_triangle_is_duplicate (t))
	fprintf (stderr, "%p->%d is duplicate\n", t, id (t));
      if (gts_segment_is_duplicate (GTS_SEGMENT (t->e1)))
	fprintf (stderr, "e1 of %p->%d is duplicate\n", t, id (t));
      if (gts_segment_is_duplicate (GTS_SEGMENT (t->e2)))
	fprintf (stderr, "e2 of %p->%d is duplicate\n", t, id (t));
      if (gts_segment_is_duplicate (GTS_SEGMENT (t->e3)))
	fprintf (stderr, "e3 of %p->%d is duplicate\n", t, id (t));
      i = i->next;
    }
    fclose (fptr);
    g_slist_free (triangles);
#if 0
    gts_split_expand (vs, surface);

    sprintf (fname, "invalid_after_after.%d", ninvalid);
    fptr = fopen (fname, "wt");
    triangles = gts_vertex_triangles (v1, NULL);
    triangles = gts_vertex_triangles (v2, triangles);
    i = triangles;
    while (i) {
      GtsTriangle * t = i->data;
      gts_write_triangle (t, GTS_POINT (v), fptr);
      surface = GTS_FACE (t)->surfaces->data;
      if (gts_triangle_is_duplicate (t))
	fprintf (stderr, "%p->%d is duplicate\n", t, id (t));
      if (gts_segment_is_duplicate (GTS_SEGMENT (t->e1)))
	fprintf (stderr, "e1 of %p->%d is duplicate\n", t, id (t));
      if (gts_segment_is_duplicate (GTS_SEGMENT (t->e2)))
	fprintf (stderr, "e2 of %p->%d is duplicate\n", t, id (t));
      if (gts_segment_is_duplicate (GTS_SEGMENT (t->e3)))
	fprintf (stderr, "e3 of %p->%d is duplicate\n", t, id (t));
      i = i->next;
    }
    fclose (fptr);
    g_slist_free (triangles);

    exit (1);
#endif
    ninvalid++;
  }
#endif
}

/**
 * gts_split_expand:
 * @vs: a #GtsSplit.
 * @s: a #GtsSurface.
 * @klass: a #GtsEdgeClass.
 *
 * Expands the vertex split @vs adding the newly created faces to @s. Any 
 * new edge will be of class @klass.
 */
void gts_split_expand (GtsSplit * vs, 
		       GtsSurface * s,
		       GtsEdgeClass * klass)
{
  GSList * i;
  GtsEdge * e;
  GtsVertex * v, * v1, * v2;  
  gboolean changed = FALSE;
  GtsSplitCFace * cf;
  guint j;
  
  g_return_if_fail (vs != NULL);
  g_return_if_fail (s != NULL);
  g_return_if_fail (klass != NULL);

  /* we don't want to destroy vertices */
  gts_allow_floating_vertices = TRUE;

  v1 = GTS_SPLIT_V1 (vs);
  v2 = GTS_SPLIT_V2 (vs);
  v = vs->v;
#ifdef DEBUG_EXPAND
  fprintf (stderr, "expanding %p->%d: v1: %p->%d v2: %p->%d v: %p->%d\n",
	   vs, id (vs), v1, id (v1), v2, id (v2), v, id (v));
#endif
  e = gts_edge_new (klass, v1, v2);
  cf = vs->cfaces;
  j = vs->ncf;
  while (j--) {
    cface_expand (CFACE (cf->f), cf->a1, cf->a2, e, v1, v2, v, klass);
    gts_surface_add_face (s, cf->f);
    cf++;
  }

  gts_allow_floating_vertices = FALSE;

  /* this part is described by figure "expand.fig" */
  i = v->segments;
  while (i) {
    GtsEdge * e1 = i->data;
    GtsVertex * with = NULL;
    GSList * j = e1->triangles, * next = i->next;
    // fprintf (stderr, "e1: %p->%d\n", e1, id (e1));
    while (j && !with) {
      with = GTS_OBJECT (j->data)->reserved;
      j = j->next;
    }
    if (with) {
      j = e1->triangles;
      while (j) {
	GtsTriangle * t = j->data;
	if (GTS_OBJECT (t)->reserved) {
	  g_assert (GTS_OBJECT (t)->reserved == with);
	  GTS_OBJECT (t)->reserved = NULL;
	}
	else
	  GTS_OBJECT (t)->reserved = with;
	j = j->next;
      }
      if (GTS_SEGMENT (e1)->v1 == v)
	GTS_SEGMENT (e1)->v1 = with;
      else
	GTS_SEGMENT (e1)->v2 = with;

      v->segments = g_slist_remove_link (v->segments, i);
      i->next = with->segments;
      with->segments = i;
      changed = TRUE;
    }
    if (next)
      i = next;
    else {
      /* check for infinite loop (the crossed out case in 
	 figure "expand.fig") */
      g_assert (changed);
      changed = FALSE;
      i = v->segments;
    }
  }
}

static void cface_neighbors (GtsSplitCFace * cf,
			     GtsEdge * e,
			     GtsVertex * v1,
			     GtsVertex * v2)
{
  GtsTriangle * t = GTS_TRIANGLE (cf->f), ** a;
  GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3;
  GSList * i;
  guint size;

  ROTATE_ORIENT (e, e1, e2, e3);
  if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e1), v2)) {
    e3 = e1; e1 = e2; e2 = e3;
  }
  
  i = e1->triangles;
  size = g_slist_length (i)*sizeof (GtsTriangle *);
  a = cf->a1 = g_malloc (size > 0 ? size : sizeof (GtsTriangle *));
  while (i) {
    if (i->data != t)
      *(a++) = i->data;
    i = i->next;
  }
  *a = NULL;

  i = e2->triangles;
  size = g_slist_length (i)*sizeof (GtsTriangle *);
  a = cf->a2 = g_malloc (size > 0 ? size : sizeof (GtsTriangle *));
  while (i) {
    if (i->data != t)
      *(a++) = i->data;
    i = i->next;
  }
  *a = NULL;
}

/**
 * gts_split_new:
 * @klass: a #GtsSplitClass.
 * @v: a #GtsVertex.
 * @o1: either a #GtsVertex or a #GtsSplit.
 * @o2: either a #GtsVertex or a #GtsSplit.
 *
 * Creates a new #GtsSplit which would collapse @o1 and @o2 into @v. The 
 * collapse itself is not performed.
 *
 * Returns: the new #GtsSplit.
 */
GtsSplit * gts_split_new (GtsSplitClass * klass,
			  GtsVertex * v,
			  GtsObject * o1,
			  GtsObject * o2)
{
  GtsSplit * vs;
  GtsVertex * v1, * v2;
  GtsEdge * e;
  GSList * i;
  GtsSplitCFace * cf;

  g_return_val_if_fail (klass != NULL, NULL);
  g_return_val_if_fail (v != NULL, NULL);
  g_return_val_if_fail (GTS_IS_SPLIT (o1) || GTS_IS_VERTEX (o1), NULL);
  g_return_val_if_fail (GTS_IS_SPLIT (o2) || GTS_IS_VERTEX (o2), NULL);

  vs = GTS_SPLIT (gts_object_new (GTS_OBJECT_CLASS (klass)));
  vs->v = v;
  vs->v1 = o1;
  vs->v2 = o2;
  v1 = GTS_SPLIT_V1 (vs);
  v2 = GTS_SPLIT_V2 (vs);
#ifdef DYNAMIC_SPLIT
  vs->ncf = 0;
  vs->cfaces = NULL;
#else
  g_assert ((e = GTS_EDGE (gts_vertices_are_connected (v1, v2))));
  i = e->triangles;
  vs->ncf = g_slist_length (i);
  g_assert (vs->ncf > 0);
  cf = vs->cfaces = g_malloc (vs->ncf*sizeof (GtsSplitCFace));
  while (i) {
    cf->f = i->data;
    cface_neighbors (cf, e, v1, v2);
    i = i->next;
    cf++;
  }
#endif
  
  return vs;
}

static gboolean 
split_traverse_pre_order (GtsSplit *           vs,
			  GtsSplitTraverseFunc func,
			  gpointer	       data)
{
  if (func (vs, data))
    return TRUE;
  if (GTS_IS_SPLIT (vs->v1) &&
      split_traverse_pre_order (GTS_SPLIT (vs->v1), func, data))
    return TRUE;
  if (GTS_IS_SPLIT (vs->v2) &&
      split_traverse_pre_order (GTS_SPLIT (vs->v2), func, data))
    return TRUE;
  return FALSE;
}

static gboolean 
split_depth_traverse_pre_order (GtsSplit *             vs,
				guint                  depth,
				GtsSplitTraverseFunc   func,
				gpointer	       data)
{
  if (func (vs, data))
      return TRUE;
    
  depth--;
  if (!depth)
    return FALSE;

  if (GTS_IS_SPLIT (vs->v1) &&
      split_depth_traverse_pre_order (GTS_SPLIT (vs->v1), depth, func, data))
    return TRUE;
  if (GTS_IS_SPLIT (vs->v2) &&
      split_depth_traverse_pre_order (GTS_SPLIT (vs->v2), depth, func, data))
    return TRUE;
  return FALSE;
}

static gboolean 
split_traverse_post_order (GtsSplit *           vs,
			   GtsSplitTraverseFunc func,
			   gpointer	        data)
{
  if (GTS_IS_SPLIT (vs->v1) &&
      split_traverse_post_order (GTS_SPLIT (vs->v1), func, data))
    return TRUE;
  if (GTS_IS_SPLIT (vs->v2) &&
      split_traverse_post_order (GTS_SPLIT (vs->v2), func, data))
    return TRUE;
  if (func (vs, data))
    return TRUE;
  return FALSE;
}

static gboolean
split_depth_traverse_post_order (GtsSplit *           vs,
				 guint                depth,
				 GtsSplitTraverseFunc func,
				 gpointer	      data)
{
  depth--;
  if (depth) {
    if (GTS_IS_SPLIT (vs->v1) &&
	split_depth_traverse_post_order (GTS_SPLIT (vs->v1), 
					 depth, func, data))
      return TRUE;
    if (GTS_IS_SPLIT (vs->v2) &&
	split_depth_traverse_post_order (GTS_SPLIT (vs->v2),
					 depth, func, data))
      return TRUE;
  }
  if (func (vs, data))
    return TRUE;
  return FALSE;
}

/**
 * gts_split_traverse:
 * @root: the #GtsSplit to start the traversal from.
 * @order: the order in which nodes are visited - G_PRE_ORDER or G_POST_ORDER.
 * @depth: the maximum depth of the traversal. Nodes below this depth
 * will not be visited. If depth is -1 all nodes in the tree are
 * visited. If depth is 1, only the root is visited. If depth is 2,
 * the root and its children are visited. And so on.
 * @func: the function to call for each visited #GtsHSplit.
 * @data: user data to pass to the function.
 *
 * Traverses the #GtsSplit tree having @root as root. Calls @func for each
 * #GtsSplit of the tree in the order specified by @order. If order is set
 * to G_PRE_ORDER @func is called for the #GtsSplit then its children, if order
 * is set to G_POST_ORDER @func is called for the children and then for the
 * #GtsSplit.
 */
void gts_split_traverse (GtsSplit *           root,
			 GTraverseType        order,
			 gint                 depth,
			 GtsSplitTraverseFunc func,
			 gpointer             data)
{
  g_return_if_fail (root != NULL);
  g_return_if_fail (func != NULL);
  g_return_if_fail (order < G_LEVEL_ORDER);
  g_return_if_fail (depth == -1 || depth > 0);

  switch (order) {
  case G_PRE_ORDER:
    if (depth < 0)
      split_traverse_pre_order (root, func, data);
    else
      split_depth_traverse_pre_order (root, depth, func, data);
    break;
  case G_POST_ORDER:
    if (depth < 0)
      split_traverse_post_order (root, func, data);
    else
      split_depth_traverse_post_order (root, depth, func, data);
    break;
  default:
    g_assert_not_reached ();
  }
}

/**
 * gts_split_height:
 * @root: a #GtsSplit.
 *
 * Returns: the maximum height of the vertex split tree having @root as root.
 */
guint gts_split_height (GtsSplit * root)
{
  guint height = 0, tmp_height;

  g_return_val_if_fail (root != NULL, 0);

  if (GTS_IS_SPLIT (root->v1)) {
    tmp_height = gts_split_height (GTS_SPLIT (root->v1));
    if (tmp_height > height)
      height = tmp_height;
  }
  if (GTS_IS_SPLIT (root->v2)) {
    tmp_height = gts_split_height (GTS_SPLIT (root->v2));
    if (tmp_height > height)
      height = tmp_height;
  }

  return height + 1;
}

static gboolean list_array_are_identical (GSList * list, 
					  gpointer * array,
					  gpointer excluded)
{
  while (list) {
    gpointer data = list->data;
    if (data != excluded) {
      gboolean found = FALSE;
      gpointer * a = array;
      
      while (!found && *a)
	if (*(a++) == data)
	  found = TRUE;
      if (!found)
	return FALSE;
    }
    list = list->next;
  }
  return TRUE;
}

#ifndef NEW
gboolean gts_split_is_collapsable (GtsSplit * vs)
{
  guint i;
  GtsSplitCFace * cf;
  GtsVertex * v1, * v2;
  GtsEdge * e;

  g_return_val_if_fail (vs != NULL, FALSE);

  v1 = GTS_SPLIT_V1 (vs);
  v2 = GTS_SPLIT_V2 (vs);
  g_return_val_if_fail ((e = GTS_EDGE (gts_vertices_are_connected (v1, v2))),
			FALSE);

#ifdef DYNAMIC_SPLIT
  if (!gts_edge_collapse_is_valid (e))
    return FALSE;
#else 
  i = vs->ncf;
  cf = vs->cfaces;
  while (i--) {
    GtsTriangle * t = GTS_TRIANGLE (cf->f);
    GtsEdge * e1 = t->e1, * e2 = t->e2, * e3 = t->e3;

    ROTATE_ORIENT (e, e1, e2, e3);
    if (SEGMENT_USE_VERTEX (GTS_SEGMENT (e1), v2)) {
      e3 = e1; e1 = e2; e2 = e3;
    }

    if (!list_array_are_identical (e1->triangles, (gpointer *) cf->a1, t))
      return FALSE;
    if (!list_array_are_identical (e2->triangles, (gpointer *) cf->a2, t))
      return FALSE;
    
    cf++;
  }
#endif
  return TRUE;
}
#endif /* not NEW */

#ifdef DEBUG_HEXPAND
static guint expand_level = 0;

static void expand_indent (FILE * fptr)
{
  guint i = expand_level;
  while (i--)
    fputc (' ', fptr);
}
#endif

/**
 * gts_hsplit_force_expand:
 * @hs: a #GtsHSplit.
 * @hsurface: a #GtsHSurface.
 *
 * Forces the expansion of @hs by first expanding all its dependencies not
 * already expanded.
 */
void gts_hsplit_force_expand (GtsHSplit * hs,
			      GtsHSurface * hsurface)
{
  guint i;
  GtsSplitCFace * cf;

  g_return_if_fail (hs != NULL);
  g_return_if_fail (hsurface != NULL);
  g_return_if_fail (hs->nchild == 0);

#ifdef DEBUG_HEXPAND
  expand_level += 2;
#endif

  if (hs->parent && hs->parent->nchild == 0) {
#ifdef DEBUG_HEXPAND
    expand_indent (stderr); 
    fprintf (stderr, "expand parent %p\n", hs->parent);
#endif
    gts_hsplit_force_expand (hs->parent, hsurface);
  }

  i = GTS_SPLIT (hs)->ncf;
  cf = GTS_SPLIT (hs)->cfaces;
  while (i--) {
    GtsTriangle ** j, * t;

    j = cf->a1;
    while ((t = *(j++)))
      if (IS_CFACE (t)) {
#ifdef DEBUG_HEXPAND
	expand_indent (stderr); 
	fprintf (stderr, "expand a1: cf->f: %p t: %p parent_split: %p\n", 
		 cf->f,
		 t,
		 GTS_HSPLIT (CFACE (t)->parent_split));
#endif
	gts_hsplit_force_expand (GTS_HSPLIT (CFACE (t)->parent_split),
				 hsurface);
#ifdef DEBUG_HEXPAND
	g_assert (!IS_CFACE (t));
#endif
      }
    j = cf->a2;
    while ((t = *(j++)))
      if (IS_CFACE (t)) {
#ifdef DEBUG_HEXPAND
	expand_indent (stderr); 
	fprintf (stderr, "expand a2: cf->f: %p t: %p parent_split: %p\n", 
		 cf->f,
		 t,
		 GTS_HSPLIT (CFACE (t)->parent_split));
#endif
	gts_hsplit_force_expand (GTS_HSPLIT (CFACE (t)->parent_split),
				 hsurface);
      }
    cf++;
  }

  gts_hsplit_expand (hs, hsurface);

#ifdef DEBUG_HEXPAND
  expand_level -= 2; 
  expand_indent (stderr); 
  fprintf (stderr, "%p expanded\n", hs);
#endif
}

static void index_object (GtsObject * o, guint * n)
{
  o->reserved = GUINT_TO_POINTER ((*n)++);
}

static void index_face (GtsFace * f, gpointer * data)
{
  guint * nf = data[1];

  g_hash_table_insert (data[0], f, GUINT_TO_POINTER ((*nf)++));
}

/**
 * gts_psurface_write:
 * @ps: a #GtsPSurface.
 * @fptr: a file pointer.
 *
 * Writes to @fptr a GTS progressive surface description.
 */
void gts_psurface_write (GtsPSurface * ps, FILE * fptr)
{
  guint nv = 1;
  guint nf = 1;
  GHashTable * hash;
  gpointer data[2];

  g_return_if_fail (ps != NULL);
  g_return_if_fail (fptr != NULL);
  g_return_if_fail (GTS_PSURFACE_IS_CLOSED (ps));

  while (gts_psurface_remove_vertex (ps))
    ;

  GTS_POINT_CLASS (ps->s->vertex_class)->binary = FALSE;
  gts_surface_write (ps->s, fptr);
  
  gts_surface_foreach_vertex (ps->s, (GtsFunc) index_object, &nv);
  hash = g_hash_table_new (NULL, NULL);
  data[0] = hash;
  data[1] = &nf;
  gts_surface_foreach_face (ps->s, (GtsFunc) index_face, data);

  fprintf (fptr, "%u\n", ps->split->len);
  while (ps->pos) {
    GtsSplit * vs = g_ptr_array_index (ps->split, --ps->pos);
    GtsSplitCFace * scf = vs->cfaces;
    GtsVertex * v1, * v2;
    guint i = vs->ncf;

    fprintf (fptr, "%u %u",
	     GPOINTER_TO_UINT (GTS_OBJECT (vs->v)->reserved),
	     vs->ncf);
    if (GTS_OBJECT (vs)->klass->write)
      (*GTS_OBJECT (vs)->klass->write) (GTS_OBJECT (vs), fptr);
    fputc ('\n', fptr);

    v1 = GTS_IS_SPLIT (vs->v1) ? GTS_SPLIT (vs->v1)->v : GTS_VERTEX (vs->v1);
    GTS_OBJECT (v1)->reserved = GUINT_TO_POINTER (nv++);
    v2 = GTS_IS_SPLIT (vs->v2) ? GTS_SPLIT (vs->v2)->v : GTS_VERTEX (vs->v2);
    GTS_OBJECT (v2)->reserved = GUINT_TO_POINTER (nv++);

    (*GTS_OBJECT (v1)->klass->write) (GTS_OBJECT (v1), fptr);
    fputc ('\n', fptr);

    (*GTS_OBJECT (v2)->klass->write) (GTS_OBJECT (v2), fptr);
    fputc ('\n', fptr);
    
    while (i--) {
      CFace * cf = CFACE (scf->f);
      GtsTriangle ** a, * t;

      fprintf (fptr, "%u %u",
	       GPOINTER_TO_UINT (g_hash_table_lookup (hash, cf->t)),
	       cf->flags);
      if (GTS_OBJECT_CLASS (ps->s->face_class)->write)
	(*GTS_OBJECT_CLASS (ps->s->face_class)->write) (GTS_OBJECT (cf), fptr);
      fputc ('\n', fptr);

      a = scf->a1;
      while ((t = *(a++)))
	fprintf (fptr, "%u ",
		 GPOINTER_TO_UINT (g_hash_table_lookup (hash, t)));
      fprintf (fptr, "\n");

      a = scf->a2;
      while ((t = *(a++)))
	fprintf (fptr, "%u ",
		 GPOINTER_TO_UINT (g_hash_table_lookup (hash, t)));
      fprintf (fptr, "\n");

      g_hash_table_insert (hash, cf, GUINT_TO_POINTER (nf++));

      scf++;
    }

    gts_split_expand (vs, ps->s, ps->s->edge_class);
  }

  gts_surface_foreach_vertex (ps->s, 
			      (GtsFunc) gts_object_reset_reserved, NULL);
  g_hash_table_destroy (hash);
}

static guint surface_read (GtsSurface * surface, 
			   GtsFile * f,
			   GPtrArray * vertices,
			   GPtrArray * faces)
{
  GtsEdge ** edges;
  guint n, nv, ne, nf;

  g_return_val_if_fail (surface != NULL, 1);
  g_return_val_if_fail (f != NULL, 1);
  
  if (f->type != GTS_INT) {
    gts_file_error (f, "expecting an integer (number of vertices)");
    return f->line;
  }
  nv = atoi (f->token->str);

  gts_file_next_token (f);
  if (f->type != GTS_INT) {
    gts_file_error (f, "expecting an integer (number of edges)");
    return f->line;
  }
  ne = atoi (f->token->str);

  gts_file_next_token (f);
  if (f->type != GTS_INT) {
    gts_file_error (f, "expecting an integer (number of faces)");
    return f->line;
  }
  nf = atoi (f->token->str);

  gts_file_next_token (f);
  if (f->type == GTS_STRING) {
    if (f->type != GTS_STRING) {
      gts_file_error (f, "expecting a string (GtsSurfaceClass)");
      return f->line;
    }
    gts_file_next_token (f);
    if (f->type != GTS_STRING) {
      gts_file_error (f, "expecting a string (GtsFaceClass)");
      return f->line;
    }
    gts_file_next_token (f);
    if (f->type != GTS_STRING) {
      gts_file_error (f, "expecting a string (GtsEdgeClass)");
      return f->line;
    }
    gts_file_next_token (f);
    if (f->type != GTS_STRING) {
      gts_file_error (f, "expecting a string (GtsVertexClass)");
      return f->line;
    }
    if (!strcmp (f->token->str, "GtsVertexBinary"))
      GTS_POINT_CLASS (surface->vertex_class)->binary = TRUE;
    else
      gts_file_first_token_after (f, '\n');
  }
  else
    gts_file_first_token_after (f, '\n');

  g_ptr_array_set_size (vertices, nv);
  g_ptr_array_set_size (faces, nf);
  /* allocate nv + 1 just in case nv == 0 */
  edges = g_malloc ((ne + 1)*sizeof (GtsEdge *));
  
  n = 0;
  while (n < nv && f->type != GTS_ERROR) {
    GtsObject * new_vertex =
      gts_object_new (GTS_OBJECT_CLASS (surface->vertex_class));

    (* GTS_OBJECT_CLASS (surface->vertex_class)->read) (&new_vertex, f);
    if (f->type != GTS_ERROR) {
      if (!GTS_POINT_CLASS (surface->vertex_class)->binary)
	gts_file_first_token_after (f, '\n');
      g_ptr_array_index (vertices, n++) = new_vertex;
    }
    else
      gts_object_destroy (new_vertex);
  }
  if (f->type == GTS_ERROR)
    nv = n;
  if (GTS_POINT_CLASS (surface->vertex_class)->binary)
    gts_file_first_token_after (f, '\n');

  n = 0;
  while (n < ne && f->type != GTS_ERROR) {
    guint p1, p2;

    if (f->type != GTS_INT)
      gts_file_error (f, "expecting an integer (first vertex index)");
    else {
      p1 = atoi (f->token->str);
      if (p1 == 0 || p1 > nv)
	gts_file_error (f, "vertex index `%d' is out of range `[1,%d]'", 
			p1, nv);
      else {
	gts_file_next_token (f);
	if (f->type != GTS_INT)
	  gts_file_error (f, "expecting an integer (second vertex index)");
	else {
	  p2 = atoi (f->token->str);
	  if (p2 == 0 || p2 > nv)
	    gts_file_error (f, "vertex index `%d' is out of range `[1,%d]'", 
			    p2, nv);
	  else {
	    GtsEdge * new_edge =
	      gts_edge_new (surface->edge_class,
			    g_ptr_array_index (vertices, p1 - 1),
			    g_ptr_array_index (vertices, p2 - 1));

	    gts_file_next_token (f);
	    if (f->type != '\n')
	      if (GTS_OBJECT_CLASS (surface->edge_class)->read)
		(*GTS_OBJECT_CLASS (surface->edge_class)->read)
		  ((GtsObject **) &new_edge, f);
	    gts_file_first_token_after (f, '\n');
	    edges[n++] = new_edge;
	  }
	}
      }
    }
  }
  if (f->type == GTS_ERROR)
    ne = n;

  n = 0;
  while (n < nf && f->type != GTS_ERROR) {
    guint s1, s2, s3;

    if (f->type != GTS_INT)
      gts_file_error (f, "expecting an integer (first edge index)");
    else {
      s1 = atoi (f->token->str);
      if (s1 == 0 || s1 > ne)
	gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", 
			s1, ne);
      else {
	gts_file_next_token (f);
	if (f->type != GTS_INT)
	  gts_file_error (f, "expecting an integer (second edge index)");
	else {
	  s2 = atoi (f->token->str);
	  if (s2 == 0 || s2 > ne)
	    gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", 
			    s2, ne);
	  else {
	    gts_file_next_token (f);
	    if (f->type != GTS_INT)
	      gts_file_error (f, "expecting an integer (third edge index)");
	    else {
	      s3 = atoi (f->token->str);
	      if (s3 == 0 || s3 > ne)
		gts_file_error (f, "edge index `%d' is out of range `[1,%d]'", 
				s3, ne);
	      else {
		GtsFace * new_face = gts_face_new (surface->face_class,
						   edges[s1 - 1],
						   edges[s2 - 1],
						   edges[s3 - 1]);

		gts_file_next_token (f);
		if (f->type != '\n')
		  if (GTS_OBJECT_CLASS (surface->face_class)->read)
		    (*GTS_OBJECT_CLASS (surface->face_class)->read)
		      ((GtsObject **) &new_face, f);
		gts_file_first_token_after (f, '\n');
		gts_surface_add_face (surface, new_face);
		g_ptr_array_index (faces, n++) = new_face;
	      }
	    }
	  }
	}
      }
    }
  }

  g_free (edges);

  if (f->type == GTS_ERROR) {
    gts_allow_floating_vertices = TRUE;
    while (nv)
      gts_object_destroy (GTS_OBJECT (g_ptr_array_index (vertices, nv-- - 1)));
    gts_allow_floating_vertices = FALSE;    
    return f->line;
  }

  return 0;
}

/**
 * gts_psurface_open:
 * @klass: a #GtsPSurfaceClass.
 * @s: a #GtsSurface.
 * @split_class: a #GtsSplitClass to use for the #GtsSplit.
 * @f: a #GtsFile.
 *
 * Creates a new #GtsPSurface prepared for input from the file @f 
 * containing a valid GTS representation of a progressive surface. The initial
 * shape of the progressive surface is loaded into @s.
 * 
 * Before being usable as such this progressive surface must be closed using
 * gts_psurface_close(). While open however, the functions
 * gts_psurface_get_vertex_number(), gts_psurface_min_vertex_number() and
 * gts_psurface_max_vertex_number() can still be used.
 *
 * Returns: a new #GtsPSurface or %NULL if there was a format error while
 * reading the file, in which case @f contains information about the error.
 */
GtsPSurface * gts_psurface_open (GtsPSurfaceClass * klass,
				 GtsSurface * s,
				 GtsSplitClass * split_class,
				 GtsFile * f)
{
  GtsPSurface * ps;

  g_return_val_if_fail (klass != NULL, NULL);
  g_return_val_if_fail (s != NULL, NULL);
  g_return_val_if_fail (split_class != NULL, NULL);
  g_return_val_if_fail (f != NULL, NULL);

  ps = GTS_PSURFACE (gts_object_new (GTS_OBJECT_CLASS (klass)));
  ps->s = s;
  ps->split_class = split_class;

  ps->vertices = g_ptr_array_new ();
  ps->faces = g_ptr_array_new ();

  if (surface_read (s, f, ps->vertices, ps->faces)) {
    ps->s = NULL;
    gts_object_destroy (GTS_OBJECT (ps));
    return NULL;
  }

  ps->min = gts_surface_vertex_number (ps->s);
  ps->pos = 0;

  if (f->type == GTS_INT) {
    gint ns = atoi (f->token->str);
    
    if (ns > 0) {
      g_ptr_array_set_size (ps->split, ns);
      gts_file_first_token_after (f, '\n');
    }
  }

  return ps;
}

/**
 * gts_psurface_read_vertex:
 * @ps: a #GtsPSurface prealably created with gts_psurface_open().
 * @fp: a #GtsFile.
 *
 * Reads in one vertex split operation from @fp and performs the expansion.
 *
 * If an error occurs while reading the file, the @error field of @fp is set.
 *
 * Returns: the newly created #GtsSplit or %NULL if no vertex split could be
 * read from @fp.
 */
GtsSplit * gts_psurface_read_vertex (GtsPSurface * ps, GtsFile * fp)
{
  guint nv, ncf;
  GtsSplit * vs, * parent;
  GtsSplitCFace * scf;

  g_return_val_if_fail (ps != NULL, NULL);
  g_return_val_if_fail (fp != NULL, NULL);
  g_return_val_if_fail (!GTS_PSURFACE_IS_CLOSED (ps), NULL);
  
  if (ps->pos >= ps->split->len)
    return NULL;

  if (fp->type == GTS_NONE)
    return NULL;
  if (fp->type != GTS_INT) {
    gts_file_error (fp, "expecting an integer (vertex index)");
    return NULL;
  }
  nv = atoi (fp->token->str);
  if (nv == 0 || nv > ps->vertices->len) {
    gts_file_error (fp, "vertex index `%d' is out of range `[1,%d]'",
		    nv, ps->vertices->len);
    return NULL;
  }

  gts_file_next_token (fp);
  if (fp->type != GTS_INT) {
    gts_file_error (fp, "expecting an integer (ncf)");
    return NULL;
  }
  ncf = atoi (fp->token->str);
  
  vs = GTS_SPLIT (gts_object_new (GTS_OBJECT_CLASS (ps->split_class)));

  vs->v = g_ptr_array_index (ps->vertices, nv - 1);
  vs->v1 = vs->v2 = NULL;
  vs->cfaces = NULL;
  vs->ncf = 0;

  gts_file_next_token (fp);
  if (fp->type != '\n')
    if (GTS_OBJECT (vs)->klass->read)
      (* GTS_OBJECT (vs)->klass->read) ((GtsObject **) &vs, fp);
  gts_file_first_token_after (fp, '\n');

  if (fp->type != GTS_ERROR) {
    vs->v1 = gts_object_new (GTS_OBJECT_CLASS (ps->s->vertex_class));
    (* GTS_OBJECT_CLASS (ps->s->vertex_class)->read) (&(vs->v1), fp);
    if (fp->type != GTS_ERROR) {
      vs->v1->reserved = vs;
      g_ptr_array_add (ps->vertices, vs->v1);

      gts_file_first_token_after (fp, '\n');
      
      vs->v2 = gts_object_new (GTS_OBJECT_CLASS (ps->s->vertex_class));
      (*GTS_OBJECT_CLASS (ps->s->vertex_class)->read) (&(vs->v2), fp);
      if (fp->type != GTS_ERROR) {
	vs->v2->reserved = vs;
	g_ptr_array_add (ps->vertices, vs->v2);
	gts_file_first_token_after (fp, '\n');
      }
    }
  }

  if (fp->type != GTS_ERROR) {
    scf = vs->cfaces = g_malloc (sizeof (GtsSplitCFace)*ncf);
    while (fp->type != GTS_ERROR && ncf--) {
      guint it, flags;
      GtsFace * f;
      CFace * cf;
      GPtrArray * a;

      if (fp->type != GTS_INT)
	gts_file_error (fp, "expecting an integer (face index)");
      else {
	it = atoi (fp->token->str);
	if (it == 0 || it > ps->faces->len)
	  gts_file_error (fp, "face index `%d' is out of range `[1,%d]'",
			  it, ps->faces->len);
	else {
	  gts_file_next_token (fp);
	  if (fp->type != GTS_INT)
	    gts_file_error (fp, "expecting an integer (flags)");
	  else {
	    flags = atoi (fp->token->str);
	    f = 
	      GTS_FACE (gts_object_new (GTS_OBJECT_CLASS (ps->s->face_class)));

	    gts_file_next_token (fp);
	    if (fp->type != '\n')
	      if (GTS_OBJECT (f)->klass->read)
		(*GTS_OBJECT (f)->klass->read) ((GtsObject **) &f, fp);
	    gts_file_first_token_after (fp, '\n');
	    if (fp->type != GTS_ERROR) {
	      scf->f = f;

	      cf = (CFace *) f;
	      GTS_OBJECT (cf)->klass = GTS_OBJECT_CLASS (cface_class ());
	      cf->parent_split = vs;
	      cf->t = g_ptr_array_index (ps->faces, it - 1);
	      cf->flags = flags;
	  
	      a = g_ptr_array_new ();
	      do {
		if (fp->type != GTS_INT)
		  gts_file_error (fp, "expecting an integer (face index)");
		else {
		  it = atoi (fp->token->str);
		  if (it > ps->faces->len)
		    gts_file_error (fp, 
				    "face index `%d' is out of range `[1,%d]'",
				    it, ps->faces->len);
		  else {
		    g_ptr_array_add (a, g_ptr_array_index (ps->faces, 
							   it - 1));
		    gts_file_next_token (fp);
		  }
		}
	      } while (fp->type != GTS_ERROR && fp->type != '\n');
	      gts_file_first_token_after (fp, '\n');
	      g_ptr_array_add (a, NULL);
	      scf->a1 = (GtsTriangle **) a->pdata;
	      g_ptr_array_free (a, FALSE);

	      if (fp->type != GTS_ERROR) {
		a = g_ptr_array_new ();
		do {
		  if (fp->type != GTS_INT)
		    gts_file_error (fp, "expecting an integer (face index)");
		  else {
		    it = atoi (fp->token->str);
		    if (it > ps->faces->len)
		      gts_file_error (fp, 
				   "face index `%d' is out of range `[1,%d]'",
				      it, ps->faces->len);
		    else {
		      g_ptr_array_add (a, g_ptr_array_index (ps->faces, 
							     it - 1));
		      gts_file_next_token (fp);
		    }
		  }
		} while (fp->type != GTS_ERROR && fp->type != '\n');
		gts_file_first_token_after (fp, '\n');
		g_ptr_array_add (a, NULL);
		scf->a2 = (GtsTriangle **) a->pdata;
		g_ptr_array_free (a, FALSE);
		
		g_ptr_array_add (ps->faces, f);
	      
		vs->ncf++;
		scf++;
	      }
	    }
	  }
	}
      }
    }
  }

  if (fp->type != GTS_ERROR) {
    if ((parent = GTS_OBJECT (vs->v)->reserved)) {
      GTS_OBJECT (vs->v)->reserved = NULL;
      if (parent->v1 == GTS_OBJECT (vs->v))
	parent->v1 = GTS_OBJECT (vs);
      else {
	g_assert (parent->v2 == GTS_OBJECT (vs->v));
	parent->v2 = GTS_OBJECT (vs);
      }
    }
    g_ptr_array_index (ps->split, ps->pos++) = vs;
    gts_split_expand (vs, ps->s, ps->s->edge_class);

    return vs;
  }

  if (vs->v1) gts_object_destroy (vs->v1);
  if (vs->v2) gts_object_destroy (vs->v2);
  gts_object_destroy (GTS_OBJECT (vs));
  
  return NULL;
}

/**
 * gts_psurface_close:
 * @ps: a #GtsPSurface prealably created with gts_psurface_open().
 *
 * Closes a progressive surface.
 */
void gts_psurface_close (GtsPSurface * ps)
{
  g_return_if_fail (ps != NULL);
  g_return_if_fail (!GTS_PSURFACE_IS_CLOSED (ps));

  g_ptr_array_free (ps->vertices, TRUE);
  g_ptr_array_free (ps->faces, TRUE);
  ps->faces = ps->vertices = NULL;
  
  gts_surface_foreach_vertex (ps->s, 
			      (GtsFunc) gts_object_reset_reserved, NULL);
  if (ps->pos > 0)
    g_ptr_array_set_size (ps->split, ps->pos);
  if (ps->split->len > 1) {
    guint i, half = ps->split->len/2, n = ps->split->len - 1;
    
    for (i = 0; i < half; i++) {
      gpointer p1 = g_ptr_array_index (ps->split, i);
      gpointer p2 = g_ptr_array_index (ps->split, n - i);
      g_ptr_array_index (ps->split, n - i) = p1;
      g_ptr_array_index (ps->split, i) = p2;
    }
  }
  ps->pos = 0;
}


syntax highlighted by Code2HTML, v. 0.9.1