{-# LANGUAGE DeriveGeneric #-}
{-# CFILES cbits/hfrequencyqueue_backend.cpp #-}
{-|
Module      : Data.FrequencyQueue.IO
Description : Provide the IO interface for FrequencyQueue
Copyright   : (c) Andrea Bellandi 2014
License     : GPL-3
Maintainer  : bellaz89@gmai.com
Stability   : experimental
Portability : POSIX

This module export the IO interface of FrequencyQueue. 
-}
module Data.FrequencyQueue.IO(
  -- *Types
  FrequencyQueue(),
  -- *Functions
  -- **Creation functions
  newFrequencyQueue, cloneFrequencyQueue,
  -- **Basic properties
  length, probabilityUnit,
  -- **Pop-push functions
  pushBack, popBack, popBackMax, popBackMin, getRandom, getRandomPop,
  -- **Iterative functions
  mapWprobability, foldWprobability,
  -- **Unsafe interface
  popBackUnsafe, popBackMaxUnsafe, popBackMinUnsafe, getRandomUnsafe, getRandomPopUnsafe) where

import Prelude hiding (length)
import GHC.Generics

import Control.Monad(replicateM)
import Foreign.Concurrent(newForeignPtr)
import Foreign.Marshal.Utils(new)
import Foreign.Marshal.Alloc(free)
import Foreign.CStorable(CStorable, cAlignment, cSizeOf, cPoke, cPeek)
import Foreign.Storable(Storable, alignment, sizeOf, poke, peek)
import Foreign.ForeignPtr(ForeignPtr, withForeignPtr)
import Foreign.StablePtr(StablePtr, deRefStablePtr, freeStablePtr, newStablePtr)
import Foreign.Ptr(Ptr)
import Foreign.C.Types

type FrequencyQueue_ a = Ptr a

-- | FrequencyQueue the basic type of the Library
data FrequencyQueue a = FrequencyQueue{ queue :: ForeignPtr a}

data RandomElement a = RandomElement{ probability :: CUInt,
                                      element :: StablePtr a}
                       deriving(Generic)


instance CStorable (StablePtr a) where
  cAlignment = alignment
  cSizeOf    = sizeOf
  cPoke      = poke
  cPeek      = peek

instance CStorable (RandomElement a)

instance  Storable (RandomElement a) where 
  alignment = cAlignment
  sizeOf    = cSizeOf
  poke      = cPoke
  peek      = cPeek

-- the foreign import shouldn't call functions that call-back the GHC runtime (clone_FrequencyQueue_priv_ and free_FrequencyQueue_priv_)
-- unsafely. Functions that are called unsafely should have constant or constant amortized time to not block the caller OS too much. 
foreign import ccall unsafe new_FrequencyQueue_priv_ :: CUInt -> IO (FrequencyQueue_ a)
foreign import ccall clone_FrequencyQueue_priv_ :: (FrequencyQueue_ a) -> IO (FrequencyQueue_ a)
foreign import ccall unsafe length_priv_ :: (FrequencyQueue_ a) -> IO (CUInt)
foreign import ccall unsafe probability_unit_priv_ :: (FrequencyQueue_ a) -> IO (CUInt)       
foreign import ccall unsafe push_back_priv_ :: (FrequencyQueue_ a) -> (Ptr (RandomElement a)) -> IO ()
foreign import ccall unsafe pop_back_priv_ ::  (FrequencyQueue_ a) -> IO (Ptr (RandomElement a))
foreign import ccall pop_back_max_prob_priv_ :: (FrequencyQueue_ a) -> IO (Ptr (RandomElement a)) 
foreign import ccall pop_back_min_prob_priv_ :: (FrequencyQueue_ a) -> IO (Ptr (RandomElement a)) 
foreign import ccall get_random_priv_ :: (FrequencyQueue_ a) -> IO (Ptr (RandomElement a))
foreign import ccall get_random_pop_priv_ :: (FrequencyQueue_ a) -> IO (Ptr (RandomElement a))
foreign import ccall unsafe reset_iterator_priv_ :: (FrequencyQueue_ a) -> IO ()
foreign import ccall unsafe get_next_priv_ :: (FrequencyQueue_ a) -> IO (Ptr (RandomElement a))
foreign import ccall unsafe get_random_number_priv_ :: (FrequencyQueue_ a) -> IO (CUInt)
foreign import ccall free_FrequencyQueue_priv_ :: FrequencyQueue_ a -> IO ()
foreign import ccall unsafe free_RandomElement_priv_ :: (Ptr (RandomElement a)) -> IO ()
foreign export ccall freeStablePtr :: StablePtr a -> IO () 
foreign export ccall makeNewStableRef :: StablePtr a -> IO (StablePtr a)

makeNewStableRef :: StablePtr a -> IO (StablePtr a)
makeNewStableRef ptr = deRefStablePtr ptr >>= newStablePtr 

-- |Create a new FrequencyQueue with a seed
newFrequencyQueue :: Int -> IO (FrequencyQueue a)
newFrequencyQueue seed = do rawqueue <- new_FrequencyQueue_priv_ (fromIntegral seed)
                            queue_ <- newForeignPtr rawqueue (free_FrequencyQueue_priv_ rawqueue) 
                            return (FrequencyQueue queue_)

-- |Make a clone of the FrequencyQueue Passed
cloneFrequencyQueue :: FrequencyQueue a -> IO (FrequencyQueue a)
cloneFrequencyQueue oldqueue = do rawqueue <- withForeignPtr (queue oldqueue) clone_FrequencyQueue_priv_
                                  queue_ <- newForeignPtr rawqueue (free_FrequencyQueue_priv_ rawqueue) 
                                  return (FrequencyQueue queue_)

-- |Return the number of elements in the queue
length :: FrequencyQueue a -> IO Int
length queue_ =  withForeignPtr (queue queue_) length_priv_ >>= (return . fromIntegral) 

-- |Return the sum of all elements' probabilities passed to the queue
probabilityUnit :: FrequencyQueue a -> IO Int
probabilityUnit queue_ = withForeignPtr (queue queue_) probability_unit_priv_ >>= (return . fromIntegral)

-- |Push an element a in the queue with a corresponding relative probability
pushBack :: FrequencyQueue a -> a -> Int -> IO()
pushBack queue_ element_ probability_ = do stableElement_ <- newStablePtr element_
                                           let cUIntProbability = (fromIntegral probability_)
                                           let randomElement_ = RandomElement cUIntProbability stableElement_
                                           allocatedElement_ <- new randomElement_
                                           withForeignPtr (queue queue_) (\x -> push_back_priv_ x allocatedElement_) 
                                           free allocatedElement_

-- |Pop an element of the queue. Return Nothing if the queue is empty
popBack :: FrequencyQueue a -> IO (Maybe (a,Int))
popBack queue_ = makeSafePop queue_ popBackUnsafe

-- |Pop the element of the queue that have the biggest relative probability.
--  Return Nothing if the queue is empty
popBackMax :: FrequencyQueue a -> IO (Maybe (a,Int))
popBackMax queue_ = makeSafePop queue_ popBackMaxUnsafe

-- |Pop the element of the queue that have the smallest relative probability.
-- Return Nothing if the queue is empty
popBackMin :: FrequencyQueue a -> IO (Maybe (a,Int))
popBackMin queue_ = makeSafePop queue_ popBackMinUnsafe

-- |Return a random element from the queue using its relative probability.
-- Return Nothing if the queue is empty
getRandom :: FrequencyQueue a -> IO (Maybe (a,Int))
getRandom queue_ = makeSafePop queue_ getRandomUnsafe

-- |Pop a random element from the queue using its relative probability.
-- Return Nothing if the queue is empty
getRandomPop :: FrequencyQueue a -> IO (Maybe (a,Int))
getRandomPop queue_ = makeSafePop queue_ getRandomPopUnsafe

-- |Return a new queue with the elements and relative probability mapped
-- by the function provided
mapWprobability :: ((a, Int) -> (b, Int)) -> FrequencyQueue a -> IO (FrequencyQueue b)
mapWprobability fun queue_ = do rnd_number <- withForeignPtr (queue queue_) get_random_number_priv_
                                queue_length <- length queue_
                                newqueue_ <- newFrequencyQueue (fromIntegral rnd_number)
                                withForeignPtr (queue queue_) reset_iterator_priv_
                                replicateM (queue_length) (trasformCopyQueue queue_ newqueue_)
                                return newqueue_
  where
    trasformCopyQueue q1 q2 = do ptr_rawelement <- withForeignPtr (queue q1) get_next_priv_
                                 result <- peek ptr_rawelement
                                 let probability_ = probability result
                                 let elementStable_ = element result
                                 element_ <- deRefStablePtr elementStable_
                                 let transformed_element_ = fun (element_, fromIntegral probability_)
                                 pushBack q2 (fst transformed_element_) (snd transformed_element_)

-- |Return a folded value made by an initial value b and a folding function
-- evaluated on the entire queue.
foldWprobability :: (b -> (a, Int) -> b) -> b -> FrequencyQueue a -> IO b
foldWprobability fold_fun b0 queue_ = do withForeignPtr (queue queue_) reset_iterator_priv_
                                         queue_length <- length queue_
                                         iterateOverFrequencyQueue queue_length b0
  where
    iterateOverFrequencyQueue 0     acc = return acc
    iterateOverFrequencyQueue nitem acc = do ptr_rawelement <- withForeignPtr (queue queue_) get_next_priv_
                                             result <- peek ptr_rawelement
                                             let probability_ = probability result
                                             let elementStable_ = element result
                                             element_ <- deRefStablePtr elementStable_
                                             let next_acc = fold_fun acc (element_, fromIntegral probability_)
                                             iterateOverFrequencyQueue (nitem-1) next_acc

-- |Pop an element of the queue. Fail if empty
popBackUnsafe :: FrequencyQueue a -> IO (a, Int)
popBackUnsafe queue_ = do ptr_rawelement <- withForeignPtr (queue queue_) pop_back_priv_
                          deRefRawElementPtr ptr_rawelement

-- |Pop the element of the queue that have the biggest relative probability.
-- Fail if empty
popBackMaxUnsafe :: FrequencyQueue a -> IO (a, Int)
popBackMaxUnsafe queue_ = do ptr_rawelement <- withForeignPtr (queue queue_) pop_back_max_prob_priv_
                             deRefRawElementPtr ptr_rawelement

-- |Pop the element of the queue that have the smallest relative probability.
-- Fail if empty
popBackMinUnsafe :: FrequencyQueue a -> IO (a, Int)
popBackMinUnsafe queue_ = do ptr_rawelement <- withForeignPtr (queue queue_) pop_back_min_prob_priv_
                             deRefRawElementPtr ptr_rawelement

-- |Pop the element of the queue that have the smallest relative probability.
-- Fail if empty
getRandomUnsafe :: FrequencyQueue a -> IO (a, Int)
getRandomUnsafe queue_ = do ptr_rawelement <- withForeignPtr (queue queue_) get_random_priv_
                            result <- peek ptr_rawelement
                            let probability_ = probability result
                            let elementStable_ = element result
                            element_ <- deRefStablePtr elementStable_
                            return (element_, fromIntegral probability_)

-- |Pop a random element from the queue using its relative probability.
-- Fail if empty
getRandomPopUnsafe :: FrequencyQueue a -> IO (a, Int)
getRandomPopUnsafe queue_ = do ptr_rawelement <- withForeignPtr (queue queue_) get_random_pop_priv_
                               deRefRawElementPtr ptr_rawelement

deRefRawElementPtr :: Ptr (RandomElement a) -> IO (a, Int)
deRefRawElementPtr ptr_rawelement = do result <- peek ptr_rawelement
                                       free_RandomElement_priv_ ptr_rawelement
                                       let probability_ = probability result
                                       let elementStable_ = element result
                                       element_ <- deRefStablePtr elementStable_
                                       freeStablePtr elementStable_
                                       return (element_, fromIntegral probability_)

makeSafePop :: FrequencyQueue a -> (FrequencyQueue a -> IO (a, Int)) -> IO (Maybe (a,Int))
makeSafePop queue_ unsafefun = do qlength <- length queue_ 
                                  if qlength == 0
                                  then return Nothing
                                  else (unsafefun queue_) >>= (\x -> return (Just x))
