{-#LANGUAGE FlexibleContexts, ExistentialQuantification, ScopedTypeVariables, OverloadedStrings, TypeSynonymInstances, FlexibleInstances #-}
module Transient.Parse where
import Transient.Internals
import Transient.Indeterminism
import Data.String
import Data.Typeable
import Control.Applicative
import Data.Char
import Data.Monoid

import System.IO.Unsafe
import Control.Monad
import Control.Monad.State
-- import Control.Exception (throw,IOException)
import Control.Concurrent.MVar


import qualified Data.ByteString.Lazy.Char8  as BS

-- | set a stream of strings to be parsed
setParseStream ::  IO (StreamData BS.ByteString) -> TransIO ()

setParseStream iox= do delData NoRemote; setState $ ParseContext iox ""

-- | set a string to be parsed
setParseString :: BS.ByteString -> TransIO ()
setParseString x = do delData NoRemote; setState $ ParseContext (return SDone) x

withParseString :: BS.ByteString -> TransIO a -> TransIO a
withParseString x parse= do
     p@(ParseContext c str) <- getState <|> return(ParseContext (return SDone) mempty)
     setParseString x
     r <- parse
     setState (ParseContext c (str :: BS.ByteString))
     return r

-- | The parse context contains either the string to be parsed or a computation that gives an stream of
-- strings or both. First, the string is parsed. If it is empty, the stream is pulled for more.
data ParseContext str = IsString str => ParseContext (IO  (StreamData str)) str deriving Typeable


-- | succeed if read the string given as parameter
string :: BS.ByteString -> TransIO BS.ByteString
string s= withData $ \str -> do
    let len= BS.length s
        ret@(s',_) = BS.splitAt len str
    if s == s' -- !> ("parse string looked, found",s,s')
      then return ret
      else empty -- !> "STRING EMPTY"

-- | fast search for a token
tDropUntilToken token= withData $ \str ->
    if BS.null str then empty else  drop2 str
  where
  drop2 str=
    if token `BS.isPrefixOf` str  !> (BS.take 2 str)
          then  return ((),BS.drop (BS.length token) str)
          else if not $ BS.null str then drop2 $ BS.tail str else empty

tTakeUntilToken :: BS.ByteString -> TransIO BS.ByteString
tTakeUntilToken token= withData $ \str -> takeit mempty str
  where
  takeit :: BS.ByteString -> BS.ByteString -> TransIO ( BS.ByteString, BS.ByteString)
  takeit res str=
   if BS.null str then return (res,str) else
      if token `BS.isPrefixOf` str
          then  return (res !> ("tTakeUntilString",res),BS.drop (BS.length token) str)
          else  if not $ BS.null str then takeit ( BS.snoc res (BS.head str)) $ BS.tail str else empty

-- | read an Integer
integer :: TransIO Integer
integer= do
    s <- tTakeWhile isNumber
    if BS.null  s  then empty else return $ stoi 0 s
  :: TransIO Integer

   where
   stoi :: Integer -> BS.ByteString -> Integer
   stoi x s| BS.null s = x
           | otherwise=  stoi (x *10 + fromIntegral(ord (BS.head s) - ord '0')) (BS.tail s)



-- | read an Int
int :: TransIO Int
int= do
    s <- tTakeWhile' isNumber
    if BS.null s then empty else return $ stoi 0 s

    where
    stoi :: Int -> BS.ByteString -> Int
    stoi x s| BS.null s = x
            | otherwise=  stoi (x *10 + (ord (BS.head s) - ord '0')) (BS.tail s)


-- | read many results with a parser (at least one) until a `end` parser succeed.



manyTill :: TransIO a -> TransIO b -> TransIO [a]
manyTill= chainManyTill (:)

chainManyTill op p end=  op <$> p <*> scan
      where
      scan  = do{ end; return mempty }
            <|>
              do{ x <- p; xs <- scan; return (x `op` xs) }

between open close p
                    = do{ open; x <- p; close; return x }

symbol = string

parens p        = between (symbol "(") (symbol ")") p  !> "parens "
braces p        = between (symbol "{") (symbol "}") p  !> "braces "
angles p        = between (symbol "<") (symbol ">") p  !> "angles "
brackets p      = between (symbol "[") (symbol "]") p  !> "brackets "

semi            = symbol ";"  !> "semi"
comma           = symbol ","  !> "comma"
dot             = symbol "."  !> "dot"
colon           = symbol ":"  !> "colon"



sepBy p sep         = sepBy1 p sep <|> return []


sepBy1 = chainSepBy1 (:)


chainSepBy chain p sep= chainSepBy1 chain p sep <|> return mempty

-- take a byteString of elements separated by a separator and  apply the desired operator to the parsed results
chainSepBy1
  :: (Monad m, Monoid b, Alternative m) =>
     (a -> b -> b) -> m a -> m x -> m b
chainSepBy1 chain p sep= do{ x <- p
                        ; xs <- chainMany chain (sep >> p)
                        ; return (x `chain` xs)
                        }
                        !> "chainSepBy "

chainMany chain v= (chain <$> v <*> chainMany chain v) <|> return mempty

commaSep p      = sepBy p comma
semiSep p       = sepBy p semi

commaSep1 p     = sepBy1 p comma
semiSep1 p      = sepBy1 p semi

dropSpaces= withData $ \str -> return( (),BS.dropWhile isSpace str)




dropTillEndOfLine= withData $ \str -> return ((),BS.dropWhile ( /= '\n') str) !> "dropTillEndOfLine"

--manyTill anyChar (tChar '\n' <|> (isDonep >> return ' ') )

parseString= do
    dropSpaces
    tTakeWhile (not . isSpace)

-- | take characters while they meet the condition
tTakeWhile :: (Char -> Bool) -> TransIO BS.ByteString
tTakeWhile cond= -- parse (BS.span cond)
    withData $ \s -> let (h,t)= BS.span cond s in if BS.null h then empty else return (h,t) !> ("tTakeWhile",h)


-- | take characters while they meet the condition and drop the next character
tTakeWhile' :: (Char -> Bool) -> TransIO BS.ByteString
tTakeWhile' cond= withData $ \s -> do
   let (h,t)= BS.span cond s
   return () !> ("takewhile'",h,t)
   if BS.null h then  empty else return (h, if BS.null t then t else BS.tail t)


just1 f x= let (h,t)= f x in (Just h,t)

-- | take n characters 
tTake n= withData $ \s ->  return $ BS.splitAt n s -- !> ("tTake",n,BS.take n s)

-- | drop n characters
tDrop n= withData $ \s ->  return $ ((),BS.drop n s)

-- | read a char
anyChar= withData $ \s -> if BS.null s then empty else return (BS.head s,BS.tail s) -- !> ("anyChar",s)

-- | verify that the next character is the one expected
tChar c= withData $ \s -> if BS.null s || BS.head s /= c then empty else return (BS.head s,BS.tail s)  !> ("tChar", BS.head s)
   --  anyChar >>= \x -> if x == c then return c else empty !> ("tChar",x)




-- | bring the lazy byteString state to a parser
-- and actualize the byteString state with the result
-- The tuple that the parser should return should be :  (what it returns, what should remain to be parsed)
withData :: (BS.ByteString -> TransIO (a,BS.ByteString)) -> TransIO a
withData parser= Transient $ do
   ParseContext readMore s <- getData `onNothing` error "parser: no context"

   let loop = unsafeInterleaveIO $ do
           mr <-  readMore

           return () !> ("readMore",mr)
           case mr of
             SMore r ->  return r  <> loop
             SLast r ->  return r
             SDone -> return mempty  -- !> "withData SDONE" 
   str <- liftIO $ return s <> loop
   --if str == mempty then return Nothing else do
   mr <- runTrans $ parser str
   case mr of
            Nothing -> return Nothing     -- !> "NOTHING"
            Just (v,str') -> do
                  setData $ ParseContext readMore str'
                  return $ Just v



-- | bring the data of the parse context as a lazy byteString
giveData= (noTrans $ do
   ParseContext readMore s <- getData `onNothing` error "parser: no context"
                                  :: StateIO (ParseContext BS.ByteString)  -- change to strict BS

   let loop = unsafeInterleaveIO $ do
           mr <-  readMore
           case mr of
            SMore r ->  (r <>) `liftM` loop
            SLast r ->  (r <>) `liftM` loop
            SDone -> return mempty
   liftIO $ (s <> ) `liftM` loop)

-- | True if the stream has finished
isDone :: TransIO Bool
isDone=  noTrans $ do
    return () !> "isDone"
    ParseContext readMore s <- getData `onNothing` error "parser: no context"
       :: StateIO (ParseContext BS.ByteString)  -- change to strict BS
    if not $ BS.null s then return False else do
      mr <- liftIO readMore
      case mr of
        SMore r -> do setData $ ParseContext readMore r ; return False
        SLast r -> do setData $ ParseContext readMore r ; return False
        SDone -> return True






-- infixl 0 |-

-- | Chain two parsers. The motivation is to parse a chunked HTTP response which contains
-- JSON messages.
--
-- If the REST response is infinite and contains JSON messages, I have to chain the 
-- dechunk parser with the JSON decoder of aeson, to produce a stream of aeson messages. 
-- Since the boundaries of chunks and JSON messages do not match, it is not possible to add a 
-- `decode` to the monadic pipeline. Since the stream is potentially infinite and/or the
-- messages may arrive at any time, I can not wait until all the input finish before decoding 
-- the messages.
--
-- I need to generate a ByteString stream with the first parser, which is the input for
-- the second parser. 
-- 
-- The first parser wait until the second consume the previous chunk, so it is pull-based.
--
-- many parsing stages can be chained with this operator.
--
-- The output is nondeterministic: it can return 0, 1 or more results
--
-- example: https://t.co/fmx1uE2SUd
(|-) :: TransIO (StreamData BS.ByteString) -> TransIO b -> TransIO b
p |- q =  do
   v  <- liftIO $ newEmptyMVar
   initp v <|> initq v

 where
 initq v= do
   --abduce
   setParseStream (takeMVar v >>= \v -> (return v !> ("!- operator return",v)))  -- each time the parser need more data, takes the var
   q

 initp v=  abduce >> repeatIt
   where
   repeatIt= (do r <- p; liftIO  (putMVar v r !> "putMVar") ; empty) <|> repeatIt