{-|
  Binominal Heap

  - the fun of programming
-}

module Data.Heap.Binominal (
  -- * Data structures
    Heap(..)
  , Tree(..)
  , Rank
  -- * Creating heaps
  , empty
  , singleton
  , insert
  , fromList
  -- * Converting to a list
  , toList
  -- * Deleting
  , deleteMin
  -- * Checking heaps
  , null
  -- * Helper functions
  , merge
  , minimum
  , valid
  , heapSort
  ) where

import Control.Applicative hiding (empty)
import Data.List (foldl', unfoldr)
import Data.Maybe
import Prelude hiding (minimum, maximum, null)
import qualified Prelude as L (null)

----------------------------------------------------------------

type Rank = Int

data Tree a =
    -- | Rank, a minimum root element, trees
    Node Rank a [Tree a] deriving Show

newtype Heap a = Heap [Tree a] deriving Show

instance (Eq a, Ord a) => Eq (Heap a) where
    h1 == h2 = heapSort h1 == heapSort h2

----------------------------------------------------------------

rank :: Tree a -> Rank
rank (Node r _ _) = r

root :: Tree a -> a
root (Node _ x _) = x

link :: Ord a => Tree a -> Tree a -> Tree a
link t1@(Node r1 x1 ts1) t2@(Node r2 x2 ts2)
  | x1 <= x2  = Node (r1+1) x1 (t2:ts1)
  | otherwise = Node (r2+1) x2 (t1:ts2)

----------------------------------------------------------------

{-| Empty heap.
-}

empty :: Heap a
empty = Heap []

{-|
See if the heap is empty.

>>> Data.Heap.Binominal.null empty
True
>>> Data.Heap.Binominal.null (singleton 1)
False
-}

null :: Heap a -> Bool
null (Heap ts) = L.null ts

{-| Singleton heap.
-}

singleton :: a -> Heap a
singleton x = Heap [Node 0 x []]

----------------------------------------------------------------

{-| Insertion.

>>> insert 7 (fromList [5,3]) == fromList [3,5,7]
True
>>> insert 5 empty            == singleton 5
True
-}

insert :: Ord a => a -> Heap a -> Heap a
insert x (Heap ts) = Heap (insert' (Node 0 x []) ts)

insert' :: Ord a => Tree a -> [Tree a] -> [Tree a]
insert' t [] = [t]
insert' t ts@(t':ts')
  | rank t < rank t' = t : ts
  | otherwise        = insert' (link t t') ts'

----------------------------------------------------------------

{-| Creating a heap from a list.

>>> empty == fromList []
True
>>> singleton 'a' == fromList ['a']
True
>>> fromList [5,3] == fromList [5,3]
True
-}

fromList :: Ord a => [a] -> Heap a
fromList = foldl' (flip insert) empty

----------------------------------------------------------------

{-| Creating a list from a heap. O(N)

>>> let xs = [5,3,5]
>>> length (toList (fromList xs)) == length xs
True
>>> toList empty
[]
-}

toList :: Heap a -> [a]
toList (Heap ts) = concatMap toList' ts

toList' :: Tree a -> [a]
toList' (Node _ x []) = [x]
toList' (Node _ x ts) = x : concatMap toList' ts

----------------------------------------------------------------

{-| Finding the minimum element.

>>> minimum (fromList [3,5,1])
Just 1
>>> minimum empty
Nothing
-}

minimum :: Ord a => Heap a -> Maybe a
minimum (Heap ts) = root . fst <$> deleteMin' ts

----------------------------------------------------------------

{-| Deleting the minimum element.

>>> deleteMin (fromList [5,3,7]) == fromList [5,7]
True
>>> deleteMin empty == empty
True
-}

deleteMin :: Ord a => Heap a -> Heap a
deleteMin (Heap ts) = case deleteMin' ts of
    Nothing                  -> empty
    Just (Node _ _ ts1, ts2) -> Heap (merge' (reverse ts1) ts2)

deleteMin2 :: Ord a => Heap a -> Maybe (a, Heap a)
deleteMin2 (Heap []) = Nothing
deleteMin2 h         = (\m -> (m, deleteMin h)) <$> minimum h

deleteMin' :: Ord a => [Tree a] -> Maybe (Tree a, [Tree a])
deleteMin' []     = Nothing
deleteMin' [t]    = Just (t,[])
deleteMin' (t:ts)
  | root t < root t' = Just (t,  ts)
  | otherwise        = Just (t', t:ts')
  where
    Just (t',ts')    = deleteMin' ts

----------------------------------------------------------------
{-| Merging two heaps

>>> merge (fromList [5,3]) (fromList [5,7]) == fromList [3,5,5,7]
True
-}

merge :: Ord a => Heap a -> Heap a -> Heap a
merge (Heap ts1) (Heap ts2) = Heap (merge' ts1 ts2)

merge' :: Ord a => [Tree a] -> [Tree a] -> [Tree a]
merge' ts1 [] = ts1
merge' [] ts2 = ts2
merge' ts1@(t1:ts1') ts2@(t2:ts2')
  | rank t1 < rank t2 = t1 : merge' ts1' ts2
  | rank t2 < rank t1 = t2 : merge' ts1 ts2'
  | otherwise         = insert' (link t1 t2) (merge' ts1' ts2')

----------------------------------------------------------------
-- Basic operations
----------------------------------------------------------------

{-| Checking validity of a heap.
-}

valid :: Ord a => Heap a -> Bool
valid t = isOrdered (heapSort t)

heapSort :: Ord a => Heap a -> [a]
heapSort t = unfoldr deleteMin2 t

isOrdered :: Ord a => [a] -> Bool
isOrdered [] = True
isOrdered [_] = True
isOrdered (x:y:xys) = x <= y && isOrdered (y:xys) -- allowing duplicated keys