/**
 * Fibonacci Heap
 * Copyright (c) 2014, Emmanuel Benazera beniz@droidnik.fr, All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
    
#ifndef FIBOHEAP_H
#define FIBOHEAP_H

#include <cstddef>
#include <math.h>
#include <limits>
#include <iostream>

template<class T>
class FibHeap
{
 public:

  // node
  class FibNode
  {
  public:
    FibNode(T k, void *pl)
      :key(k),mark(false),p(nullptr),left(nullptr),right(nullptr),child(nullptr),degree(-1),payload(pl)
    {
    }
    
    ~FibNode()
      {
      }

    T key;
    bool mark;
    FibNode *p;
    FibNode *left;
    FibNode *right;
    FibNode *child;
    int degree;
    void *payload;
  }; // end FibNode

  FibHeap()
    :n(0),min(nullptr)
    {
    }

  ~FibHeap()
    {
      // delete all nodes.
      delete_fibnodes(min);
    }

  void delete_fibnodes(FibNode *x)
  {
    if (!x)
      return;
    
    FibNode *cur = x;
    while(true)
      {
	if (cur->left && cur->left != x)
	  {
	    FibNode *tmp = cur;
	    cur = cur->left;
	    if (tmp->child)
	      delete_fibnodes(tmp->child);
	    delete tmp;
	  }
	else
	  {
	    if (cur->child)
	      delete_fibnodes(cur->child);
	    delete cur;
	    break;
	  }
      }
  }
      
  /*
   * insert(x)
   * 1. x.degree = 0
   * 2. x.p = NIL
   * 3. x.child = NIL
   * 4. x.mark = FALSE
   * 5. if H.min == NIL
   * 6. 	create a root list for H containing just x
   * 7. 	H.min = x
   * 8. else insert x into H's root list
   * 9. 	if x.key < H.min.key
   *10. 		H.min = x
   *11. H.n = H.n + 1
   */
  void insert(FibNode *x)
  {
    // 1
    x->degree = 0;
    // 2
    x->p = nullptr;
    // 3
    x->child = nullptr;
    // 4
    x->mark = false;
    // 5
    if ( min == nullptr)
      {
	// 6, 7
	min = x->left = x->right = x;
      }
    else
      {
	// 8
	min->left->right = x;
	x->left = min->left;
	min->left = x;
	x->right = min;
	// 9
	if ( x->key < min->key )
	  {
	    // 10
	    min = x;
	  }
      }
    // 11
    ++n;
  }

  /*
   * The minimum node of the heap.
   */
  FibNode* minimum()
  {
    return min;
  }

  /*
   * union_fibheap(H1,H2)
   * 1. H = MAKE-FIB-HEAP()
   * 2. H.min = H1.min
   * 3. concatenate the root list of H2 with the root list of H
   * 4. if (H1.min == NIL) or (H2.min != NIL and H2.min.key < H1.min.key)
   * 5. 	H.min = H2.min
   * 6. H.n = H1.n + H2.n
   * 7. return H
   */
  static FibHeap* union_fibheap(FibHeap *H1, FibHeap *H2)
  {
    // 1
    FibHeap* H = new FibHeap();
    // 2
    H->min = H1->min;
    // 3
    if ( H->min != nullptr && H2->min != nullptr )
      {
	H->min->right->left = H2->min->left;
	H2->min->left->right = H->min->right;
	H->min->right = H2->min;
	H2->min->left = H->min;
      }
    // 4
    if ( H1->min == nullptr || ( H2->min != nullptr && H2->min->key < H1->min->key ) )
      {
	// 5
	H->min = H2->min;
      }
    // 6
    H->n = H1->n + H2->n;
    // 7
    return H;
  }

  /*
   * extract_min
   * 1. z = H.min
   * 2. if z != NIL
   * 3. 	for each child x of z
   * 4. 		add x to the root list of H
   * 5. 		x.p = NIL
   * 6. 	remove z from the root list of H
   * 7.		if z == z.right
   * 8. 		H.min = NIL
   * 9. 	else H.min = z.right
   *10. 		CONSOLIDATE(H)
   *11. 	H.n = H.n - 1
   *12. return z
   */
  FibNode* extract_min()
  {
    FibNode *z, *x, *next;
    FibNode ** childList;

    // 1
    z = min;
    // 2
    if ( z != nullptr )
      {
	// 3
	x = z->child;
	if ( x != nullptr )
	  {
	    childList = new FibNode*[z->degree];
	    next = x;
	    for ( int i = 0; i < (int)z->degree; i++ )
	      {
		childList[i] = next;
		next = next->right;
	      }
	    for ( int i = 0; i < (int)z->degree; i++ )
	      {
		x = childList[i];
		// 4
		min->left->right = x;
		x->left = min->left;
		min->left = x;
		x->right = min;
		// 5
		x->p = nullptr;
	      }
	    delete [] childList;
	  }
	// 6
	z->left->right = z->right;
	z->right->left = z->left;
	// 7
	if ( z == z->right )
	  {
	    // 8
	    min = nullptr;
	  }
	else
	  {
	    // 9
	    min = z->right;
	    // 10
	    consolidate();
	  }
	// 11
	n--;
      }
    // 12
    return z;
  }

  /*
   * consolidate
   * 1. let A[0 . . D(H.n)] be a new array
   * 2. for i = 0 to D(H.n)
   * 3. 	A[i] = NIL
   * 4. for each node w in the root list of H
   * 5. 	x = w
   * 6. 	d = x.degree
   * 7. 	while A[d] != NIL
   * 8. 		y = A[d]
   * 9. 		if x.key > y.key
   *10.			exchange x with y
   *11. 		FIB-HEAP-LINK(H,y,x)
   *12. 		A[d] = NIL
   *13. 		d = d + 1
   *14. 	A[d] = x
   *15. H.min = NIL
   *16. for i = 0 to D(H.n)
   *17. 	if A[i] != NIL
   *18. 		if H.min == NIL
   *19. 			create a root list for H containing just A[i]
   *20. 			H.min = A[i]
   *21. 		else insert A[i] into H's root list
   *22. 			if A[i].key < H.min.key
   *23. 				H.min = A[i]
   */
  void consolidate()
  {
    FibNode* w, * next, * x, * y, * temp;
    FibNode** A, ** rootList;
    // Max degree <= log base golden ratio of n
    int d, rootSize;
    int max_degree = static_cast<int>(floor(log(static_cast<double>(n))/log(static_cast<double>(1 + sqrt(static_cast<double>(5)))/2)));
    
    // 1
    A = new FibNode*[max_degree+2]; // plus two both for indexing to max degree and so A[max_degree+1] == NIL
    // 2, 3
    std::fill_n(A, max_degree+2, nullptr);
    // 4
    w = min;
    rootSize = 0;
    next = w;
    do
      {
	rootSize++;
	next = next->right;
      } while ( next != w );
    rootList = new FibNode*[rootSize];
    for ( int i = 0; i < rootSize; i++ )
      {
	rootList[i] = next;
	next = next->right;
      }
    for ( int i = 0; i < rootSize; i++ )
      {
	w = rootList[i];
	// 5
	x = w;
	// 6
	d = x->degree;
	// 7
	while ( A[d] != nullptr )
	  {
	    // 8
	    y = A[d];
	    // 9
	    if ( x->key > y->key )
	      {
		// 10
		temp = x;
		x = y;
		y = temp;
	      }
	    // 11
	    fib_heap_link(y,x);
	    // 12
	    A[d] = nullptr;
	    // 13
	    d++;
	  }
	// 14
	A[d] = x;
      }
    delete [] rootList;
    // 15
    min = nullptr;
    // 16
    for ( int i = 0; i < max_degree+2; i++ )
      {
	// 17
	if ( A[i] != nullptr )
	  {
	    // 18
	    if ( min == nullptr )
	      {
		// 19, 20
		min = A[i]->left = A[i]->right = A[i];
	      }
	    else
	      {
		// 21
		min->left->right = A[i];
		A[i]->left = min->left;
		min->left = A[i];
		A[i]->right = min;
		// 22
		if ( A[i]->key < min->key )
		  {
		    // 23
		    min = A[i];
		  }
	      }
	  }
      }
    delete [] A;
  }

/*
 * fib_heap_link(y,x)
 * 1. remove y from the root list of heap
 * 2. make y a child of x, incrementing x.degree
 * 3. y.mark = FALSE
 */
  void fib_heap_link( FibNode* y, FibNode* x )
  {
    // 1
    y->left->right = y->right;
    y->right->left = y->left;
    // 2
    if ( x->child != nullptr )
      {
	x->child->left->right = y;
	y->left = x->child->left;
	x->child->left = y;
	y->right = x->child;
      }
    else
      {
	x->child = y;
	y->right = y;
	y->left = y;
      }
    y->p = x;
    x->degree++;
    // 3
    y->mark = false;
  }

  
  /*
   * decrease_key(x,k)
   * 1. if k > x.key
   * 2. 	error "new key is greater than current key"
   * 3. x.key = k
   * 4. y = x.p
   * 5. if y != NIL and x.key < y.key
   * 6. 	CUT(H,x,y)
   * 7. 	CASCADING-CUT(H,y)
   * 8. if x.key < H.min.key
   * 9. 	H.min = x
   */
  void decrease_key( FibNode* x, int k )
  {
    FibNode* y;
    
    // 1
    if ( k > x->key )
      {
	// 2
	// error( "new key is greater than current key" );
	return;
      }
    // 3
    x->key = k;
    // 4
    y = x->p;
    // 5
    if ( y != nullptr && x->key < y->key )
      {
	// 6
	cut(x,y);
	// 7
	cascading_cut(y);
      }
    // 8
    if ( x->key < min->key )
      {
	// 9
	min = x;
      }
  }

  /*
   * cut(x,y)
   * 1. remove x from the child list of y, decrementing y.degree
   * 2. add x to the root list of H
   * 3. x.p = NIL
   * 4. x.mark = FALSE
   */
  void cut( FibNode* x, FibNode* y )
  {
    // 1
    if ( x->right == x )
      {
	y->child = nullptr;
      }
    else
      {
	x->right->left = x->left;
	x->left->right = x->right;
	if ( y->child == x )
	  {
	    y->child = x->right;
	  }
      }
    y->degree--;
    // 2
    min->right->left = x;
    x->right = min->right;
    min->right = x;
    x->left = min;
    // 3
    x->p = nullptr;
    // 4
    x->mark = false;
  }

/*
  * cascading_cut(y)
  * 1. z = y.p
  * 2. if z != NIL
  * 3. 	if y.mark == FALSE
  * 4. 		y.mark = TRUE
  * 5. 	else CUT(H,y,z)
  * 6. 		CASCADING-CUT(H,z)
  */
  void cascading_cut( FibNode* y )
  {
    FibNode* z;
    
    // 1
    z = y->p;
    // 2
    if ( z != nullptr )
      {
	// 3
	if ( y->mark == false )
	  {
	    // 4
	    y->mark = true;
	  }
	else
	  {
	    // 5
	    cut(y,z);
	    // 6
	    cascading_cut(z);
	  }
      }
  }

  /*
   * set to infinity so that it hits the top of the heap, then easily remove.
   */
  void remove_fibnode( FibNode* x )
  {
    decrease_key(x,std::numeric_limits<T>::min());
    FibNode *fn = extract_min();
    delete fn;
  }
  
  /*
   * mapping operations to STL-compatible signatures.
   */
  bool empty() const
  {
    return n == 0;
  }

  FibNode* topNode()
  {
    return minimum();
  }

  T top()
  {
    return minimum()->key;
  }

  void pop()
  {
    if (empty())
      return;
    FibNode *x = extract_min();
    if (x)
      delete x;
  }

  FibNode* push(T k, void *pl)
  {
    FibNode *x = new FibNode(k,pl);
    insert(x);
    return x;
  }

  FibNode* push(T k)
  {
    return push(k,nullptr);
  }

  unsigned int size()
  {
    return (unsigned int) n;
  }
  
  int n;
  FibNode *min;
  
};

#endif
