/* edge.c: routines for manipulating BREP_EDGEs and BREP_WINGs */

#include "brep.h"
#include "private.h"
#include "pools.h"

#ifndef NOPOOLS
static POOL *edgePool = (POOL *)NULL;
#define NEWEDGE()  	(BREP_EDGE *)NewPoolCell(sizeof(BREP_EDGE), 0, "brep edges", &edgePool)
#define DISPOSEEDGE(ptr) Dispose((unsigned char *)(ptr), &edgePool)
#else /* NOPOOLS */
#define NEWEDGE()	(BREP_EDGE *)Alloc(sizeof(BREP_EDGE))
#define DISPOSEEDGE(ptr) Free((char *)ptr, sizeof(BREP_EDGE))
#endif /* NOPOOLS */

/* callback functions for manipulating BREP_EDGEs */
static BREP_CALLBACK_FUNC brep_create_edge_callback = (BREP_CALLBACK_FUNC)NULL,
                          brep_close_edge_callback = (BREP_CALLBACK_FUNC)NULL,
                          brep_destroy_edge_callback = (BREP_CALLBACK_FUNC)NULL;

/* set the CreateEdge callback routine: called when the edge is created, normally
 * when it is inserted into a first contour. */
BREP_CALLBACK_FUNC BrepSetCreateEdgeCallback(BREP_CALLBACK_FUNC func)
{
  BREP_CALLBACK_FUNC oldfunc = brep_create_edge_callback;
  brep_create_edge_callback = func;
  return oldfunc;
}

/* set the CloseEdge callback routine: called when an edge is inserted into a second
 * contour. */
BREP_CALLBACK_FUNC BrepSetCloseEdgeCallback(BREP_CALLBACK_FUNC func)
{
  BREP_CALLBACK_FUNC oldfunc = brep_close_edge_callback;
  brep_close_edge_callback = func;
  return oldfunc;
}

/* set the DestroyEdge callback routine */
BREP_CALLBACK_FUNC BrepSetDestroyEdgeCallback(BREP_CALLBACK_FUNC func)
{
  BREP_CALLBACK_FUNC oldfunc = brep_destroy_edge_callback;
  brep_destroy_edge_callback = func;
  return oldfunc;
}

/* Creates an edge connecting vertex1 to vertex2. It is not
 * connected to a contour. It calls the CreateEdge callback
 * if set. */
BREP_EDGE *BrepCreateEdge(BREP_VERTEX *vertex1, BREP_VERTEX *vertex2, void *client_data)
{
  BREP_EDGE *edge;

  edge = NEWEDGE();
  edge->wing[0].edge = edge->wing[1].edge = edge;

  edge->wing[0].vertex = vertex1;
  edge->wing[1].vertex = vertex2;

  /* initialize the wings: they still need to be connected with a contour */
  edge->wing[0].contour = edge->wing[1].contour = (BREP_CONTOUR *)NULL;
  edge->wing[0].prev = edge->wing[0].next = 
  edge->wing[1].prev = edge->wing[1].next = (BREP_WING *)NULL;
  
  /* call the CreateEdge callback if set */
  edge->client_data = client_data;
  if (brep_create_edge_callback)
    edge->client_data = brep_create_edge_callback(edge);

  return edge;
}

/* Creates a wing from vertex1 to vertex2. First looks for an
 * existing edge connecting the vertices. Creates such an edge if 
 * if there is not yet one. Returns a pointer to an unused wing in
 * the edge. The wing still needs to be connected in a contour. */
BREP_WING *BrepCreateWingWithoutContour(BREP_VERTEX *vertex1, BREP_VERTEX *vertex2, void *client_data)
{
  BREP_EDGE *edge;
  BREP_WING *wing;

  /* first look if there is already an edge connecting the two 
   * vertices */
  edge = BrepFindEdge(vertex1, vertex2);
  if (!edge) {	/* there was not yet such an edge */
    /* create such an edge */
    edge = BrepCreateEdge(vertex1, vertex2, client_data);

    /* the wing to be used is the first wing --- vertex1 is the
     * startvertex for the wing to be created */
    wing = &edge->wing[0];
  } else {	/* there was already such an edge */
    if (edge->wing[0].vertex == vertex1)	/* vertex1 is startvertex in the first wing? */
      wing = &(edge->wing[0]);
    else if (edge->wing[1].vertex == vertex1)	/* vertex1 is startvertex in the second wing */
      wing = &(edge->wing[1]);
    else {					/* shouldn't happen */
      BrepFatal(edge->client_data, "BrepCreateWingWithoutContour", "something impossible wrong here");
      return (BREP_WING *)NULL;  /* actually, it shouldn't matter what is returned here */
    }
      
    /* is the wing found already used in a contour? */
    if (wing->contour) {
      BrepInfo(edge->client_data, "BrepCreateWing", "attempt to use an edge two times in the same sense - creating a duplicate edge");
      edge = BrepCreateEdge(vertex1, vertex2, client_data);
      wing = &edge->wing[0];
    }
  }

  return wing;
}

/* connect the wing as last wing in the contour */
void BrepConnectWingToContour(BREP_WING *wing, BREP_CONTOUR *contour)
{
  wing->contour = contour;

  if (!contour->wings) {	/* first wing in contour */
    wing->next = wing->prev = wing;
    contour->wings = wing;
  } else {			/* not first wing */
    wing->next = contour->wings;
    wing->prev = contour->wings->prev;
    wing->prev->next = wing->next->prev = wing;
  }
}

/* various actions to be performed when an edge has been specified 
 * completely, i.e. it is enclosed in two contours. */
void BrepCloseEdge(BREP_EDGE *edge)
{
  if (brep_close_edge_callback)
    edge->client_data = brep_close_edge_callback(edge);
}

/* creates a wing into the contour from vertex1 to vertex2. The 
 * inside of the face bounded by the contour is supposed to be on the 
 * left side. The wing being created is supposed to be the new last
 * one in a contour, i.e. its starting vertex 'vertex1' must be the 
 * endvertex of the most recently created wing in the contour (if 
 * non-empty). It is the responsibility of the user to close the
 * contour, i.e., the last edge in a contour should connect to the 
 * first one. */
BREP_WING *BrepCreateWing(BREP_VERTEX *vertex1, BREP_VERTEX *vertex2, BREP_CONTOUR *contour, void *client_data)
{
  BREP_WING *wing;

  if (contour->wings && BrepEdgeOtherWing(contour->wings->prev)->vertex != vertex1) {
    BrepError(contour->client_data, "BrepCreateWing", "the starting point of a new wing should be the endpoint of the previous wing");
    return (BREP_WING *)NULL;
  }

  /* first create a wing that still needs to be connected with a contour */
  wing = BrepCreateWingWithoutContour(vertex1, vertex2, client_data);

  /* complain if the edge is not used in opposite sense in both directions */
  if (wing->vertex != vertex1) 
      BrepError(wing->edge->client_data, "BrepCreateWing", "edge should be used in opposite sense by the contours sharing it");

  /* connect the wing to the contour */
  BrepConnectWingToContour(wing, contour);

  /* connect the wing to its startvertex after it has been connected in a 
   * contour */
  BrepConnectWingToVertex(wing);

  /* close the edge */
  BrepCloseEdge(wing->edge);

  return wing;
}

/* Splits only one wing. If a full edge is to be split, also its
 * wing in the other contour (if any) needs to be split at the same vertex.
 * (BrepSplitEdge does this).
 * Returns the wing leaving at the inserted vertex. */
BREP_WING *BrepSplitWing(BREP_WING *wing, BREP_VERTEX *vertex)
{
  BREP_WING *wing1, *wing2;

  /* create two new wings, not connected to the contour */
  wing1 = BrepCreateWingWithoutContour(wing->vertex, vertex, NULL);
  wing2 = BrepCreateWingWithoutContour(vertex, wing->next->vertex, NULL);
  
  /* now connect the pair of consecutive wings to the contour */    
  wing1->contour = wing2->contour = wing->contour;
  
  wing1->next = wing2;
  wing2->prev = wing1;

  if (wing->prev != wing) {	/* wing is not the only wing in the contour */
    wing1->prev = wing->prev;
    wing2->next = wing->next;
  } else {			/* wing is the only wing in the contour */
    wing1->prev = wing2;
    wing2->next = wing1;
  }

  /* destroy the original wing, leaves the contour with a hole in it or
   * even a contour without wings ... */
  BrepDestroyWing(wing);
  
  /* ... fill the hole */
  wing1->prev->next = wing1;
  wing2->next->prev = wing2;

  /* and make sure the contour knows about the news wings. */
  if (!wing1->contour->wings)
    wing1->contour->wings = wing1;

  /* finally, connect the new wings to their startvertex */
  BrepConnectWingToVertex(wing1);
  BrepConnectWingToVertex(wing2);  
  
  /* close the edges */
  BrepCloseEdge(wing1->edge);
  BrepCloseEdge(wing2->edge);

  return wing2;
}

/* Splits an edge in two at the specified vertex (at least: 
 * topologically; the new vertex doesn't have to be collinear
 * with the endpoints of the edge to be split, but you should make
 * sure that no contours are created that intersect in other
 * places than at vertices and along edges. The contours
 * to which the edge belongs are assumed to be closed loops. */
void BrepSplitEdge(BREP_EDGE *edge, BREP_VERTEX *vertex)
{
  int do_wing0, do_wing1;

  do_wing0 = (edge->wing[0].contour != (BREP_CONTOUR *)NULL);
  do_wing1 = (edge->wing[1].contour != (BREP_CONTOUR *)NULL);
  if (do_wing0)
    BrepSplitWing(&(edge->wing[0]), vertex);
  if (do_wing1)
    BrepSplitWing(&(edge->wing[1]), vertex);
}

/* Joins two wings at there common endpoint. The wings are supposed to 
 * be consecutive wings, i.e. wing1->next = wing2 (and wing2->prev = wing1)
 * in a closed contour. The common vertex is wing2->vertex,
 * the start vertex of the second wing (which is supposed to be
 * the endpoint of the first wing as well). */
BREP_WING *BrepJoinWings(BREP_WING *wing1, BREP_WING *wing2)
{
  BREP_WING *wing;

  /* create a new wing connecting the startpoint of wing1 with the endpoint of
   * wing2 */
  wing = BrepCreateWingWithoutContour(wing1->vertex, wing2->next->vertex, NULL);
    
  /* connect it to the contour */
  wing->contour = wing1->contour;

  if (wing1->prev != wing2) {	/* the two wings were not the only two in the contour */
    wing->prev = wing1->prev;
    wing->next = wing2->next;
  } else {			/* the two wings were the only two in the contour */
    wing->prev = wing->next = wing;      
  }

  /* destroy the two wings, this leaves a hole in the contour, or if the wings
   * were the only two, a contour without wings ... */
  BrepDestroyWing(wing1);
  BrepDestroyWing(wing2);
    
  /* ... fill the hole */
  wing->prev->next = wing;
  wing->next->prev = wing;
  
  /* make sure the contour knows about the new wing */
  if (!wing->contour->wings)
    wing->contour->wings = wing;

  /* connect the new wing to its starvertex */
  BrepConnectWingToVertex(wing);

  /* close the edge */
  BrepCloseEdge(wing->edge);

  return wing;
}

/* Join two edges at their common endpoint. The edges must be used
 * in the same contours and must share an endpoint. */
void BrepJoinEdges(BREP_EDGE *edge1, BREP_EDGE *edge2)
{
  int do_wing0, do_wing1;

  do_wing0 = (edge1->wing[0].contour != (BREP_CONTOUR *)NULL);
  do_wing1 = (edge1->wing[1].contour != (BREP_CONTOUR *)NULL);

  /* first join the wings on the edge1->wing[0] side */
  if (do_wing0) {
    if (edge1->wing[0].next == &(edge2->wing[0]))
      BrepJoinWings(&(edge1->wing[0]), &(edge2->wing[0]));
    else if (edge1->wing[0].next == &(edge2->wing[1]))
      BrepJoinWings(&(edge1->wing[0]), &(edge2->wing[1]));
    else if (edge1->wing[0].prev == &(edge2->wing[0]))
      BrepJoinWings(&(edge2->wing[0]), &(edge1->wing[0]));
    else if (edge1->wing[0].prev == &(edge2->wing[1]))
      BrepJoinWings(&(edge2->wing[1]), &(edge1->wing[0]));
    else
      BrepError(edge1->client_data, "BrepJoinEdges", "the two edges don't fit on the first side");
  }

  /* then join the wings on the edge1->wing[1] side */
  if (do_wing1) {
    if (edge1->wing[1].next == &(edge2->wing[0]))
      BrepJoinWings(&(edge1->wing[1]), &(edge2->wing[0]));
    else if (edge1->wing[1].next == &(edge2->wing[1]))
      BrepJoinWings(&(edge1->wing[1]), &(edge2->wing[1]));
    else if (edge1->wing[1].prev == &(edge2->wing[0]))
      BrepJoinWings(&(edge2->wing[0]), &(edge1->wing[1]));
    else if (edge1->wing[1].prev == &(edge2->wing[1]))
      BrepJoinWings(&(edge2->wing[1]), &(edge1->wing[1]));
    else
      BrepError(edge1->client_data, "BrepJoinEdges", "the two edges don't fit on the second side");
  }
}

/* Replace the starting vertex of 'wing' by 'newvertex'. Assumes
 * that 'wing' is correctly connected in a contour and it is not
 * the only wing (that means: the contour is not a selfloop).
 * Returns a pointer to the new wing, which is leaving at 'newvertex'. */
BREP_WING *BrepWingReplaceVertex(BREP_WING *wing, BREP_VERTEX *newvertex)
{
  BREP_WING *wing1, *wing2;

  if (!wing->prev) {
    BrepError(wing->edge->client_data, "BrepWingReplaceVertex", "wing should be properly connected to a contour");
    return (BREP_WING *)NULL;
  }

  if (wing->prev == wing) {
    BrepError(wing->edge->client_data, "BrepWingReplaceVertex", "wing should not be the only wing in the contour");
    return (BREP_WING *)NULL;
  }

  /* create two new wings that will replace wing->prev and wing */
  wing1 = BrepCreateWingWithoutContour(wing->prev->vertex, newvertex, NULL);
  wing2 = BrepCreateWingWithoutContour(newvertex, wing->next->vertex, NULL);

  /* connect the pair of new wings to the contour */
  wing1->contour = wing2->contour = wing->contour;

  wing1->next = wing2;
  wing2->prev = wing1;

  if (wing->prev->prev != wing) {
    /* there are more than two wings in the contour */
    wing1->prev = wing->prev->prev;
    wing2->next = wing->next;
  } else {
    /* there are only two wings in the contour: wing and its predecessor,
     * and they will be destroyed. */
    wing1->prev = wing2;
    wing2->next = wing1;
  }

  /* destroy the original wing and its predecessor in the contour, leaves 
   * the contour with a hole in it or even a contour without wings ... */
  BrepDestroyWing(wing->prev);
  BrepDestroyWing(wing);

  /* ... fill the hole */
  wing1->prev->next = wing1;
  wing2->next->prev = wing2;

  /* and make sure the contour knows about the news wings. */
  if (!wing1->contour->wings)
    wing1->contour->wings = wing1;

  /* finally, connect the new wings to their startvertex */
  BrepConnectWingToVertex(wing1);
  BrepConnectWingToVertex(wing2);  
  
  /* close the edges */
  BrepCloseEdge(wing1->edge);
  BrepCloseEdge(wing2->edge);

  return wing2;  
}

/* returns the other wing in the edge */
BREP_WING *BrepEdgeOtherWing(BREP_WING *wing)
{
  return (wing == &(wing->edge->wing[0]) ? 
	  &(wing->edge->wing[1]) : 
	  &(wing->edge->wing[0])  );
}

/* iterate over a ring of wings */
void BrepIterateWings(BREP_WING *wings, void (*func)(BREP_WING *))
{
  BREP_WING *first_wing, *wing, *next;

  if (wings) {
    next = first_wing = wings;
    do {
      wing = next;
      next = wing->next;
      func(wing);
    } while (next != first_wing);
  }
}

/* release all storage associated with an edge and its vertices
 * if not used in other edges as well. The given edge must already
 * be disconnected from its contours. */
void BrepDestroyEdge(BREP_EDGE *edge)
{
  /* check if the edge is still being used in a contour. If
   * so, don't destroy the edge. */
  if (edge->wing[0].contour || edge->wing[1].contour)
    return;

  /* notify the user if a DestroyEdge callback is set */
  if (brep_destroy_edge_callback)
    brep_destroy_edge_callback(edge);

  /* dispose of the BREP_EDGE structure itself (inverse of 
   * BrepCreateEdgeWithoutVertices() */
  DISPOSEEDGE(edge);  
}

/* Disconnect the wing from the contour. Use with care: the contour
 * might contain holes after disconnecting the wing! wing->next
 * and wing->prev are left untouched as these fields are needed
 * when disconnecting an edge from its vertices. */
void BrepDisconnectWingFromContour(BREP_WING *wing)
{
  BREP_CONTOUR *contour = wing->contour;

  if (!contour || !wing->next || !wing->prev) 
    BrepError(wing->edge->client_data, "BrepDisconnectWingFromContour", "wing improperly connected to contour");

  if (contour && contour->wings == wing) {	/* the wing is the first wing 
						 * in the contour */
    if (wing->next == wing)			/* it is the only wing */
      contour->wings = (BREP_WING *)NULL;
    else 		/* make the next wing the first wing of the contour */
      contour->wings = wing->next;
  }

  /* endpoint of wing->prev will in general not be the same as
   * startpoint of wing->next! A hole might be created in the contour. */
  if (wing->next)
    wing->next->prev = wing->prev;
  if (wing->prev)
    wing->prev->next = wing->next;  

  wing->contour = (BREP_CONTOUR *)NULL;
  wing->next = wing->prev = (BREP_WING *)NULL;
}

/* remove a wing from a contour, release the storage associated with the
 * edge and its vertices if it is not used in other contours as well. 
 * Inverse of BrepCreateWing(), but also destroys unused vertices. */
void BrepDestroyWing(BREP_WING *wing)
{
  /* first disconnect the wing from its starting vertex */
  BrepDisconnectWingFromVertex(wing);

  /* then disconnect the wing from its contour */
  BrepDisconnectWingFromContour(wing);

  /* destroy the edge if not being used in any contour anymore
   * (BrepDestroyEdge already checks for this). */
  BrepDestroyEdge(wing->edge);
}

