/*
 * FibonacciHeap.h
 *
 *   Created on: Apr 7, 2016
 *       Author: Jan Broz
 */

#ifndef FIBONACCI_HEAP_INCLUDED
#define FIBONACCI_HEAP_INCLUDED

#include <stdexcept>
#include <cmath>      // maxDegree

namespace alib {

/// fibonacci heap used as mergeable priority queue
template< typename elem_t >
class FibonacciHeap {
public:
	FibonacciHeap( int (* compare)( const elem_t &, const elem_t & ) );

	~FibonacciHeap();

	/// inserts a node with new value into the heap
	void insert( const elem_t & value );

	/// finds the maximum value in the heap
	const elem_t & getMax() const {
		return _max->value;
	}

	/// finds and removes the maximum value from the heap
	elem_t extractMax();

	/// merges this heap with another heap (!! this is a DESTRUCTIVE merge, heap in argument will be cleared !!)
	void mergeWith( FibonacciHeap<elem_t> && that );

	size_t size() const {
		return _size;
	}

//	void print ( );

//	void checkConsystency ( );

protected:
	struct Node {
		elem_t value;
		unsigned degree;
		bool   mark;
		Node * parent;
		Node * child;
		Node * prev;
		Node * next;
		Node(const elem_t & val, unsigned deg = 0, Node * par = nullptr, Node * chld = nullptr, Node * pr = nullptr, Node * ne = nullptr)
		 : value(val), degree(deg), mark(false), parent(par), child(chld), prev(pr), next(ne) {}
	};

	Node *  _max;     ///< pointer to cyclic doubly linked list of trees
	size_t    _size;    ///< count of elements stored in the heap
	int (*  _compare)( const elem_t &, const elem_t & ); ///< user-defined comparator function

protected:

	/// deletes one linked list of trees
	void deleteTreeList( Node * head );

	/// connects doubly linked lists from 2 fibonacci heaps and returns address of head of the new linked list
	void mergeHeaps( Node * & max1, Node * & max2 );

	/// goes through list of trees and merges trees with same degree
	void consolidate();

	/// links tree to child list of node
	void linkTreeToNode( Node * node, Node * tree );

//	void printNode ( Node * node );

//	void checkConsystency ( Node * node, Node * parent );
};


template< typename elem_t >
FibonacciHeap<elem_t>::FibonacciHeap ( int (* compare)( const elem_t &, const elem_t & ) ) : _max( nullptr ), _size( 0 ), _compare( compare ) {
}

template< typename elem_t >
FibonacciHeap<elem_t>::~FibonacciHeap ( ) {
	deleteTreeList( _max );
}

template< typename elem_t >
void FibonacciHeap<elem_t>::deleteTreeList( Node * head ) {
	if ( ! head )
		return;

	Node * actual = head;
	do {
		Node * next = actual->next;
		deleteTreeList( actual->child );
		delete actual;
		actual = next;
	} while (actual != head);
}

template< typename elem_t >
void FibonacciHeap<elem_t>::insert( const elem_t & value ) {
	Node * newNode = new Node( value, 0, nullptr, nullptr, nullptr, nullptr );
	newNode->prev = newNode;
	newNode->next = newNode;     // make this node be a single-element cyclic list

	if (!_max) {
		_max = newNode;       // this heap is empty, newNode becomes head of a linked list
		_size = 1;
		return;
	}

	mergeHeaps( _max, newNode );    // link the newNode into the existing linked list

	_max = _compare( _max->value, newNode->value ) > 0 ? _max : newNode;

	_size++;
}

template< typename elem_t >
elem_t FibonacciHeap<elem_t>::extractMax() {
	if ( _max == nullptr )
		throw std::out_of_range ( "Heap is empty." );

	Node * z = _max;
	elem_t maxVal = z->value;

	if (z->child != nullptr) {
		Node * z1 = z->child;
		do {
			z1->parent = nullptr;
			z1 = z1->next;
		} while (z1 != z->child);
	}

	mergeHeaps ( z, z->child );

	if (z == z->next)
		_max = nullptr;
	else {
		_max = z->next;
		_max->prev = z->prev;
		z->prev->next = _max;
		consolidate();
	}

	_size--;
	delete z;
	return maxVal;
}

template< typename elem_t >
void FibonacciHeap<elem_t>::mergeWith( FibonacciHeap<elem_t> && that ) {
	if (this->_compare != that._compare)    // nodes of these heaps are sorted by different condition
		throw std::logic_error("compare functions aren't equal, unable to merge");

	mergeHeaps( this->_max, that._max );    // link the other heap into this heap
	this->_max = _compare( _max->value, that._max->value ) > 0 ? this->_max : that._max;
	that._max = nullptr;

	this->_size += that._size;
	that._size = 0;
}

template< typename elem_t >
void FibonacciHeap<elem_t>::mergeHeaps( Node * & max1, Node * & max2 ) {
	if ( ! max1 ) {
		max1 = max2;    // this heap is empty, move the content from the other heap inside
	} else if ( max2 ) {
		Node * max2prev = max2->prev;

		max2->prev->next = max1;
		max2->prev = max1->prev;    // this magicaly works even if the lists contain only one node
		max1->prev->next = max2;
		max1->prev = max2prev;
	}
}

template< typename elem_t >
void FibonacciHeap<elem_t>::consolidate() {
	unsigned maxDegree = log2( _size );
	Node * * rootsByDegree = new Node * [ maxDegree + 1 ];
	for (unsigned i = 0; i <= maxDegree; i++)
		rootsByDegree[i] = nullptr;

	Node * next = _max;
	Node * actual = next;

	bool finish = next == next->next;
	while ( ! finish ) {
		actual = next;
		next = actual->next;
		finish = next == rootsByDegree [ next->degree ];

		while ( rootsByDegree[ actual->degree ] != nullptr) {
			Node * other = rootsByDegree[ actual->degree ];
			rootsByDegree[ actual->degree ] = nullptr;

			if (_compare( actual->value, other->value ) > 0) {
				linkTreeToNode( actual, other );
			} else {
				linkTreeToNode( other, actual );
				actual = other;
			}

		}
		rootsByDegree[ actual->degree ] = actual;
	};

	delete [] rootsByDegree;

	_max = actual;
	Node * node = actual;
	do {
		if (_compare( node->value, _max->value ) > 0)
			_max = node;
		node = node->next;
	} while ( node != actual );
}

template< typename elem_t >
void FibonacciHeap<elem_t>::linkTreeToNode( Node * node, Node * tree ) {
	tree->prev->next = tree->next;
	tree->next->prev = tree->prev;

	tree->prev = tree;
	tree->next = tree;

	tree->parent = node;
	node->degree ++;

	mergeHeaps ( node->child, tree );
}

/*template < typename elem_t >
void FibonacciHeap<elem_t>::print() {
	if ( _max == nullptr )
		std::cout << "Empty" << std::endl;

	printNode ( _max );
}

template < typename elem_t >
void FibonacciHeap < elem_t >::printNode ( Node * node ) {
	if ( node == nullptr )
		return;

	Node * actual = node;
	do {
		std::cout << " Value: " << actual->value << " Parent: " << ((actual->parent) ? actual->parent->id : -1) << " Left: " << actual->prev->id << " Right: " << actual->next->id << std::endl;
		printNode ( actual->child );
		actual = actual->next;

	} while ( node != actual );
}

template < typename elem_t >
void FibonacciHeap<elem_t>::checkConsystency() {
	checkConsystency ( _max, nullptr );
}

template < typename elem_t >
void FibonacciHeap < elem_t >::checkConsystency ( Node * node, Node * parent ) {
	if ( node == nullptr )
		return;

	Node * actual = node;
	do {
		assert ( node == node->prev->next && node->next->prev == node && node->parent == parent );
		checkConsystency ( actual->child, actual );
		actual = actual->next;

	} while ( node != actual );
}*/

} /* namespace alib */

#endif // FIBONACCI_HEAP_INCLUDED
