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

#ifndef BINOMIAL_HEAP_INCLUDED
#define BINOMIAL_HEAP_INCLUDED

#include <stdexcept>

namespace alib {

/// binomial heap used as mergeable priority queue
template< typename elem_t >
class BinomialHeap {
public:

	BinomialHeap( int (* compare)( const elem_t &, const elem_t & ) );

	~BinomialHeap();

	/// 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 (*searchMax( const_cast<Node**>(&_head) ))->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( BinomialHeap<elem_t> && that );

	size_t size() const {
		return _size;
	}

protected:

	struct Node {
		elem_t value;
		unsigned   degree;
		Node * parent;
		Node * child;
		Node * sibling;
		Node( const elem_t & val, unsigned deg = 0, Node * par = nullptr, Node * chld = nullptr, Node * sib = nullptr )
		 : value(val), degree(deg), parent(par), child(chld), sibling(sib) {}
	};

	Node *  _head;    ///< head of a singly linked list of binomial 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 binomial trees
	void deleteTreeList( Node * head );

	/// searches the linked list and returns address of variable pointing to the node with maximum value
	Node * * searchMax( Node * * head ) const;
	/// merges linked lists from 2 binomial heaps and returns address of head of the new linked list

	Node *   mergeHeaps( Node * head1, Node * head2 );
	/// merges 2 linked lists of binomial trees and sorts the trees by increasing degree

	Node *   mergeListsByDeg( Node * head1, Node * head2 );
	/// reverses a linked list

	Node *   reverseList( Node * head );
	/// links root of tree1 to root of tree2 (tree1 becomes child of tree2)
	Node *   linkTreeToTree( Node * root1, Node * root2 );

};


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

template< typename elem_t >
BinomialHeap<elem_t>::~BinomialHeap() {
	deleteTreeList( _head );
}

template< typename elem_t >
void BinomialHeap<elem_t>::deleteTreeList( Node * head ) {
	while (head != nullptr) {
		Node * sibling = head->sibling;
		deleteTreeList( head->child );
		delete head;
		head = sibling;
	}
}

template< typename elem_t >
void BinomialHeap<elem_t>::insert( const elem_t & value ) {
	Node * newNode = new Node( value, 0, nullptr, nullptr, nullptr );

	_head = mergeHeaps( _head, newNode ); // merge the current heap with the newNode,
	                              // as if the newNode was a single-element heap
	_size++;
}

template< typename elem_t >
elem_t BinomialHeap<elem_t>::extractMax() {
	if ( _size == 0 )
		throw std::out_of_range ( "Heap is empty." );

	Node * * ptrToMax = searchMax( &_head );    // find the node with maximum value
	Node * max = *ptrToMax;

	*ptrToMax = max->sibling;       // disconnect it from the linked list

	Node * chlHead = reverseList( max->child );       // merge them with the heap in reversed order
	_head = mergeHeaps( this->_head, chlHead );

	_size--;
	elem_t maxVal = max->value;
	delete max;                     // extract the value from node and return it
	return maxVal;
}

template< typename elem_t >
void BinomialHeap<elem_t>::mergeWith( BinomialHeap<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");

	this->_head = mergeHeaps( this->_head, that._head );    // move the other heap into this heap
	that._head = nullptr;

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

template< typename elem_t >
typename BinomialHeap<elem_t>::Node * * BinomialHeap<elem_t>::searchMax( Node * * head ) const {
	Node * max = *head, * * ptrToMax = head;
	for (Node * actual = * head, * prev = nullptr; actual != nullptr; prev = actual, actual = actual->sibling) {
		if (_compare( actual->value, max->value ) > 0) {
			max = actual;
			ptrToMax = &prev->sibling;
		}
	}
	return ptrToMax;
}

template< typename elem_t >
typename BinomialHeap<elem_t>::Node * BinomialHeap<elem_t>::mergeHeaps( Node * head1, Node * head2 ) {
	if ( ! head1 )
		return head2;

	if ( ! head2 )
		return head1;

	head1 = mergeListsByDeg( head1, head2 );   // first, merge the lists of trees by their degrees

	Node * actual = head1;
	Node * * toLink = & head1;
	while (actual->sibling) {
		Node * next = actual->sibling;

		if (actual->degree != next->degree || (next->sibling && next->sibling->degree == actual->degree)) {
			toLink = &actual->sibling;                // not merging trees with same degree
			actual = next;                            // or postponing the merge by 1 iteration
		} else if (_compare( actual->value, next->value ) >= 0) {
			actual->sibling = next->sibling;          // merging 2 binomial trees with same degree
			actual = linkTreeToTree( next, actual );  // 'next'    becomes child of   'actual'
		} else {
			*toLink = next;                           // merging 2 binomial trees with same degree
			actual = linkTreeToTree( actual, next );  // 'actual'  becomes child of   'next'
		}
	}

	return head1;
}

template< typename elem_t >
typename BinomialHeap<elem_t>::Node * BinomialHeap<elem_t>::mergeListsByDeg( Node * head1, Node * head2 ) {
	Node * newHead = nullptr;
	Node * * toLink = &newHead;
	while (head1 && head2) {
		if (head1->degree < head2->degree) {
			*toLink = head1;                      // linking node from first list
			toLink = &head1->sibling;             // and moving first pointer
			head1 = head1->sibling;
		} else {
			*toLink = head2;                      // linking node from second list
			toLink = &head2->sibling;             // and moving second pointer
			head2 = head2->sibling;
		}
	}
	if (!head1)
		*toLink = head2;        // list1 ended, link the rest of list2
	else
		*toLink = head1;        // list2 ended, link the rest of list1

	return newHead;
}

template< typename elem_t >
typename BinomialHeap<elem_t>::Node * BinomialHeap<elem_t>::reverseList( Node * head ) {
	Node * prev = nullptr;

	while (head) {
		Node * next = head->sibling;
		head->sibling = prev;
		prev = head;
		head = next;
	}

	return prev;
}

template< typename elem_t >
typename BinomialHeap<elem_t>::Node * BinomialHeap<elem_t>::linkTreeToTree( Node * root1, Node * root2 ) {
	root1->parent = root2;
	root1->sibling = root2->child;
	root2->child = root1;
	root2->degree++;

	return root2;
}

} /* namespace alib */

#endif // BINOMIAL_HEAP_INCLUDED
