module GLL.Combinators.BinInterface (
    Parser,
    parse, parseString,
    char, token, Token(..),
    epsilon, satisfy,
    many,some,optional,
    (<::=>),(<:=>),
    (<$>),
    (<$),
    (<*>),
    (*>),
    (<*),
    (<|>),
) where

import Prelude hiding ((<*>), (<*), (<$>), (<$), (*>))

import GLL.Combinators.Options
import GLL.Types.Abstract
import GLL.Types.Grammar hiding (epsilon)
import GLL.Parser (gllSPPF,ParseResult(..))

import qualified    Data.Array  as A
import qualified    Data.IntMap as IM
import qualified    Data.Map    as M
import qualified    Data.Set    as S

type Visit1     = Symbol 
type Visit2     = M.Map Nt [Alt] -> M.Map Nt [Alt]
type Visit3 a   = PCOptions -> A.Array Int Token -> ParseContext -> SPPF 
                    -> Int -> Int -> Int -> S.Set a

type Parser a   = (Visit1, Visit2, Visit3 a)

type ParseContext = IM.IntMap (IM.IntMap Nt)

-- | Given a parser and a string of tokens, return:
--  * The grammar (GLL.Types.Abstract)
--  * a list of results, which are all semantic evaluations of 'good derivations'
--      - semantic evaluations are specified by using <$> and satisfy
--      - 'good derivations' as defined by by Tom Ridge
parse' :: PCOptions -> Parser a -> [Token] -> (Grammar, ParseResult, [a])
parse' opts (Nt start,rules,sem) str = 
    let cfg     = Grammar start [] [ Rule x alts  
                                   | (x, alts) <- M.assocs (rules M.empty) ]
        parse_r = gllSPPF cfg str
        sppf    = sppf_result parse_r
        as      = sem opts arr IM.empty sppf 0 m m
        m       = length str
        arr     = A.array (0,m) (zip [0..] str)
    in (cfg,parse_r,S.toList as)

-- | The grammar of a given parser
grammar :: Parser a -> Grammar
grammar p = (\(f,_,_) -> f) (parse' defaultOptions p [])

-- | The semantic results of a parser, given a token string
parse :: Parser a -> [Token] -> [a]
parse = parseWithOptions defaultOptions 

-- | The semantic results of a parser, given a token string 
--      and GLL.Combinator.Options
parseWithOptions :: PCOptions -> Parser a -> [Token] -> [a]
parseWithOptions opts p str = (\(_,_,t) -> t) (parse' opts  p str)

-- | Get the SPPF produced by parsing the given input with the given parser
sppf :: Parser a -> [Token] -> ParseResult
sppf p str =  (\(_,s,_) -> s) (parse' defaultOptions p str)

-- | Parse a given string of characters 
parseString :: Parser a -> [Char] -> [a]
parseString p = parse p . charS

-- | Parse a given string of characters and options 
parseStringWithOptions :: PCOptions -> Parser a -> [Char] -> [a]
parseStringWithOptions opts p = parseWithOptions opts p . charS
infixl 3 <::=>
-- | use <::=> to enforce using parse context (to handle left-recursion)
(<::=>) :: String -> Parser a -> Parser a
x <::=> _r = let (sym,_r_rules,_r_sem) = _r
                 alt     = Alt x [sym] -- TODO indirection (extra alt)
                 rules m = case M.lookup x m of
                            Nothing -> _r_rules (M.insert x [alt] m)
                            Just _  -> m

                 sem opts arr ctx sppf l r m
                    | (l,r,x) `inContext` ctx = S.empty
                    | otherwise = let ctx' = (l,r,x) `toContext` ctx
                                   in _r_sem opts arr ctx' sppf l r m
              in (Nt x,rules,sem)

-- | useful for non-recursive definitions (only internally)
infixl 3 <:=>
(<:=>) :: String -> Parser a -> Parser a
x <:=> _r = let (sym,_r_rules,_r_sem) = _r
                alt     = Alt x [sym] -- TODO indirection (extra alt)
                rules m = case M.lookup x m of
                          Nothing -> _r_rules (M.insert x [alt] m)
                          Just _  -> m
              in (Nt x,rules,_r_sem)

infixl 5 <$>
-- | Application of a semantic action. 
(<$>) :: (Ord b, Ord a) => (a -> b) -> Parser a -> Parser b
f <$> _r = let (sym,rules,_r_sem) = _r
               sem opts arr ctx sppf l r m = S.map f (_r_sem opts arr ctx sppf l r m)
            in (sym,rules,sem)

infixl 6 <*>
-- | Sequence two parsers, the results of the two parsers are tupled.
(<*>) :: (Ord a, Ord b) => Parser a -> Parser b -> Parser (a,b)
_l <*> _r = (Nt lhs_id,rules,sem)
 where  l_id    = id_ _l
        r_id    = id_ _r
        lhs_id  = concat [l_id, "*", r_id]
        
        -- ** one can bind this parser and recurse on it + other duplicate work
        alt     = Alt lhs_id [sym_ _l, sym_ _r]
        rules m = case M.lookup lhs_id m of -- necessary? **
                    Nothing -> rules_ _r (rules_ _l (M.insert lhs_id [alt] m))
                    Just _  -> m
        
        sem opts arr ctx sppf l r m = 
            let filter = maybe id id $ pivot_select opts in S.fromList
            [ (a,b) | k <- filter ks
                    , a <- S.toList (sem_ _l opts arr ctx sppf l k m)
                    , b <- S.toList (sem_ _r opts arr ctx sppf k r m) ]
         where ks = maybe [] id $ sppf `pNodeLookup` ((alt,2), l, r)

infixl 4 <|>
-- | A choice between two parsers, results of the two are concatenated
(<|>) :: (Ord a) => Parser a -> Parser a -> Parser a
_l <|> _r = (Nt lhs_id,rules,sem)
 where  l_id    = id_ _l
        r_id    = id_ _r
        lhs_id  = concat [l_id, "|", r_id]

        alts    = [Alt lhs_id [sym_ _l], Alt lhs_id [sym_ _r]]
        rules m = case M.lookup lhs_id m of
                    Nothing -> rules_ _r (rules_ _l (M.insert lhs_id alts m))
                    Just _  -> m

        sem opts arr ctx sppf l r m =  
            concatChoice opts (sem_ _l opts arr ctx sppf l r m)
                              (sem_ _r opts arr ctx sppf l r m)

-- derived combinators
infixl 6 <*
-- | Sequencing, ignoring the result to the right
(<*) :: (Ord a, Ord b) => Parser a -> Parser b -> Parser a
_l <* _r = (\(x,y) -> x) <$> _l <*> _r

infixl 6 *>
-- | Sequencing, ignoring the result to the left 
(*>) :: (Ord a, Ord b) => Parser a -> Parser b -> Parser b
_l *> _r = (\(x,y) -> y) <$> _l <*> _r

infixl 5 <$
-- | Ignore all results and just return the given value
(<$) :: (Ord a, Ord b) => a -> Parser b -> Parser a
f <$ _r = const f <$> _r 

-- elementary parsers
raw_parser :: String -> Token -> (Token -> a) -> Parser a
raw_parser str t f = (Nt str, rules, sem)
    where   alt     = Alt str [Term t]
            rules   = M.insert str [alt] 
            sem _ arr ctx sppf l r m 
                | l + 1 == r && l < m && arr A.! l == t = S.singleton (f t)
                | otherwise = S.empty

-- | A parser that recognises a given character
char :: Char -> Parser Char
char c = raw_parser ([c]) (Char c) (\(Char c) -> c)

-- | A parser that recognises a given token
token :: Token -> Parser Token
token t = raw_parser (show t) t id

-- | A parser that always succeeds (and returns unit)
epsilon :: Parser ()
epsilon = (Nt x, rules, sem)
    where   x       = "__eps"
            alt     = Alt x [Term Epsilon]
            rules   = M.insert x [alt]
            sem _ arr ctx sppf l r m  | l == r    = S.singleton ()
                                      | otherwise = S.empty

-- | A parser that always succeeds and returns a given value
satisfy :: (Ord a) => a -> Parser a
satisfy a = a <$ epsilon

-- helpers
sym_ :: Parser a -> Symbol
sym_ (f,_,_) = f

id_   :: Parser a -> Nt 
id_ (Nt x,_,_)   = x

rules_ :: Parser a -> Visit2
rules_ (_,f,_) = f

sem_   :: Parser a -> Visit3 a
sem_ (_,_,f)   = f

mkNt :: String -> Char -> Nt
mkNt x c = concat ["(",x,")",[c]]

inContext :: (Int, Int, Nt) -> ParseContext -> Bool
inContext (l,r,x) = maybe False inner . IM.lookup l 
    where inner = maybe False ((==) x) . IM.lookup r

toContext :: (Int, Int, Nt) -> ParseContext -> ParseContext
toContext (l,r,x) = IM.insertWith IM.union l (IM.singleton r x)

concatChoice :: (Ord a) => PCOptions -> S.Set a -> S.Set a -> S.Set a
concatChoice opts ls rs = if left_biased_choice opts
                            then firstRes
                            else ls `S.union` rs
 where  firstRes | S.null ls  = rs
                 | otherwise  = ls

-- higher level patterns

-- | Optionally use the given parser
optional :: (Ord a) => Parser a -> Parser (Maybe a)
optional p@(Nt x,_,_) = (mkNt x '?') <:=> satisfy Nothing <|> Just <$> p

-- | Apply the given parser many times, 0 or more times (Kleene closure)
many :: (Ord a) => Parser a -> Parser [a]
many p@(Nt x,_,_) = (mkNt x '^') <::=> satisfy [] 
                                   <|> uncurry (:) <$> p <*> many p

-- | Apply the given parser some times, 1 or more times (positive closure)
some :: (Ord a) => Parser a -> Parser [a]
some p@(Nt x,_,_) = let rec = (mkNt x '+') <::=> (:[]) <$> p
                                            <|> uncurry (:) <$> p <*> rec
                    in rec