{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
-- |
-- Module      : Database.Monarch.Mock.Types
-- Copyright   : 2013 Noriyuki OHKAWA
-- License     : BSD3
--
-- Maintainer  : n.ohkawa@gmail.com
-- Stability   : experimental
-- Portability : unknown
--
-- Type definitions.
--
module Database.Monarch.Mock.Types
    (
      MockT
    , MockDB, mockDB
    , newMockDB, emptyMockDB
    , runMock
    , TTValue(..)
    ) where

import Control.Concurrent.STM.TVar
import Control.Monad.Error
import Control.Monad.Reader
import Control.Monad.Base
import Control.Applicative
import Control.Monad.Trans.Control
import qualified Data.ByteString as BS
import qualified Data.Map as M

import Database.Monarch.Types ( Code )

-- | KVS Value type
data TTValue = TTString BS.ByteString
             | TTInt Int
             | TTDouble Double

-- | Connection with TokyoTyrant
data MockDB = MockDB { mockDB :: M.Map BS.ByteString TTValue -- ^ DB
                     }

-- | The Mock monad transformer to provide TokyoTyrant access.
newtype MockT m a =
    MockT { unMockT :: ErrorT Code (ReaderT (TVar MockDB) m) a }
    deriving ( Functor, Applicative, Monad, MonadIO
             , MonadReader (TVar MockDB), MonadError Code, MonadBase base )

instance MonadTrans MockT where
    lift = MockT . lift . lift

instance MonadTransControl MockT where
    newtype StT MockT a = StMock { unStMock :: Either Code a }
    liftWith f = MockT . ErrorT . ReaderT $ (\r -> liftM Right (f $ \t -> liftM StMock (runReaderT (runErrorT (unMockT t)) r)))
    restoreT = MockT . ErrorT . ReaderT . const . liftM unStMock

instance MonadBaseControl base m => MonadBaseControl base (MockT m) where
    newtype StM (MockT m) a = StMMockT { unStMMockT :: ComposeSt MockT m a }
    liftBaseWith = defaultLiftBaseWith StMMockT
    restoreM = defaultRestoreM unStMMockT

-- | Empty mock DB
emptyMockDB :: MockDB
emptyMockDB = MockDB { mockDB = M.empty }

-- | Create mock DB
newMockDB :: IO (TVar MockDB)
newMockDB = newTVarIO emptyMockDB

-- | Run Mock with TokyoTyrant at target host and port.
runMock :: MonadIO m =>
           MockT m a
        -> TVar MockDB
        -> m (Either Code a)
runMock action =
    runReaderT (runErrorT $ unMockT action)