--------------------------------------------------------------------
-- |
-- Module    : Data.MessagePack.Monad
-- Copyright : (c) Hideyuki Tanaka, 2009
-- License   : BSD3
--
-- Maintainer:  tanaka.hideyuki@gmail.com
-- Stability :  experimental
-- Portability: portable
--
-- Monadic Stream Serializers and Deserializers
--
--------------------------------------------------------------------

module Data.MessagePack.Monad(
  -- * Classes
  MonadPacker(..),
  MonadUnpacker(..),
  
  -- * Packer and Unpacker type
  PackerT(..),
  UnpackerT(..),
  
  -- * Packers
  packToString,
  packToHandle,
  packToFile,
  
  -- * Unpackers
  unpackFrom,
  unpackFromString,
  unpackFromHandle,
  unpackFromFile,
  ) where

import Control.Monad
import Control.Monad.Trans
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import System.IO

import Data.MessagePack.Base hiding (Unpacker)
import qualified Data.MessagePack.Base as Base
import Data.MessagePack.Class
import Data.MessagePack.Feed

class Monad m => MonadPacker m where
  -- | Serialize a object
  put :: OBJECT a => a -> m ()

class Monad m => MonadUnpacker m where
  -- | Deserialize a object
  get :: OBJECT a => m a

-- | Serializer Type
newtype PackerT m r = PackerT { runPackerT :: Base.Packer -> m r }

instance Monad m => Monad (PackerT m) where
  a >>= b =
    PackerT $ \pc -> do
      r <- runPackerT a pc
      runPackerT (b r) pc
  
  return r =
    PackerT $ \_ -> return r

instance MonadTrans PackerT where
  lift m = PackerT $ \_ -> m

instance MonadIO m => MonadIO (PackerT m) where
  liftIO = lift . liftIO

instance MonadIO m => MonadPacker (PackerT m) where
  put v = PackerT $ \pc -> liftIO $ do
    pack pc v

-- | Execute given serializer and returns byte sequence.
packToString :: MonadIO m => PackerT m r -> m ByteString
packToString m = do
  sb <- liftIO $ newSimpleBuffer
  pc <- liftIO $ newPacker sb
  _ <- runPackerT m pc
  liftIO $ simpleBufferData sb

-- | Execute given serializer and write byte sequence to Handle.
packToHandle :: MonadIO m => Handle -> PackerT m r -> m ()
packToHandle h m = do
  sb <- packToString m
  liftIO $ BS.hPut h sb
  liftIO $ hFlush h

-- | Execute given serializer and write byte sequence to file.
packToFile :: MonadIO m => FilePath -> PackerT m r -> m ()
packToFile p m = do
  sb <- packToString m
  liftIO $ BS.writeFile p sb

-- | Deserializer type
newtype UnpackerT m r = UnpackerT { runUnpackerT :: Base.Unpacker -> Feeder -> m r }

instance Monad m => Monad (UnpackerT m) where
  a >>= b =
    UnpackerT $ \up feed -> do
      r <- runUnpackerT a up feed
      runUnpackerT (b r) up feed
  
  return r =
    UnpackerT $ \_ _ -> return r

instance MonadTrans UnpackerT where
  lift m = UnpackerT $ \_ _ -> m

instance MonadIO m => MonadIO (UnpackerT m) where
  liftIO = lift . liftIO

instance MonadIO m => MonadUnpacker (UnpackerT m) where
  get = UnpackerT $ \up feed -> liftIO $ do
    executeOne up feed
    obj <- unpackerData up
    freeZone =<< unpackerReleaseZone up
    unpackerReset up
    let Right r = fromObject obj
    return r
    
    where
      executeOne up feed = do
        resp <- unpackerExecute up
        guard $ resp>=0
        when (resp==0) $ do
          Just bs <- feed
          unpackerFeed up bs
          executeOne up feed

-- | Execute deserializer using given feeder.
unpackFrom :: MonadIO m => Feeder -> UnpackerT m r -> m r
unpackFrom f m = do
  up <- liftIO $ newUnpacker defaultInitialBufferSize
  runUnpackerT m up f

-- | Execute deserializer using given handle.
unpackFromHandle :: MonadIO m => Handle -> UnpackerT m r -> m r
unpackFromHandle h m =
  flip unpackFrom m =<< liftIO (feederFromHandle h)

-- | Execute deserializer using given file content.
unpackFromFile :: MonadIO m => FilePath -> UnpackerT m r -> m r
unpackFromFile p m = do
  h <- liftIO $ openFile p ReadMode
  r <- flip unpackFrom m =<< liftIO (feederFromHandle h)
  liftIO $ hClose h
  return r

-- | Execute deserializer from given byte sequence.
unpackFromString :: MonadIO m => ByteString -> UnpackerT m r -> m r
unpackFromString bs m = do
  flip unpackFrom m =<< liftIO (feederFromString bs)