{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{- |
Module      : $Header$
Description : Module providing simple implementation based on mutable linked
              list.
Copyright   : (c) Maciej Piechotka
License     : MIT

Maintainer  : uzytkownik2@gmail.com
Stability   : none
Portability : portable

Module providing simple implementation based on mutable linked list.
It is optimised for longer parsers.
-}
module Text.Parsec.Iteratee.LinkedList
  (
    Reference(..),
    Cursor,
    NextCursor,
    mkCursor,
    parsecIteratee,
    safeParsecIteratee,
  )
where
import Control.Concurrent.MVar
import Control.Monad
import Control.Monad.ST
import Control.Monad.Trans
import Data.Maybe
import Data.Monoid
import Data.Iteratee
import Data.Iteratee.Base.StreamChunk (StreamChunk)
import Data.IORef
import qualified Data.ListLike as LL
import Data.STRef
import Text.Parsec
import Text.Parsec.Pos

-- | Class notifing a reference in monad.
-- Probably should be in separate module.
class Monad m => Reference r m where
  -- | Create new reference
  newRef :: a -- ^ An initial value
         -> m (r a) -- ^ A new reference
  -- | Reads a reference
  readRef :: r a -- ^ Reference
          -> m a -- ^ Value hold by reference
  -- | Write to reference
  writeRef :: r a -- ^ Reference
           -> a -- ^ New value
           -> m ()
  -- | Modify the reference. Default implementation is provided but it MUST be
  -- overloaded if the reference is atomic to provide an atomic write
  modifyRef :: r a -- ^ Reference
            -> (a -> m (a, b)) -- ^ Computation
            -> m b -- ^ Result of computation
  modifyRef r f = readRef r >>= f >>= \(a, b) -> writeRef r a >> return b

instance Reference IORef IO where
  newRef = newIORef
  readRef = readIORef
  writeRef = writeIORef

instance Reference (STRef s) (ST s) where
  newRef = newSTRef
  readRef = readSTRef
  writeRef = writeSTRef

instance Reference MVar IO where
  newRef = newMVar
  readRef = readMVar
  writeRef = putMVar

-- | Specify the 3 possible states of next cursor - existence, non-existence
-- and not being evaluated
data (Monad m, Reference r m, StreamChunk c el) => NextCursor r m c el
  -- | Points to next cursor
  = NextCursor (Cursor r m c el)
  -- | States that next cursor does not exists
  | None
  -- | Next cursor is not evaluated
  | Uneval

-- | Cursor holds current value and reference to possible next cursor
data (Monad m, Reference r m, StreamChunk c el) => Cursor r m c el = 
  Cursor (r (NextCursor r m c el)) (c el)

-- | Creates new cursor
mkCursor :: (Monad m, Reference r m, StreamChunk c el) => m (Cursor r m c el)
mkCursor = newRef Uneval >>= \r -> (return $! Cursor r LL.empty)

instance (Monad m, Reference r m, StreamChunk c el) =>
         Stream (Cursor r m c el) (IterateeG c el m) el where
  uncons = unconsStream

unconsStream :: (Monad m, Reference r m, StreamChunk c el)
             => Cursor r m c el
             -> IterateeG c el m (Maybe (el, Cursor r m c el))
unconsStream p@(Cursor r c)
    | LL.null c = IterateeG $ \st -> join $ modifyRef r $ unconsCursor st p
    | otherwise = return $! justUnconsCursor p

unconsCursor :: forall r m c el. (Monad m, Reference r m, StreamChunk c el)
             => StreamG c el
             -> Cursor r m c el
             -> NextCursor r m c el
             -> m (NextCursor r m c el,
                   m (IterGV c el m (Maybe (el, Cursor r m c el))))
unconsCursor st                _ rv@(NextCursor p@(Cursor r c))
  | LL.null c = return $! (rv, join $ modifyRef r $ unconsCursor st p)
  | otherwise = return $! (rv, return $! Done (justUnconsCursor p) st)
unconsCursor st                _ rv@None
              = return $! (rv, return $! Done Nothing st)
unconsCursor    (Chunk c)      p rv@Uneval
  | LL.null c = return $! (rv, return $! Cont (unconsStream p) Nothing)
  | otherwise = do r <- newRef Uneval :: m (r (NextCursor r m c el))
                   let p' = Cursor r c
                       ra = Done (justUnconsCursor p') (Chunk LL.empty)
                   return $! (NextCursor p', return $! ra)
unconsCursor st@(EOF Nothing)  _    Uneval
              = return $! (None, return $! Done Nothing st)
unconsCursor    (EOF (Just e)) _ rv@Uneval
              = return $! (rv, return $! Cont (throwErr e) (Just e))

justUnconsCursor :: (Monad m, Reference r m, StreamChunk c el) =>
                    Cursor r m c el -> Maybe (el, Cursor r m c el)
justUnconsCursor (Cursor r c) = Just $! (LL.head c, Cursor r $ LL.tail c)

concatCursor :: (Monad m, Reference r m, StreamChunk c el)
             => Cursor r m c el -> m (c el)
concatCursor c = liftM mconcat (concatCursor' c)

concatCursor' :: (Monad m, Reference r m, StreamChunk c el)
              => Cursor r m c el -> m [c el]
concatCursor' (Cursor r v) =
  liftM2 (:) (return v) (readRef r >>= concatNextCursor')

concatNextCursor' :: (Monad m, Reference r m, StreamChunk c el)
                 => NextCursor r m c el -> m [c el]
concatNextCursor' (NextCursor c) = concatCursor' $! c
concatNextCursor' _              = return $! []

-- | Runs parser. If it suceed the remaining part of stream stands in stream,
-- however if it fails the stream is not in defined state.
parsecIteratee :: (Monad m, Reference r m, StreamChunk c el)
               => ParsecT (Cursor r m c el) u (IterateeG c el m) a
                  -- ^ Parser to run
               -> u -- ^ A user state
               -> SourceName -- ^ Source name
               -> IterateeG c el m (Either ParseError a)
parsecIteratee p u sn = do
   c <- lift mkCursor
   res <- runParserT (liftM2 (,) p getInput) u sn c
   case res of
     Right (a, c') -> do sc <- lift $ concatCursor c'
                         liftI $! Done (Right a) $! Chunk $! sc
     Left   err   -> return $ Left err

-- | Runs parser. If it suceed the remaining part of stream stands in stream,
-- however if it fails everything stands in stream.
safeParsecIteratee :: (Monad m, Reference r m, StreamChunk c el)
                   => ParsecT (Cursor r m c el) u (IterateeG c el m) a
                      -- ^ Parser to run
                   -> u -- ^ A user state
                   -> SourceName -- ^ Source name
                   -> IterateeG c el m (Either ParseError a)
safeParsecIteratee p u sn = do
    c <- lift mkCursor
    Right (c', res) <- runParserT (parsecSafe p) u sn c
    sc <- lift $ concatCursor c'
    liftI $! Done res $! Chunk $! sc

parsecSafe :: Monad m
           => ParsecT s u m a
           -> ParsecT s u m (s, Either ParseError a)
parsecSafe (ParsecT p) = ParsecT $ \s -> do
    r <- join (unConsume `liftM` p s)
    case r of
      Error pe  -> let rp = Ok (stateInput s,  Left pe) s  (unknownError s)
                  in return $! Empty $ return $! rp
      Ok v s' _ -> let rp = Ok (stateInput s', Right v) s' (unknownError s')
                  in return $! Consumed $! return $! rp
    where unConsume (Consumed x) = x
          unConsume (Empty    x) = x