{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{- |
Module      : $Header$
Description : Module providing simple implementation based on accumulating
              chunks.
Copyright   : (c) John Lato
License     : Public domain

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

Module providing simple implementation based on accumulating chunks.
It is optimised for short parsers.
-}
module Text.Parsec.Iteratee.Chunk
  (
    safeParsecIterateeShort
  )
where

import qualified Data.Iteratee as I
import Data.Iteratee.Base (IterateeG (..), StreamG (..), IterGV (..))
import qualified Data.Iteratee.Base.StreamChunk as SC
import Control.Monad
import Control.Applicative
import qualified Data.ListLike as LL
import Data.Monoid
import Text.Parsec

-- |Create an Iteratee from a ParsecT parser.
-- This is most efficient for relatively smaller parsers (< 1e5 chars),
-- and becomes increasingly inefficient as the parser size increases.
-- If the parse fails, no input is consumed.  If the parse succeeds,
-- any data remaining after the parse is available to the iteratee.
safeParsecIterateeShort
  :: (Stream Int (IterateeG s t m) t, Monad m, SC.StreamChunk s t) =>
    ParsecT Int u (IterateeG s t m) a
    -> u
    -> SourceName
    -> IterateeG s t m (Either ParseError a)
safeParsecIterateeShort p u sn = do 
  res <- runParserT ((,) <$> p <*> getInput ) u sn 0
  case res of
    Right (a, lpos) -> I.drop lpos >> return (Right a)
    Left  err       -> return $ Left err

-- |Make an Iteratee instance of Parsec's Stream class.
-- This is only efficient for relatively small parsers (on order of 1e5 chars).
instance (Monad m, SC.StreamChunk s el) =>
  Stream Int (IterateeG s el m) el where
    uncons n = (liftM . fmap) (\res -> (res, n+1)) $ peekAt n


-- |Peek @n@ points ahead into the stream.  This will force chunks if
-- necessary.
peekAt :: (SC.StreamChunk s el, Monad m) => Int -> IterateeG s el m (Maybe el)
peekAt 0 = I.peek
peekAt n = IterateeG step
  where
    step c@(Chunk xs)
      | SC.null xs       = return $ Cont (peekAt n) Nothing
      | n < SC.length xs = return $ Done (Just $ LL.index xs n) c
      | True             = return $ Cont (nextChunk xs) Nothing
    step str             = return $ Done Nothing str
    nextChunk xs = IterateeG step2
      where
        step2 (Chunk xs')
          | SC.null xs' = return $ Cont (nextChunk xs) Nothing
          | n < (SC.length xs + SC.length xs') =
                          let nxs = xs `mappend` xs'
                          in return $ Done (Just $ LL.index nxs n) (Chunk nxs)
          | True        = let nxs = xs `mappend` xs'
                          in return $ Cont (nextChunk nxs) Nothing
        step2 str       = return $ Done Nothing str