{- |
Transfer type without IO interaction.
Optimal for testing.
-}
module Network.Monad.Transfer.Offline where

import qualified Network.Monad.Transfer as Transfer
import qualified Network.Monad.Reader as Reader
import qualified Network.Monad.Body as Body

import qualified Network.Stream as Stream
import Control.Monad.Trans.Reader (ReaderT, runReaderT, )
import Control.Monad.Trans.RWS    (RWS, runRWS, tell, )
import Control.Monad.Trans (lift, )

import qualified Control.Monad.Trans.RWS as RWS

import qualified Control.Monad.Exception.Asynchronous as Async
-- import qualified Control.Monad.Exception.Synchronous  as Sync

import Data.Char (chr, )

import qualified Data.List    as List
import qualified Data.List.HT as ListHT
import Data.Maybe.HT (toMaybe, )
import Data.Tuple.HT (forcePair, mapFst, )

import qualified Data.ByteString      as BS
import qualified Data.ByteString.Lazy as BL

import Prelude hiding (splitAt, )



type T body = RWS Stream.ConnError [body] body


class Body.C body => Body body where
   splitAt    :: Int -> body -> (body, body)
   breakAfter :: (Char  -> Bool) -> body -> (body, body)

instance Body.CharType char => Body [char] where
   splitAt      = List.splitAt
   breakAfter p = ListHT.breakAfter (p . Body.toChar)

instance Body BS.ByteString where
   splitAt = BS.splitAt
   breakAfter p s =
      forcePair $
      maybe (s,BS.empty)
         (\i -> splitAt (i+1) s)
         (BS.findIndex (p . chr . fromIntegral) s)

instance Body BL.ByteString where
   splitAt = BL.splitAt . fromIntegral
   breakAfter p s =
      let (prefix,suffix) =
             BL.break (p . chr . fromIntegral) s
      in  forcePair $
          maybe
             (prefix,suffix)
             (mapFst (BL.snoc prefix))
             (BL.uncons suffix)


withBuffer :: (Body.C body) =>
   (body -> (a, body)) ->
   Transfer.AsyncExceptional (T body) a
withBuffer f =
   {-
   It is important to run all monadic actions
   independent from the exceptional case of an empty buffer,
   because only this way it is clear to the run-time system,
   that there is no write action.
   This in turn is important for a maximum of laziness.
   -}
   do buf <- RWS.get
      let (block,rest) = f buf
      RWS.put rest
      closeReason <- RWS.ask
      return $
         Async.Exceptional (toMaybe (Body.isEmpty rest) closeReason) block


transfer :: (Body body) =>
   Transfer.T (T body) body
transfer =
   Transfer.Cons {
      Transfer.readBlock  = \n -> withBuffer $ splitAt n,
      Transfer.readLine   = withBuffer $ breakAfter ('\n'==),
      Transfer.writeBlock = \str -> lift $ tell [str]
   }

run :: (Body body) =>
   Reader.T body (T body) a -> Stream.ConnError -> body -> (a, body, [body])
run m = runRWS (runReaderT m $ transfer)