using System;
using System.Collections.Generic;
using System.Linq;

namespace DataStructures.Heap.FibonacciHeap
{
    /// <summary>
    ///     A generic implementation of a Fibonacci heap.
    /// </summary>
    /// <remarks>
    ///     <para>
    ///         A Fibonacci heap is similar to a standard binary heap
    ///         <see cref="DataStructures.Heap.BinaryHeap{T}" />, however it uses concepts
    ///         of amortized analysis to provide theoretical speedups on common operations like
    ///         insert, union, and decrease-key while maintaining the same speed on all other
    ///         operations.
    ///     </para>
    ///     <para>
    ///         In practice, Fibonacci heaps are more complicated than binary heaps and require
    ///         a large input problems before the benifits of the theoretical speed up
    ///         begin to show.
    ///     </para>
    ///     <para>
    ///         References:
    ///         [1] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest,
    ///         and Clifford Stein. 2009. Introduction to Algorithms, Third Edition (3rd. ed.).
    ///         The MIT Press.
    ///     </para>
    /// </remarks>
    /// <typeparam name="T">Type of elements in binary heap.</typeparam>
    public class FibonacciHeap<T> where T : IComparable
    {
        /// <summary>
        ///     Gets or sets the count of the number of nodes in the Fibonacci heap.
        /// </summary>
        public int Count { get; set; }

        /// <summary>
        ///     Gets or sets a reference to the MinItem. The MinItem and all of its siblings
        ///     comprise the root list, a list of trees that satisfy the heap property and
        ///     are joined in a circularly doubly linked list.
        /// </summary>
        private FHeapNode<T>? MinItem { get; set; }

        /// <summary>
        ///     Add item <c>x</c> to this Fibonacci heap.
        /// </summary>
        /// <remarks>
        ///     To add an item to a Fibonacci heap, we simply add it to the "root list",
        ///     a circularly doubly linked list where our minimum item sits. Since adding
        ///     items to a linked list takes O(1) time, the overall time to perform this
        ///     operation is O(1).
        /// </remarks>
        /// <param name="x">An item to push onto the heap.</param>
        /// <returns>
        ///     A reference to the item as it is in the heap. This is used for
        ///     operations like decresing key.
        /// </returns>
        public FHeapNode<T> Push(T x)
        {
            Count++;

            var newItem = new FHeapNode<T>(x);

            if (MinItem == null)
            {
                MinItem = newItem;
            }
            else
            {
                MinItem.AddRight(newItem);

                if (newItem.Key.CompareTo(MinItem.Key) < 0)
                {
                    MinItem = newItem;
                }
            }

            return newItem;
        }

        /// <summary>
        ///     Combines all the elements of two fibonacci heaps.
        /// </summary>
        /// <remarks>
        ///     To union two Fibonacci heaps is a single fibonacci heap is a single heap
        ///     that contains all the elements of both heaps. This can be done in O(1) time
        ///     by concatenating the root lists together.
        ///     For more details on how two circularly linked lists are concatenated, see
        ///     <see cref="FHeapNode{T}.ConcatenateRight" />
        ///     Finally, check to see which of <c>this.MinItem</c> and <c>other.MinItem</c>
        ///     is smaller, and set <c>this.MinItem</c> accordingly
        ///     This operation destroys <c>other</c>.
        /// </remarks>
        /// <param name="other">
        ///     Another heap whose elements we wish to add to this heap.
        ///     The other heap will be destroyed as a result.
        /// </param>
        public void Union(FibonacciHeap<T> other)
        {
            // If there are no items in the other heap, then there is nothing to do.
            if (other.MinItem == null)
            {
                return;
            }

            // If this heap is empty, simply set it equal to the other heap
            if (MinItem == null)
            {
                // Set this heap to the other one
                MinItem = other.MinItem;
                Count = other.Count;

                // Destroy the other heap
                other.MinItem = null;
                other.Count = 0;

                return;
            }

            Count += other.Count;

            // <see cref="DataStructures.FibonacciHeap{T}.FHeapNode.ConcatenateRight(DataStructures.FibonacciHeap{T}.FHeapNode)"/>
            MinItem.ConcatenateRight(other.MinItem);

            // Set the MinItem to the smaller of the two MinItems
            if (other.MinItem.Key.CompareTo(MinItem.Key) < 0)
            {
                MinItem = other.MinItem;
            }

            other.MinItem = null;
            other.Count = 0;
        }

        /// <summary>
        ///     Return the MinItem and remove it from the heap.
        /// </summary>
        /// <remarks>
        ///     This function (with all of its helper functions) is the most complicated
        ///     part of the Fibonacci Heap. However, it can be broken down into a few steps.
        ///     <list type="number">
        ///         <item>
        ///             Add the children of MinItem to the root list. Either one of these children,
        ///             or another of the items in the root list is a candidate to become the new
        ///             MinItem.
        ///         </item>
        ///         <item>
        ///             Remove the MinItem from the root list and appoint a new MinItem temporarily.
        ///         </item>
        ///         <item>
        ///             <see cref="Consolidate" /> what's left
        ///             of the heap.
        ///         </item>
        ///     </list>
        /// </remarks>
        /// <returns>The minimum item from the heap.</returns>
        public T Pop()
        {
            FHeapNode<T>? z = null;
            if (MinItem == null)
            {
                throw new InvalidOperationException("Heap is empty!");
            }

            z = MinItem;

            // Since z is leaving the heap, add its children to the root list
            if (z.Child != null)
            {
                foreach (var x in SiblingIterator(z.Child))
                {
                    x.Parent = null;
                }

                // This effectively adds each child x to the root list
                z.ConcatenateRight(z.Child);
            }

            if (Count == 1)
            {
                MinItem = null;
                Count = 0;
                return z.Key;
            }

            // Temporarily reassign MinItem to an arbitrary item in the root
            // list
            MinItem = MinItem.Right;

            // Remove the old MinItem from the root list altogether
            z.Remove();

            // Consolidate the heap
            Consolidate();

            Count -= 1;

            return z.Key;
        }

        /// <summary>
        ///     A method to see what's on top of the heap without changing its structure.
        /// </summary>
        /// <returns>
        ///     Returns the top element without popping it from the structure of
        ///     the heap.
        /// </returns>
        public T Peek()
        {
            if (MinItem == null)
            {
                throw new InvalidOperationException("The heap is empty");
            }

            return MinItem.Key;
        }

        /// <summary>
        ///     Reduce the key of x to be k.
        /// </summary>
        /// <remarks>
        ///     k must be less than x.Key, increasing the key of an item is not supported.
        /// </remarks>
        /// <param name="x">The item you want to reduce in value.</param>
        /// <param name="k">The new value for the item.</param>
        public void DecreaseKey(FHeapNode<T> x, T k)
        {
            if (MinItem == null)
            {
                throw new ArgumentException($"{nameof(x)} is not from the heap");
            }

            if (x.Key == null)
            {
                throw new ArgumentException("x has no value");
            }

            if (k.CompareTo(x.Key) > 0)
            {
                throw new InvalidOperationException("Value cannot be increased");
            }

            x.Key = k;
            var y = x.Parent;
            if (y != null && x.Key.CompareTo(y.Key) < 0)
            {
                Cut(x, y);
                CascadingCut(y);
            }

            if (x.Key.CompareTo(MinItem.Key) < 0)
            {
                MinItem = x;
            }
        }

        /// <summary>
        ///     Remove x from the child list of y.
        /// </summary>
        /// <param name="x">A child of y we just decreased the value of.</param>
        /// <param name="y">The now former parent of x.</param>
        protected void Cut(FHeapNode<T> x, FHeapNode<T> y)
        {
            if (MinItem == null)
            {
                throw new InvalidOperationException("Heap malformed");
            }

            if (y.Degree == 1)
            {
                y.Child = null;
                MinItem.AddRight(x);
            }
            else if (y.Degree > 1)
            {
                x.Remove();
            }
            else
            {
                throw new InvalidOperationException("Heap malformed");
            }

            y.Degree--;
            x.Mark = false;
            x.Parent = null;
        }

        /// <summary>
        ///     Rebalances the heap after the decrease operation takes place.
        /// </summary>
        /// <param name="y">An item that may no longer obey the heap property.</param>
        protected void CascadingCut(FHeapNode<T> y)
        {
            var z = y.Parent;
            if (z != null)
            {
                if (!y.Mark)
                {
                    y.Mark = true;
                }
                else
                {
                    Cut(y, z);
                    CascadingCut(z);
                }
            }
        }

        /// <summary>
        ///     <para>
        ///         Consolidate is analogous to Heapify in <see cref="DataStructures.Heap.BinaryHeap{T}" />.
        ///     </para>
        ///     <para>
        ///         First, an array <c>A</c> [0...D(H.n)] is created where H.n is the number
        ///         of items in this heap, and D(x) is the max degree any node can have in a
        ///         Fibonacci heap with x nodes.
        ///     </para>
        ///     <para>
        ///         For each node <c>x</c> in the root list, try to add it to <c>A[d]</c> where
        ///         d is the degree of <c>x</c>.
        ///         If there is already a node in <c>A[d]</c>, call it <c>y</c>, and make
        ///         <c>y</c> a child of <c>x</c>. (Swap <c>x</c> and <c>y</c> beforehand if
        ///         <c>x</c> is greater than <c>y</c>). Now that <c>x</c> has one more child,
        ///         remove if from <c>A[d]</c> and add it to <c>A[d+1]</c> to reflect that its
        ///         degree is one more. Loop this behavior until we find an empty spot to put
        ///         <c>x</c>.
        ///     </para>
        ///     <para>
        ///         With <c>A</c> all filled, empty the root list of the heap. And add each item
        ///         from <c>A</c> into the root list, one by one, making sure to keep track of
        ///         which is smallest.
        ///     </para>
        /// </summary>
        protected void Consolidate()
        {
            if (MinItem == null)
            {
                return;
            }

            // There's a fact in Intro to Algorithms:
            // "the max degree of any node in an n-node fibonacci heap is O(lg(n)).
            // In particular, we shall show that D(n) <= floor(log_phi(n)) where phi is
            // the golden ratio, defined in equation 3.24 as phi = (1 + sqrt(5))/2"
            //
            // For a proof, see [1]
            var maxDegree = 1 + (int)Math.Log(Count, (1 + Math.Sqrt(5)) / 2);

            // Create slots for every possible node degree of x
            var a = new FHeapNode<T>?[maxDegree];
            var siblings = SiblingIterator(MinItem).ToList();
            foreach (var w in siblings)
            {
                var x = w;
                var d = x.Degree;

                var y = a[d];

                // While A[d] is not empty, we can't blindly put x here
                while (y != null)
                {
                    if (x.Key.CompareTo(y.Key) > 0)
                    {
                        // Exchange x and y
                        var temp = x;
                        x = y;
                        y = temp;
                    }

                    // Make y a child of x
                    FibHeapLink(y, x);

                    // Empty out this spot since x now has a higher degree
                    a[d] = null;

                    // Add 1 to x's degree before going back into the loop
                    d++;

                    y = a[d];
                }

                // Now that there's an empty spot for x, place it there
                a[d] = x;
            }

            ReconstructHeap(a);
        }

        /// <summary>
        ///     Reconstructs the heap based on the array of node degrees created by the consolidate step.
        /// </summary>
        /// <param name="a">An array of FHeapNodes where a[i] represents a node of degree i.</param>
        private void ReconstructHeap(FHeapNode<T>?[] a)
        {
            // Once all items are in A, empty out the root list
            MinItem = null;

            for (var i = 0; i < a.Length; i++)
            {
                var r = a[i];
                if (r == null)
                {
                    continue;
                }

                if (MinItem == null)
                {
                    // If the root list is completely empty, make this the new
                    // MinItem
                    MinItem = r;

                    // Make a new root list with just this item. Make sure to make
                    // it its own list.
                    MinItem.SetSiblings(MinItem, MinItem);
                    MinItem.Parent = null;
                }
                else
                {
                    // Add A[i] to the root list
                    MinItem.AddRight(r);

                    // If this item is smaller, make it the new min item
                    if (MinItem.Key.CompareTo(r.Key) > 0)
                    {
                        MinItem = a[i];
                    }
                }
            }
        }

        /// <summary>
        ///     Make y a child of x.
        /// </summary>
        /// <param name="y">A node to become the child of x.</param>
        /// <param name="x">A node to become the parent of y.</param>
        private void FibHeapLink(FHeapNode<T> y, FHeapNode<T> x)
        {
            y.Remove();
            x.AddChild(y);
            y.Mark = false;
        }

        /// <summary>
        ///     A helper function to iterate through all the siblings of this node in the
        ///     circularly doubly linked list.
        /// </summary>
        /// <param name="node">A node we want the siblings of.</param>
        /// <returns>An iterator over all of the siblings.</returns>
        private IEnumerable<FHeapNode<T>> SiblingIterator(FHeapNode<T> node)
        {
            var currentNode = node;
            yield return currentNode;

            currentNode = node.Right;
            while (currentNode != node)
            {
                yield return currentNode;
                currentNode = currentNode.Right;
            }
        }
    }
}
