-      ``Data/PriorityQueue''
 -      (c) 2008 Cook, J. MR  SSD, Inc.
 -      the PriorityQueue kicks ass, if I do say so myself ;-)
 -      the |DefaultStateRef| class makes the choice of StateRef
 -      decidable, and the laxity of the StateRef classes' fundeps makes
 -      queues constructible in monads other than where they are intended
 -      to be used; eg:
 -         q <- newPriorityQueue show :: IO (PriorityQueue STM Integer)
 -      after which the whole interface to the queue is:
 -         enqueue (x :: Integer) q :: STM ()
 -         dequeue q :: STM Integer
 -      If the queue is being constructed in the same scope it is used,
 -      the full type of |newPriorityQueue f| can be inferred as well,
 -      as long as |f|'s target type is monomorphic.

module Data.PriorityQueue
        ( Enqueue(..)
        , Dequeue(..)
        , DequeueWhere(..)
        , PeekQueue(..)
        , QueueSize(..)
        , PQ
        , emptyPQ
        , mkPriorityQueue
        , mkDefaultPriorityQueue
        , PriorityQueue
        , newPriorityQueue
        , newPriorityQueueBy
        ) where

import Data.Queue.Classes

import Control.Arrow ((&&&), (***))
import Data.StateRef
import Data.Ord.ReOrd
import qualified Data.Map as M
import Data.Sequence as Seq
import Data.List as List
import Data.Foldable as Foldable

-- |The "pure" type at the chewy center.
data PQ a = forall p. Ord p =>
        PQ { priorityFunc       :: a -> p
           , queue              :: M.Map p (Seq a)

-- |A new empty 'PQ'
emptyPQ :: Ord p => (a -> p) -> PQ a
emptyPQ f = PQ f M.empty

-- |A priority queue usable in the monad 'm' with values of type 'a'
data PriorityQueue m a = 
    forall sr. ( ModifyRef sr m (PQ a) 
               ) => PriorityQueue sr

-- |Build a priority queue from a modifiable reference containing
--  a 'PQ'
mkPriorityQueue :: ModifyRef sr m (PQ a) => sr -> PriorityQueue m a
mkPriorityQueue = PriorityQueue

-- |Build a priority queue using an instance of the default modifiable 
--  reference for the requested monad and value type
mkDefaultPriorityQueue :: Ref m (PQ a) -> PriorityQueue m a
mkDefaultPriorityQueue = PriorityQueue

-- |Construct a new priority queue using the specified indexing function
newPriorityQueue :: 
        ( Monad m
        , HasRef m1
        , NewRef (Ref m1 (PQ a)) m (PQ a)
        , Ord p
        ) => (a -> p) -> m (PriorityQueue m1 a)
newPriorityQueue f = do
        pq <- newReference (emptyPQ f)
        return (mkDefaultPriorityQueue pq)

-- |Construct a new priority queue using a comparator function.  It is 
--  the user's responsibility to ensure that this function provides a
--  sensible order.
newPriorityQueueBy :: 
        ( Monad m
        , HasRef m1
        , NewRef (Ref m1 (PQ a)) m (PQ a)
        ) => (a -> a -> Ordering) -> m (PriorityQueue m1 a)
newPriorityQueueBy cmp = newPriorityQueue (ReOrd cmp)

instance Monad m => Enqueue (PriorityQueue m a) m a where
    enqueue (PriorityQueue pqRef) x = modifyReference pqRef $ \(PQ f pq) ->
        PQ f (M.insertWith (flip (><)) (f x) (singleton x) pq)
    -- the presumption here is that this is normally called for a bunch of
    -- elements of the same priority, so we prepare the input list by 
    -- grouping elements by priority.  In cases where the batch does have 
    -- large blocks of elements with the same priority, this will greatly 
    -- reduce the amount of work done by 'M.fromListWith'.  TODO: Test 
    -- whether (and when) this is worth the extra initial traversal.  Also
    -- check to make sure as much list fusion as I expect is actually 
    -- happening.
    enqueueBatch (PriorityQueue pqRef) xs = modifyReference pqRef $ \(PQ f pq) ->
        let prioritized = map (f &&& id) xs
            grouped = groupBy ((==) `on` fst) prioritized
            batches = map ((head *** fromList) . unzip) grouped
            newItems = M.fromListWith (flip (><)) batches
         in PQ f (M.unionWith (><) pq newItems)

instance Monad m => Dequeue (PriorityQueue m a) m a where
    dequeue (PriorityQueue pqRef) = atomicModifyReference pqRef $ \orig@(PQ f pq) ->
        case minViewWithKey pq of
            Nothing            -> (orig, Nothing)
            Just ((k,vs), pq') -> case viewl vs of
                EmptyL -> error "dequeue(PriorityQueue): internal inconsistency!"
                i :< is
                    | Seq.null is -> (PQ f pq', Just i)
                    | otherwise   -> (PQ f (M.insert k is pq'), Just i)

    dequeueBatch (PriorityQueue pqRef) = atomicModifyReference pqRef $ \orig@(PQ f pq) ->
        case M.minView pq of
            Nothing -> (orig, [])
            Just (xs, pq')
                | Seq.null xs -> error "dequeueBatch(PriorityQueue): internal inconsistency!"
                | otherwise   -> (PQ f pq', toList xs)

-- quick hack; there's probably a more efficient (and/or less ugly) way to do this
instance Monad m => DequeueWhere (PriorityQueue m a) m a where
    dequeueWhere (PriorityQueue pqRef) p = atomicModifyReference pqRef $ \orig@(PQ f pq) ->
        case List.break (Foldable.any p.snd) (M.toAscList pq) of
            (_, []) -> (orig, Nothing)
            (nonMatches, (k, firstMatch): rest) -> case extractFirstWhere p firstMatch of
                    (thing, otherThings)
                        | Seq.null otherThings ->
                            (PQ f (M.fromAscList (nonMatches ++ rest)), Just thing)
                        | otherwise -> 
                            (PQ f (M.fromAscList (nonMatches ++ (k, otherThings) : rest)), Just thing)

instance Monad m => PeekQueue (PriorityQueue m a) m a where
    peekQueue (PriorityQueue pqRef) = do
        PQ f pq <- readReference pqRef
        return [v | (k, vs) <- M.toAscList pq, v <- toList vs]

instance Monad m => QueueSize (PriorityQueue m a) m where
    queueSize (PriorityQueue pqRef) = do
        PQ f pq <- readReference pqRef
        return (M.fold (\xs t -> Seq.length xs + t) 0 pq)

-- |local version of minViewWithKey, because some versions of Data.Map
--  don't have it.
minViewWithKey :: M.Map k a -> Maybe ((k, a), M.Map k a)

#ifdef NoMinViewWithKey
minViewWithKey m = if M.null m
    then fail "empty map"
    else return (M.deleteFindMin m)
minViewWithKey = M.minViewWithKey

-- |'on' combinator (Data.Function doesn't always have it)
on :: (b -> b -> c) -> (a -> b) -> (a -> a -> c)
op `on` f = \x y -> f x `op` f y

-- |given a Seq known to contain at least one item matching the predicate,
-- return the (first) matching item and the seq sans that element
extractFirstWhere :: (a -> Bool) -> Seq a -> (a, Seq a)
extractFirstWhere p xs = case Seq.breakl p xs of
    (noMatch, rest) -> case viewl rest of
        x :< rest -> (x, noMatch >< rest)