{-# LANGUAGE BangPatterns #-}

-- This data structure is based on skew heap.
--
-- If we take weight as priority, a typical heap (priority queue)
-- is not fair enough. Consider two weight 201 for A and 101 for B.
-- A typical heap would generate A(201), A(200), A(199), A(198), ....,
-- and finally A(101), B(101), A(100), B(100).
-- What we want is A, A, B, A, A, B...
--
-- So, we introduce randomness to Skew Heap.
--
-- In the random binary tree,
-- an element is selected as the root with probability of
-- 1 / (n + 1) where n is the size of the original tree.
-- In the random skew heap, an element is selected as the root
-- with the probability of weight / total_weight.
--
-- Since this data structure uses random numbers, APIs should be
-- essentially impure. But since this is used with STM,
-- APIs are made to be pure with unsafePerformIO.

module RandomSkewHeap (
    PriorityQueue
  , empty
  , isEmpty
  , enqueue
  , dequeue
  , delete
  ) where

import Data.List (partition)
import System.IO.Unsafe (unsafePerformIO)
import System.Random.MWC (createSystemRandom, uniformR, GenIO)

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

type Key = Int
type Weight = Int

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

data PriorityQueue a = Leaf | Node {-# UNPACK #-} !Weight -- total
                                   {-# UNPACK #-} !Key
                                   {-# UNPACK #-} !Weight
                                   !a
                                   !(PriorityQueue a)
                                   !(PriorityQueue a) deriving Show

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

empty :: PriorityQueue a
empty = Leaf

isEmpty :: PriorityQueue a -> Bool
isEmpty Leaf = True
isEmpty _    = False

singleton :: Key -> Weight -> a -> PriorityQueue a
singleton k w v = Node w k w v Leaf Leaf

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

enqueue :: Key -> Weight -> a -> PriorityQueue a -> PriorityQueue a
enqueue k w v q = merge (singleton k w v) q

-- if l is a singleton, w1 == tw1.
merge :: PriorityQueue t -> PriorityQueue t -> PriorityQueue t
merge t Leaf = t
merge Leaf t = t
merge l@(Node tw1 k1 w1 v1 ll lr) r@(Node tw2 k2 w2 v2 rl rr)
  | g <= w1   = Node tw k1 w1 v1 lr $ merge ll r
  | otherwise = Node tw k2 w2 v2 rr $ merge rl l
  where
    tw = tw1 + tw2
    g = unsafePerformIO $ uniformR (1,tw) gen
{-# NOINLINE merge #-}

dequeue :: PriorityQueue a -> Maybe (Key, Weight, a, PriorityQueue a)
dequeue Leaf               = Nothing
dequeue (Node _ k w v l r) = Just (k, w, v, t)
  where
    !t = merge l r

delete :: Key -> PriorityQueue a -> (Maybe a, PriorityQueue a)
delete k q = (mv, fromList xs')
  where
    !xs = toList q
    (!ds,!xs') = partition (\(k',_,_) -> k' == k) xs
    mv = case ds of
       []          -> Nothing
       ((_,_,v):_) -> Just v

toList :: PriorityQueue a -> [(Key, Weight, a)]
toList q = go q id []
  where
    go Leaf b = b
    go (Node _ k w v l r) b = go r (go l (((k,w,v) :) . b))

fromList :: [(Key, Weight, a)] -> PriorityQueue a
fromList xs = go empty xs
  where
    go !q [] = q
    go !q ((k,w,v):xks) = go (enqueue k w v q) xks

gen :: GenIO
gen = unsafePerformIO createSystemRandom
