/*
 * dfib.c
 *
 * This file is part of SOAPdenovo.
 *
 */

/*-
 * Copyright 1997-2003 John-Mark Gurney.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *  $Id: dfib.c,v 1.12 2007/10/19 13:09:26 zerbino Exp $
 *
 */
#include <limits.h>
#include <stdlib.h>

#include "dfib.h"
#include "dfibpriv.h"
#include "extfunc2.h"

#define HEAPBLOCKSIZE 1000
static int dfh_comparedata ( DFibHeap *h, Time key, unsigned int data, DFibHeapNode *b );
static DFibHeapNode *allocateDFibHeapNode ( DFibHeap *heap )
{
  return ( DFibHeapNode * ) getItem ( heap->nodeMemory );
};

static void deallocateDFibHeapNode ( DFibHeapNode *a, DFibHeap *heap )
{
  returnItem ( heap->nodeMemory, a );
}

IDnum dfibheap_getSize ( DFibHeap *heap )
{
  return heap->dfh_n;
}

#define swap(type, a, b)        \
  do {            \
      type c;     \
      c = a;      \
      a = b;      \
      b = c;      \
    } while (0)     \
     
#define INT_BITS        (sizeof(IDnum) * 8)

static inline IDnum ceillog2 ( IDnum a )
{
  IDnum oa;
  IDnum i;
  IDnum b;
  IDnum cons;
  oa = a;
  b = INT_BITS / 2;
  i = 0;

  while ( b )
    {
      i = ( i << 1 );
      cons = ( ( IDnum ) 1 ) << b;

      if ( a >= cons )
        {
          a /= cons;
          i = i | 1;
        }
      else
        {
          a &= cons - 1;
        }

      b /= 2;
    }

  if ( ( ( ( IDnum ) 1 << i ) ) == oa )
    {
      return i;
    }
  else
    {
      return i + 1;
    }
}

/*
 * Public Heap Functions
 */
DFibHeap *dfh_makekeyheap ()
{
  DFibHeap *n;

  if ( ( n = malloc ( sizeof * n ) ) == NULL )
    {
      return NULL;
    }

  n->nodeMemory = createMem_manager ( HEAPBLOCKSIZE, sizeof ( DFibHeapNode ) );
  n->dfh_n = 0;
  n->dfh_Dl = -1;
  n->dfh_cons = NULL;
  n->dfh_min = NULL;
  n->dfh_root = NULL;
  return n;
}

void dfh_deleteheap ( DFibHeap *h )
{
  fprintf ( stderr, "DFibHeap: %lld node(s) allocated.\n", h->nodeMemory->counter );
  freeMem_manager ( h->nodeMemory );
  h->nodeMemory = NULL;

  if ( h->dfh_cons != NULL )
    {
      free ( h->dfh_cons );
    }

  free ( h );
}

/*
 * Public Key Heap Functions
 */
DFibHeapNode *dfh_insertkey ( DFibHeap *h, Time key, unsigned int data )
{
  DFibHeapNode *x;

  if ( ( x = dfhe_newelem ( h ) ) == NULL )
    {
      return NULL;
    }

  /* just insert on root list, and make sure it's not the new min */
  x->dfhe_data = data;
  x->dfhe_key = key;
  dfh_insertel ( h, x );
  return x;
}

Time dfh_replacekey ( DFibHeap *h, DFibHeapNode *x, Time key )
{
  Time ret;
  ret = x->dfhe_key;
  ( void ) dfh_replacekeydata ( h, x, key, x->dfhe_data );
  return ret;
}

unsigned int minInDHeap ( DFibHeap *h )
{
  if ( h->dfh_min )
    {
      return h->dfh_min->dfhe_data;
    }
  else
    {
      return 0;
    }
}

boolean HasMin ( DFibHeap *h )
{
  if ( h->dfh_min )
    {
      return 1;
    }
  else
    {
      return 0;
    }
}

unsigned int dfh_replacekeydata ( DFibHeap *h, DFibHeapNode *x, Time key, unsigned int data )
{
  unsigned int odata;
  Time okey;
  DFibHeapNode *y;
  int r;
  odata = x->dfhe_data;
  okey = x->dfhe_key;

  /*
   * we can increase a key by deleting and reinserting, that
   * requires O(lgn) time.
   */
  if ( ( r = dfh_comparedata ( h, key, data, x ) ) > 0 )
    {
      /* XXX - bad code! */
      abort ();
    }

  x->dfhe_data = data;
  x->dfhe_key = key;

  /* because they are equal, we don't have to do anything */
  if ( r == 0 )
    {
      return odata;
    }

  y = x->dfhe_p;

  if ( okey == key )
    {
      return odata;
    }

  if ( y != NULL && dfh_compare ( h, x, y ) <= 0 )
    {
      dfh_cut ( h, x, y );
      dfh_cascading_cut ( h, y );
    }

  /*
   * the = is so that the call from dfh_delete will delete the proper
   * element.
   */
  if ( dfh_compare ( h, x, h->dfh_min ) <= 0 )
    {
      h->dfh_min = x;
    }

  return odata;
}

/*
 * Public void * Heap Functions
 */
/*
 * this will return these values:
 *  NULL    failed for some reason
 *  ptr token to use for manipulation of data
 */
unsigned int dfh_extractmin ( DFibHeap *h )
{
  DFibHeapNode *z;
  unsigned int ret;
  ret = 0;

  if ( h->dfh_min != NULL )
    {
      z = dfh_extractminel ( h );
      ret = z->dfhe_data;
      deallocateDFibHeapNode ( z, h );
    }

  return ret;
}

unsigned int dfh_replacedata ( DFibHeapNode *x, unsigned int data )
{
  unsigned int odata = x->dfhe_data;
  //printf("replace node value %d with %d\n",x->dfhe_data,data);
  x->dfhe_data = data;
  return odata;
}

unsigned int dfh_delete ( DFibHeap *h, DFibHeapNode *x )
{
  unsigned int k;
  //printf("destroy node %d in dheap\n",x->dfhe_data);
  k = x->dfhe_data;
  dfh_replacekey ( h, x, INT_MIN );
  dfh_extractmin ( h );
  return k;
}

/*
 * begin of private element fuctions
 */
static DFibHeapNode *dfh_extractminel ( DFibHeap *h )
{
  DFibHeapNode *ret;
  DFibHeapNode *x, *y, *orig;
  ret = h->dfh_min;
  orig = NULL;

  /* put all the children on the root list */
  /* for true consistancy, we should use dfhe_remove */
  for ( x = ret->dfhe_child; x != orig && x != NULL; )
    {
      if ( orig == NULL )
        {
          orig = x;
        }

      y = x->dfhe_right;
      x->dfhe_p = NULL;
      dfh_insertrootlist ( h, x );
      x = y;
    }

  /* remove minimum from root list */
  dfh_removerootlist ( h, ret );
  h->dfh_n--;

  /* if we aren't empty, consolidate the heap */
  if ( h->dfh_n == 0 )
    {
      h->dfh_min = NULL;
    }
  else
    {
      h->dfh_min = ret->dfhe_right;
      dfh_consolidate ( h );
    }

  return ret;
}

static void dfh_insertrootlist ( DFibHeap *h, DFibHeapNode *x )
{
  if ( h->dfh_root == NULL )
    {
      h->dfh_root = x;
      x->dfhe_left = x;
      x->dfhe_right = x;
      return;
    }

  dfhe_insertafter ( h->dfh_root, x );
}

static void dfh_removerootlist ( DFibHeap *h, DFibHeapNode *x )
{
  if ( x->dfhe_left == x )
    {
      h->dfh_root = NULL;
    }
  else
    {
      h->dfh_root = dfhe_remove ( x );
    }
}

static void dfh_consolidate ( DFibHeap *h )
{
  DFibHeapNode **a;
  DFibHeapNode *w;
  DFibHeapNode *y;
  DFibHeapNode *x;
  IDnum i;
  IDnum d;
  IDnum D;
  dfh_checkcons ( h );
  /* assign a the value of h->dfh_cons so I don't have to rewrite code */
  D = h->dfh_Dl + 1;
  a = h->dfh_cons;

  for ( i = 0; i < D; i++ )
    {
      a[i] = NULL;
    }

  while ( ( w = h->dfh_root ) != NULL )
    {
      x = w;
      dfh_removerootlist ( h, w );
      d = x->dfhe_degree;

      /* XXX - assert that d < D */
      while ( a[d] != NULL )
        {
          y = a[d];

          if ( dfh_compare ( h, x, y ) > 0 )
            {
              swap ( DFibHeapNode *, x, y );
            }

          dfh_heaplink ( h, y, x );
          a[d] = NULL;
          d++;
        }

      a[d] = x;
    }

  h->dfh_min = NULL;

  for ( i = 0; i < D; i++ )
    if ( a[i] != NULL )
      {
        dfh_insertrootlist ( h, a[i] );

        if ( h->dfh_min == NULL || dfh_compare ( h, a[i], h->dfh_min ) < 0 )
          {
            h->dfh_min = a[i];
          }
      }
}

static void dfh_heaplink ( DFibHeap *h, DFibHeapNode *y, DFibHeapNode *x )
{
  /* make y a child of x */
  if ( x->dfhe_child == NULL )
    {
      x->dfhe_child = y;
    }
  else
    {
      dfhe_insertbefore ( x->dfhe_child, y );
    }

  y->dfhe_p = x;
  x->dfhe_degree++;
  y->dfhe_mark = 0;
}

static void dfh_cut ( DFibHeap *h, DFibHeapNode *x, DFibHeapNode *y )
{
  dfhe_remove ( x );
  y->dfhe_degree--;
  dfh_insertrootlist ( h, x );
  x->dfhe_p = NULL;
  x->dfhe_mark = 0;
}

static void dfh_cascading_cut ( DFibHeap *h, DFibHeapNode *y )
{
  DFibHeapNode *z;

  while ( ( z = y->dfhe_p ) != NULL )
    {
      if ( y->dfhe_mark == 0 )
        {
          y->dfhe_mark = 1;
          return;
        }
      else
        {
          dfh_cut ( h, y, z );
          y = z;
        }
    }
}

/*
 * begining of handling elements of dfibheap
 */
static DFibHeapNode *dfhe_newelem ( DFibHeap *h )
{
  DFibHeapNode *e;

  if ( ( e = allocateDFibHeapNode ( h ) ) == NULL )
    {
      return NULL;
    }

  e->dfhe_degree = 0;
  e->dfhe_mark = 0;
  e->dfhe_p = NULL;
  e->dfhe_child = NULL;
  e->dfhe_left = e;
  e->dfhe_right = e;
  e->dfhe_data = 0;
  return e;
}

static void dfhe_insertafter ( DFibHeapNode *a, DFibHeapNode *b )
{
  if ( a == a->dfhe_right )
    {
      a->dfhe_right = b;
      a->dfhe_left = b;
      b->dfhe_right = a;
      b->dfhe_left = a;
    }
  else
    {
      b->dfhe_right = a->dfhe_right;
      a->dfhe_right->dfhe_left = b;
      a->dfhe_right = b;
      b->dfhe_left = a;
    }
}

static inline void dfhe_insertbefore ( DFibHeapNode *a, DFibHeapNode *b )
{
  dfhe_insertafter ( a->dfhe_left, b );
}

static DFibHeapNode *dfhe_remove ( DFibHeapNode *x )
{
  DFibHeapNode *ret;

  if ( x == x->dfhe_left )
    {
      ret = NULL;
    }
  else
    {
      ret = x->dfhe_left;
    }

  /* fix the parent pointer */
  if ( x->dfhe_p != NULL && x->dfhe_p->dfhe_child == x )
    {
      x->dfhe_p->dfhe_child = ret;
    }

  x->dfhe_right->dfhe_left = x->dfhe_left;
  x->dfhe_left->dfhe_right = x->dfhe_right;
  /* clear out hanging pointers */
  x->dfhe_p = NULL;
  x->dfhe_left = x;
  x->dfhe_right = x;
  return ret;
}

static void dfh_checkcons ( DFibHeap *h )
{
  IDnum oDl;

  /* make sure we have enough memory allocated to "reorganize" */
  if ( h->dfh_Dl == -1 || h->dfh_n > ( 1 << h->dfh_Dl ) )
    {
      oDl = h->dfh_Dl;

      if ( ( h->dfh_Dl = ceillog2 ( h->dfh_n ) + 1 ) < 8 )
        {
          h->dfh_Dl = 8;
        }

      if ( oDl != h->dfh_Dl )
        {
          h->dfh_cons = ( DFibHeapNode ** ) realloc ( h->dfh_cons, sizeof * h->dfh_cons * ( h->dfh_Dl + 1 ) );
        }

      if ( h->dfh_cons == NULL )
        {
          abort ();
        }
    }
}

static int dfh_compare ( DFibHeap *h, DFibHeapNode *a, DFibHeapNode *b )
{
  if ( a->dfhe_key < b->dfhe_key )
    {
      return -1;
    }

  if ( a->dfhe_key == b->dfhe_key )
    {
      return 0;
    }

  return 1;
}

static int dfh_comparedata ( DFibHeap *h, Time key, unsigned int data, DFibHeapNode *b )
{
  DFibHeapNode a;
  a.dfhe_key = key;
  a.dfhe_data = data;
  return dfh_compare ( h, &a, b );
}

static void dfh_insertel ( DFibHeap *h, DFibHeapNode *x )
{
  dfh_insertrootlist ( h, x );

  if ( h->dfh_min == NULL || x->dfhe_key < h->dfh_min->dfhe_key )
    {
      h->dfh_min = x;
    }

  h->dfh_n++;
}

Time dfibheap_el_getKey ( DFibHeapNode *node )
{
  return node->dfhe_key;
}
