{-----------------------------------------------------------------------------
    Reactive Banana
------------------------------------------------------------------------------}
{-# LANGUAGE Rank2Types, BangPatterns #-}
module Reactive.Banana.Internal.TotalOrder (
    -- * Synopsis
    -- | Data structure that represents a total order.
    
    -- * TotalOrder
    TotalOrder, TotalOrderZipper,
    open, close, fromAscList, ascend, descend, insertBeforeFocus, delete,
    withTotalOrder,
    
    -- * Queue
    Queue(..), insertList, isEmpty,
    ) where


import Control.Applicative
import Control.Arrow (second)

import qualified Data.List
import Data.Maybe
import Data.Ord

import Data.Hashable
import qualified Data.HashMap.Strict as Map
import qualified Data.Set as Set

type Map = Map.HashMap
type Set = Set.Set

{-----------------------------------------------------------------------------
    Total Order implementation
------------------------------------------------------------------------------}
-- Data type representing a total order between elements
-- It's simply an ordered list of elements
newtype TotalOrder a = TO { unTO :: Map a Int }

-- Zipper variant of a total order.
data TotalOrderZipper a = TOZ { down :: [a], up :: [a] }

open  :: TotalOrder a -> TotalOrderZipper a
open (TO order) = TOZ { down = [], up = Map.keys order }

close :: (Hashable a, Eq a) => TotalOrderZipper a -> TotalOrder a
close order = TO $ Map.fromList $ zip (reverse (down order) ++ up order) [1..]

fromAscList :: (Hashable a, Eq a) => [a] -> TotalOrder a
fromAscList xs = close $ TOZ { down = [], up = xs }


-- move to the next larger element
ascend       :: TotalOrderZipper a -> TotalOrderZipper a
ascend (TOZ xs []    ) = TOZ xs     []
ascend (TOZ xs (y:ys)) = TOZ (y:xs) ys

-- move to the next smaller element
descend      :: TotalOrderZipper a -> TotalOrderZipper a
descend (TOZ []     ys) = TOZ [] ys
descend (TOZ (x:xs) ys) = TOZ xs (x:ys)

-- insert an element before the current one
insertBeforeFocus :: a -> TotalOrderZipper a -> TotalOrderZipper a
insertBeforeFocus x (TOZ xs ys) = TOZ (x:xs) ys

-- delete an element from a total order
delete       :: Eq a => a -> TotalOrderZipper a -> TotalOrderZipper a
delete x (TOZ xs ys) = TOZ (delete' x xs) (delete' x ys)
    where delete' = Data.List.delete

{-----------------------------------------------------------------------------
   Queue based on a total order
------------------------------------------------------------------------------}
-- | Obtain a queue based on a particular total order.
--
-- The type system ensures that the queue is only used temporarily.
-- The argument passed to the function is the empty queue.
withTotalOrder :: TotalOrder a -> (forall q. Queue q => q a -> b) -> b
withTotalOrder order f = f empty
    where empty = Q { order = order, queue = Set.empty }

-- public interface
class Queue q where
    insert  :: (Hashable a, Eq a) => a -> q a -> q a
    minView :: q a -> Maybe (a, q a)
    size    :: q a -> Int

-- | Check whether a queue is empty.
isEmpty :: Queue q => q a -> Bool
isEmpty = isNothing . minView

-- | Insert a collection of elements
insertList :: (Queue q, Hashable a, Eq a) => [a] -> q a -> q a
insertList xs q = foldl (flip insert) q xs

-- concrete implementation
data MyQueue a = Q { order :: TotalOrder a, queue :: Set (Pair Int a) }

data Pair a b = Pair !a b
fstPair (Pair a _) = a
instance Eq a => Eq (Pair a b) where
    x == y = fstPair x == fstPair y
instance Ord a => Ord (Pair a b) where
    compare = comparing fstPair

-- set the queue field
setQueue :: MyQueue a -> Set (Pair Int a) -> MyQueue a
setQueue q b = q { queue = b }

-- find the index of a particular element in a Total Order
position :: (Hashable a, Eq a) => TotalOrder a -> a -> Int
position (TO order) x = pos
    where Just pos = Map.lookup x order

instance Queue MyQueue where
    insert x q = q { queue = Set.insert (Pair pos x) (queue q) }
        where pos = position (order q) x
    minView  q = f <$> Set.minView (queue q)
        where f (Pair _ a,set) = (a, setQueue q set)
    size     q = Set.size (queue q)