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

/* GtsGNodeSplit */

static void gnode_split_destroy (GtsObject * object)
{
  GtsGNodeSplit * ns = GTS_GNODE_SPLIT (object);

  if (gts_container_size (GTS_CONTAINER (ns->n)) == 0) {
    g_assert (GTS_SLIST_CONTAINEE (ns->n)->containers == NULL);
    gts_object_destroy (GTS_OBJECT (ns->n));
  }
  else {
    /* GtsGNode * n1 = GTS_GNODE_SPLIT_N1 (ns); */
    /* GtsGNode * n2 = GTS_GNODE_SPLIT_N2 (ns); */

    g_warning ("Memory deallocation for GtsGNodeSplit not fully implemented yet: memory leak!");
  }

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

static void gnode_split_class_init (GtsGNodeSplitClass * klass)
{
  GTS_OBJECT_CLASS (klass)->destroy = gnode_split_destroy;
}

static void gnode_split_init (GtsGNodeSplit * ns)
{
  ns->n = NULL;
  ns->n1 = ns->n2 = NULL;
}

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

  if (klass == NULL) {
    GtsObjectClassInfo gnode_split_info = {
      "GtsGNodeSplit",
      sizeof (GtsGNodeSplit),
      sizeof (GtsGNodeSplitClass),
      (GtsObjectClassInitFunc) gnode_split_class_init,
      (GtsObjectInitFunc) gnode_split_init,
      (GtsArgSetFunc) NULL,
      (GtsArgGetFunc) NULL
    };
    klass = gts_object_class_new (gts_object_class (), &gnode_split_info);
  }

  return klass;
}

/**
 * gts_gnode_split_new:
 * @klass: a #GtsGNodeSplitClass.
 * @n: a #GtsGNode.
 * @n1: a #GtsGNodeSplit or #GtsGNode.
 * @n2: a #GtsGNodeSplit or #GtsGNode.
 *
 * Creates a new #GtsGNodeSplit which would collapse @n1 and @n2 into
 * @n. The collapse itself is not performed.
 *
 * Returns: the new #GtsGNodeSplit.
 */
GtsGNodeSplit * gts_gnode_split_new (GtsGNodeSplitClass * klass,
				     GtsGNode * n, 
				     GtsObject * n1,
				     GtsObject * n2)
{
  GtsGNodeSplit * ns;

  g_return_val_if_fail (klass != NULL, NULL);
  g_return_val_if_fail (n != NULL, NULL);
  g_return_val_if_fail (GTS_IS_GNODE_SPLIT (n1) || GTS_IS_GNODE (n1), NULL);
  g_return_val_if_fail (GTS_IS_GNODE_SPLIT (n2) || GTS_IS_GNODE (n2), NULL);

  ns = GTS_GNODE_SPLIT (gts_object_new (GTS_OBJECT_CLASS (klass)));
  ns->n = n;
  ns->n1 = n1;
  ns->n2 = n2;

  return ns;
}

static void connect_edge (GtsGEdge * e, gpointer * data)
{
  GtsGNode * n = data[0];
  GtsGNode * n1 = data[1];
  GtsGNode * n2 = data[2];

  if (GTS_OBJECT (e)->reserved || /* edge is disconnected */
      gts_gedge_connects (e, n1, n2))
    return;
  if (e->n1 == n1 || e->n1 == n2)
    e->n1 = n;
  else if (e->n2 == n1 || e->n2 == n2)
    e->n2 = n;
  else
    g_assert_not_reached ();
  gts_container_add (GTS_CONTAINER (n), GTS_CONTAINEE (e));
}

/**
 * gts_gnode_split_collapse:
 * @ns: a #GtsGNodeSplit.
 * @g: a #GtsGraph.
 * @klass: a #GtsWGEdgeClass.
 *
 * Collapses the node split @ns. Any new edge created during the
 * process will be of class @klass.  
 */
void gts_gnode_split_collapse (GtsGNodeSplit * ns,
			       GtsGraph * g,
			       GtsWGEdgeClass * klass)
{
  GtsGNode * n1, * n2;
  GSList * i;
  gpointer data[3];

  g_return_if_fail (ns != NULL);
  g_return_if_fail (g != NULL);
  g_return_if_fail (gts_container_size (GTS_CONTAINER (ns->n)) == 0);

  n1 = GTS_GNODE_SPLIT_N1 (ns);
  n2 = GTS_GNODE_SPLIT_N2 (ns);

  /* look for triangles */
  i = GTS_SLIST_CONTAINER (n1)->items;
  while (i) {
    GtsGEdge * e13 = i->data;
    GtsGNode * n3 = GTS_GNODE_NEIGHBOR (n1, e13);
    if (n3 != n2) {
      GSList * j = GTS_SLIST_CONTAINER (n3)->items;
      while (j) {
	GtsGEdge * e32 = j->data;
	GSList * next = j->next;
	GtsGNode * n4 = GTS_GNODE_NEIGHBOR (n3, e32);
	if (n4 == n2) { /* found triangle n1 (e13) n3 (e32) n2 */
	  gts_wgedge_new (klass, ns->n, n3,
			  gts_gedge_weight (e13) + gts_gedge_weight (e32));
	  GTS_OBJECT (e13)->reserved = n3;
	  GTS_OBJECT (e32)->reserved = n3;
	  GTS_SLIST_CONTAINER (n3)->items = 
	    g_slist_remove (GTS_SLIST_CONTAINER (n3)->items, e32);
	}
	j = next;
      }
      if (GTS_OBJECT (e13)->reserved == n3)
	GTS_SLIST_CONTAINER (n3)->items = 
	  g_slist_remove (GTS_SLIST_CONTAINER (n3)->items, e13);
    }
    i = i->next;
  }

  /* connect edges to new node */
  data[0] = ns->n;
  data[1] = n1;
  data[2] = n2;
  gts_container_foreach (GTS_CONTAINER (n1), (GtsFunc) connect_edge, data);
  gts_container_foreach (GTS_CONTAINER (n2), (GtsFunc) connect_edge, data);

  gts_allow_floating_gnodes = TRUE;
  gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (n1));
  gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (n2));
  gts_allow_floating_gnodes = FALSE;
  gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (ns->n));
}

static void restore_edge (GtsGEdge * e, gpointer * data)
{
  GtsGNode * n = data[0];
  GtsGNode * n1 = data[1];
  GtsGNode * n2 = data[2];
  GtsGNode * n3 = GTS_OBJECT (e)->reserved;

  if (n3) { /* e is a disconnected edge */
    GTS_OBJECT (e)->reserved = NULL;
    gts_container_add (GTS_CONTAINER (n3), GTS_CONTAINEE (e));
    return;
  }

  if (gts_gedge_connects (e, n1, n2))
    return;

  if (e->n1 == n)
    e->n1 = n1;
  else if (e->n2 == n)
    e->n2 = n1;
  else
    g_assert_not_reached ();
  GTS_SLIST_CONTAINER (n)->items = 
    g_slist_remove (GTS_SLIST_CONTAINER (n)->items, e);
}

/**
 * gts_gnode_split_expand:
 * @ns: a #GtsGNodeSplit.
 * @g: a #GtsGraph.
 *
 * Expands the node split ns adding the new nodes to @g.
 */
void gts_gnode_split_expand (GtsGNodeSplit * ns,
			     GtsGraph * g)
{
  GtsGNode * n1, * n2;
  gpointer data[3];
  GSList * i;

  g_return_if_fail (ns != NULL);
  g_return_if_fail (g != NULL);
  g_return_if_fail (gts_containee_is_contained (GTS_CONTAINEE (ns->n), 
						GTS_CONTAINER (g)));

  n1 = GTS_GNODE_SPLIT_N1 (ns);
  n2 = GTS_GNODE_SPLIT_N2 (ns);

  data[0] = ns->n;
  data[1] = n1;
  data[2] = n2;
  gts_container_foreach (GTS_CONTAINER (n1), (GtsFunc) restore_edge, data);
  data[1] = n2;
  data[2] = n1;
  gts_container_foreach (GTS_CONTAINER (n2), (GtsFunc) restore_edge, data);

  i = GTS_SLIST_CONTAINER (ns->n)->items;
  while (i) {
    GSList * next = i->next;
    gts_container_remove (GTS_CONTAINER (ns->n), GTS_CONTAINEE (i->data));
    i = next;
  }
  g_assert (gts_container_size (GTS_CONTAINER (ns->n)) == 0);
  
  gts_allow_floating_gnodes = TRUE;
  gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (ns->n));
  gts_allow_floating_gnodes = FALSE;

  gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (n1));
  gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (n2));
}

/* GtsPGraph */

static void pgraph_destroy (GtsObject * object)
{
  GtsPGraph * pg = GTS_PGRAPH (object);
  guint i;

  for (i = 0; i < pg->split->len; i++)
    gts_object_destroy (GTS_OBJECT (g_ptr_array_index (pg->split, i)));
  g_ptr_array_free (pg->split, TRUE);
  g_array_free (pg->levels, TRUE);

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

static void pgraph_class_init (GtsPGraphClass * klass)
{
  GTS_OBJECT_CLASS (klass)->destroy = pgraph_destroy;
}

static void pgraph_init (GtsPGraph * pg)
{
  pg->g = NULL;
  pg->split = g_ptr_array_new ();
  pg->levels = g_array_new (FALSE, FALSE, sizeof (guint));
  pg->level = 0;
  pg->split_class = gts_gnode_split_class ();
  pg->edge_class = gts_wgedge_class ();
  pg->pos = pg->min = 0;
}

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

  if (klass == NULL) {
    GtsObjectClassInfo pgraph_info = {
      "GtsPGraph",
      sizeof (GtsPGraph),
      sizeof (GtsPGraphClass),
      (GtsObjectClassInitFunc) pgraph_class_init,
      (GtsObjectInitFunc) pgraph_init,
      (GtsArgSetFunc) NULL,
      (GtsArgGetFunc) NULL
    };
    klass = gts_object_class_new (gts_object_class (), &pgraph_info);
  }

  return klass;
}

static void match_neighbor (GtsGNode * n, gpointer * data)
{
  if (!GTS_OBJECT (n)->reserved) {
    GtsGraph * g = data[0];
    GSList ** list = data[1];
    GSList * i = GTS_SLIST_CONTAINER (n)->items;
    gfloat wmax = - G_MAXFLOAT;
    GtsGEdge * emax = NULL;
    
    while (i) {
      GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data);
      if (!GTS_OBJECT (n1)->reserved &&
	  gts_gedge_weight (i->data) > wmax &&
	  gts_containee_is_contained (GTS_CONTAINEE (n1), GTS_CONTAINER (g))) {
	emax = i->data;
	wmax = gts_gedge_weight (emax);
      }
      i = i->next;
    }
    if (emax) {
      GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, emax);

      GTS_OBJECT (n1)->reserved = n;
      GTS_OBJECT (n)->reserved = n1;
      *list = g_slist_prepend (*list, emax);
    }
  }
}

static GSList * maximal_matching (GtsGraph * g)
{
  GSList * list = NULL;
  gpointer data[2];

  data[0] = g;
  data[1] = &list;
  gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) match_neighbor, data);
  gts_container_foreach (GTS_CONTAINER (g), 
			 (GtsFunc) gts_object_reset_reserved,
			 NULL);

  return list;
}

/**
 * gts_pgraph_new:
 * @klass: a #GtsPGraphClass.
 * @g: a #GtsGraph.
 * @split_class: a #GtsGNodeSplitClass.
 * @node_class: a #GtsWGNodeClass.
 * @edge_class: a #GtsWGEdgeClass.
 * @min: the minimum number of nodes.
 *
 * Creates a new multilevel approximation of graph @g. At each level a
 * maximal matching is created using the Heavy Edge Matching (HEM)
 * technique of Karypis and Kumar (1997). The newly created nodes are
 * of type @node_class and their weight is set to the sum of the
 * weights of their children. The newly created edges are of type
 * @edge_class and their weight is set to the sum of the weight of the
 * collapsed edges. The last level is reached when the maximal
 * matching obtained would lead to a graph with less than @min nodes.
 *
 * Returns: the new #GtsPGraph containing the multilevel
 * representation of @g.  
 */
GtsPGraph * gts_pgraph_new (GtsPGraphClass * klass,
			    GtsGraph * g,
			    GtsGNodeSplitClass * split_class,
			    GtsWGNodeClass * node_class,
			    GtsWGEdgeClass * edge_class,
			    guint min)
{
  GtsPGraph * pg;
  GSList * matching;

  g_return_val_if_fail (klass != NULL, NULL);
  g_return_val_if_fail (g != NULL, NULL);
  g_return_val_if_fail (split_class != NULL, NULL);
  g_return_val_if_fail (node_class != NULL, NULL);
  g_return_val_if_fail (edge_class != NULL, NULL);

  pg = GTS_PGRAPH (gts_object_new (GTS_OBJECT_CLASS (klass)));
  pg->g = g;
  pg->split_class = split_class;
  pg->edge_class = edge_class;

  while (gts_container_size (GTS_CONTAINER (g)) > min &&
	 (matching = maximal_matching (g))) {
    GSList * i = matching;
    guint size = gts_container_size (GTS_CONTAINER (g));

    g_array_append_val (pg->levels, size);

    while (i && gts_container_size (GTS_CONTAINER (g)) > min) {
      GtsGEdge * e = i->data;
      GtsGNode * n = GTS_GNODE (gts_wgnode_new (node_class,
						gts_gnode_weight (e->n1) +
						gts_gnode_weight (e->n2)));
      GtsGNodeSplit * ns = gts_gnode_split_new (split_class, n,
						GTS_OBJECT (e->n1),
						GTS_OBJECT (e->n2));
      gts_gnode_split_collapse (ns, g, edge_class);
      g_ptr_array_add (pg->split, ns);
      i = i->next;
    }
    g_slist_free (matching);
  }

  pg->pos = pg->split->len;
  pg->min = gts_container_size (GTS_CONTAINER (g));
  pg->level = pg->levels->len;
  
  return pg;
}

/**
 * gts_pgraph_add_node:
 * @pg: a #GtsPGraph.
 *
 * Adds one node to the multilevel graph @pg by expanding the next
 * available #GtsGNodeSplit.
 *
 * Returns: the expanded #GtsGNodeSplit or #NULL if all the
 * #GtsGNodeSplit have already been expanded.  
 */
GtsGNodeSplit * gts_pgraph_add_node (GtsPGraph * pg)
{ 
  GtsGNodeSplit * ns;

  g_return_val_if_fail (pg != NULL, NULL);

  if (pg->pos == 0)
    return NULL;

  ns = g_ptr_array_index (pg->split, --pg->pos);
  gts_gnode_split_expand (ns, pg->g);

  return ns;
}

/**
 * gts_pgraph_remove_node:
 * @pg: a #GtsPGraph.
 *
 * Removes one node from the multilevel graph @pg by collapsing the
 * first available #GtsGNodeSplit.
 *
 * Returns: the collapsed #GtsGNodeSplit or %NULL if all the
 * #GtsGNodeSplit have already been collapsed.  
 */
GtsGNodeSplit * gts_pgraph_remove_node (GtsPGraph * pg)
{
  GtsGNodeSplit * ns;

  g_return_val_if_fail (pg != NULL, NULL);

  if (pg->pos == pg->split->len)
    return NULL;

  ns = g_ptr_array_index (pg->split, pg->pos++);
  gts_gnode_split_collapse (ns, pg->g, pg->edge_class);

  return ns;
}

/**
 * gts_pgraph_max_node_number:
 * @pg: a #GtsPGraph.
 *
 * Returns: the maximum number of nodes of @pg i.e. the number of
 * nodes if all the #GtsGNodeSplit were expanded.  
 */
guint gts_pgraph_max_node_number (GtsPGraph * pg)
{
  g_return_val_if_fail (pg != NULL, 0);

  return pg->min + pg->split->len;
}

/**
 * gts_pgraph_min_node_number:
 * @pg: a #GtsPGraph.
 *
 * Returns: the minimum number of nodes of @pg i.e. the number of
 * nodes if all the #GtsGNodeSplit were collapsed.  
 */
guint gts_pgraph_min_node_number (GtsPGraph * pg)
{
  g_return_val_if_fail (pg != NULL, 0);

  return pg->min;
}

/**
 * gts_pgraph_set_node_number:
 * @pg: a #GtsPGraph.
 * @n: a number of nodes.
 *
 * Performs the required number of collapses or expansions to set the
 * number of nodes of @pg to @n.
 */
void gts_pgraph_set_node_number (GtsPGraph * pg, guint n)
{
  g_return_if_fail (pg != NULL);

  n = pg->min + pg->split->len - n;
  while (pg->pos > n && gts_pgraph_add_node (pg))
    ;
  while (pg->pos < n && gts_pgraph_remove_node (pg))
    ;
}

/**
 * gts_pgraph_get_node_number:
 * @pg: a #GtsPGraph.
 *
 * Returns: the current number of nodes of @pg.
 */
guint gts_pgraph_get_node_number (GtsPGraph * pg)
{
  g_return_val_if_fail (pg != NULL, 0);
  
  return pg->min + pg->split->len - pg->pos;
}

/**
 * gts_pgraph_down:
 * @pg: a #GtsPGraph.
 * @func: a #GtsFunc or %NULL.
 * @data: user data to pass to @func.
 *
 * Performs the required number of expansions to go from the current
 * level to the level immediately below.
 *
 * If @func is not %NULL, it is called after each #GtsGNodeSplit has
 * been expanded.  
 *
 * Returns: %FALSE if it is not possible to go down one level, %TRUE
 * otherwise.  
 */
gboolean gts_pgraph_down (GtsPGraph * pg,
			  GtsFunc func,
			  gpointer data)
{
  guint size;

  g_return_val_if_fail (pg != NULL, FALSE);

  if (pg->level == 0)
    return FALSE;

  size = g_array_index (pg->levels, guint, --(pg->level));
  while (gts_container_size (GTS_CONTAINER (pg->g)) < size) {
    GtsGNodeSplit * ns = gts_pgraph_add_node (pg);

    g_assert (ns);
    if (func)
      (* func) (ns, data);
  }
  return TRUE;
}

