{- |
Module      :  Control.Monad.Trans.Memo.Vector
Copyright   :  (c) Eduard Sergeev 2013
License     :  BSD-style (see the file LICENSE)

Maintainer  :  eduard.sergeev@gmail.com
Stability   :  experimental
Portability :  non-portable (multi-param classes, flexible instances)

VectorCache - mutable-vector-based (`IO` and `ST` hosted) `MonadCache`

The fastest memoization cache, however it is even more limiting than "Control.Monad.Memo.Array" due to nature of "Data.Vector.Mutable". Still if you can use this cache please do since it will give you dramatic calculation speed up in comparison to pure `Data.Map.Map`-based cache, especially when unboxed `UVectorCache` is used.

Limitations: Since `Data.Vector.Generic.Mutable.MVector` is used as `MonadCache` the key must be `Int` and the size of the cache's vector must be known beforehand with vector being allocated before the first call. In addition unboxed `UVectorCache` can only store `Data.Vector.Unboxed.Unbox` values (but it does it very efficiently).

-}

{-# LANGUAGE NoImplicitPrelude,
  MultiParamTypeClasses, FunctionalDependencies,
  FlexibleInstances, FlexibleContexts, TypeFamilies,
  UndecidableInstances, TypeSynonymInstances #-}

module Control.Monad.Memo.Vector
 (

   -- * VectorCache for boxed types
   Vector,
   VectorCache,
   VectorMemo,
   evalVectorMemo,
   runVectorMemo,
   -- * UVectorCache for unboxed types
   UVector,
   UVectorCache,
   UVectorMemo,
   evalUVectorMemo,
   runUVectorMemo,
   -- * Generic functions for VectorCache
   Container(..),
   Cache,
   genericEvalVectorMemo,
   genericRunVectorMemo

) where

import Data.Int
import Data.Function
import Data.Maybe (Maybe(..))
import Data.Vector.Generic.Mutable
import qualified Data.Vector.Mutable as M
import qualified Data.Vector.Unboxed.Mutable as UM
import Control.Applicative
import Control.Monad
import Control.Monad.Fix
import Control.Monad.Trans.Class
import Control.Monad.Primitive

import Data.MaybeLike
import Control.Monad.Memo.Class
import Control.Monad.Trans.Memo.ReaderCache


newtype Container vec = Container { toVector :: vec }

-- | Generic Vector-based memo cache
type Cache vec s e = ReaderCache (Container (vec s e))

instance (PrimMonad m, PrimState m ~ s, MaybeLike e v, MVector c e) =>
    MonadCache Int v (Cache c s e m) where
        {-# INLINE lookup #-}
        lookup k = do
          c <- container
          e <- lift $ read (toVector c) k
          return (if isNothing e then Nothing else Just (fromJust e))
        {-# INLINE add #-}
        add k v = do
          c <- container
          lift $ write (toVector c) k (just v)

instance (PrimMonad m, PrimState m ~ s, MaybeLike e v, MVector c e) =>
    MonadMemo Int v (Cache c s e m) where
        {-# INLINE memo #-}
        memo f k = do
          c <- container
          e <- lift $ read (toVector c) k
          if isNothing e
            then do
              v <- f k
              lift $ write (toVector c) k (just v)
              return v
            else return (fromJust e)


-- VectorCache for boxed types
-- --------------------------

-- | Boxed vector
type Vector = M.MVector

-- | `MonadCache` based on boxed vector
type VectorCache s e = Cache Vector s e

-- | This is just to be able to infer the type of the `VectorCache` element.
class MaybeLike e v => VectorMemo v e | v -> e

-- | Evaluate computation using mutable boxed vector
--
-- Vector length must covers all possible keys used in computation
-- otherwise /index out of bound/ error is generated by vector code
evalVectorMemo :: (PrimMonad m, VectorMemo v e) =>
                  VectorCache (PrimState m) e m a  -- ^memoized computation
               -> Int                              -- ^vector length
               -> m a                              -- ^result
{-# INLINE evalVectorMemo #-}
evalVectorMemo = genericEvalVectorMemo

-- | Evaluate computation using mutable boxed vector.
-- It also returns the final content of the vector cache
--
-- Vector length must covers all possible keys used in computation
-- otherwise /index out of bound/ error is generated by vector code
runVectorMemo :: (PrimMonad m, VectorMemo v e) =>
                 VectorCache (PrimState m) e m a  -- ^memoized computation
              -> Int                              -- ^vector length
              -> m (a, Vector (PrimState m) e)    -- ^result and final vector cache
{-# INLINE runVectorMemo #-}
runVectorMemo = genericRunVectorMemo


-- VectorCache for unboxed types
-- ----------------------------

-- | Unboxed vector
type UVector = UM.MVector

-- | `MonadCache` based on unboxed vector
type UVectorCache s e = Cache UVector s e

-- | This is just to be able to infer the type of the `UVectorCache` element.
class MaybeLike e v => UVectorMemo v e | v -> e

-- | Evaluate computation using mutable unboxed vector
--
-- Vector length must covers all possible keys used in computation
-- otherwise /index out of bound/ error is generated by vector code
evalUVectorMemo :: (PrimMonad m, MVector UVector e, UVectorMemo v e) =>
                   UVectorCache (PrimState m) e m a  -- ^memoized computation
                -> Int                               -- ^vector length
                -> m a                               -- ^result
{-# INLINE evalUVectorMemo #-}
evalUVectorMemo = genericEvalVectorMemo

-- | Evaluate computation using mutable unboxed vector.
-- It also returns the final content of the vector cache
--
-- Vector length must covers all possible keys used in computation
-- otherwise /index out of bound/ error is generated by vector code
runUVectorMemo :: (PrimMonad m, MVector UVector e, UVectorMemo v e) =>
                  UVectorCache (PrimState m) e m a  -- ^memoized computation
               -> Int                               -- ^vector length
               -> m (a, UVector (PrimState m) e)    -- ^result and final vector cache
{-# INLINE runUVectorMemo #-}
runUVectorMemo = genericRunVectorMemo


genericEvalVectorMemo :: (MaybeLike e v, PrimMonad m, MVector c e) =>
                         Cache c (PrimState m) e m a -> Int -> m a
{-# INLINE genericEvalVectorMemo #-}
genericEvalVectorMemo m n = do
  c <- replicate n nothing
  evalReaderCache m (Container c)

genericRunVectorMemo :: (MaybeLike e v, PrimMonad m, MVector c e) =>
                        Cache c (PrimState m) e m a -> Int -> m (a, c (PrimState m) e)
{-# INLINE genericRunVectorMemo #-}
genericRunVectorMemo m n = do
  c <- replicate n nothing
  a <- evalReaderCache m (Container c)
  return (a, c)