module Language.SexpGrammar.Base
  ( SexpGrammar (..)
  , AtomGrammar (..)
  , SeqGrammar (..)
  , PropGrammar (..)
  , runParse
  , runGen
  , SexpG
  , SexpG_
  , module Data.InvertibleGrammar
  ) where
#if !defined(__GLASGOW_HASKELL__) || __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Monad.State
import Data.Map (Map)
import qualified Data.Map as M
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid
#endif
import Data.Scientific
import Data.Text (Text)
import qualified Data.Text.Lazy as Lazy
import Data.InvertibleGrammar
import Data.InvertibleGrammar.Monad
import Language.Sexp.Pretty (prettySexp)
import Language.Sexp.Types
type SexpG a = forall t. Grammar SexpGrammar (Sexp :- t) (a :- t)
type SexpG_ = forall t. Grammar SexpGrammar (Sexp :- t) t
unexpectedStr :: (MonadContextError (Propagation Position) (GrammarError Position) m) => Text -> m a
unexpectedStr msg = grammarError $ unexpected msg
unexpectedSexp :: (MonadContextError (Propagation Position) (GrammarError Position) m) => Text -> Sexp -> m a
unexpectedSexp exp got =
  grammarError $ expected exp `mappend` unexpected (Lazy.toStrict $ prettySexp got)
unexpectedAtom :: (MonadContextError (Propagation Position) (GrammarError Position) m) => Atom -> Atom -> m a
unexpectedAtom expected atom = do
  unexpectedSexp (Lazy.toStrict $ prettySexp (Atom dummyPos expected)) (Atom dummyPos atom)
unexpectedAtomType :: (MonadContextError (Propagation Position) (GrammarError Position) m) => Text-> Atom -> m a
unexpectedAtomType expected atom = do
  unexpectedSexp ("atom of type " `mappend` expected) (Atom dummyPos atom)
data SexpGrammar a b where
  GPos  :: SexpGrammar (Sexp :- t) (Position :- Sexp :- t)
  GAtom :: Grammar AtomGrammar (Atom :- t) t' -> SexpGrammar (Sexp :- t) t'
  GList :: Grammar SeqGrammar t            t' -> SexpGrammar (Sexp :- t) t'
  GVect :: Grammar SeqGrammar t            t' -> SexpGrammar (Sexp :- t) t'
instance
  ( MonadPlus m
  , MonadContextError (Propagation Position) (GrammarError Position) m
  ) => InvertibleGrammar m SexpGrammar where
  forward GPos (s :- t) =
    return (getPos s :- s :- t)
  forward (GAtom g) (s :- t) =
    case s of
      Atom p a    -> dive $ locate p >> forward g (a :- t)
      other       -> locate (getPos other) >> unexpectedSexp "atom" other
  forward (GList g) (s :- t) = do
    case s of
      List p xs   -> dive $ locate p >> parseSequence xs g t
      other       -> locate (getPos other) >> unexpectedSexp "list" other
  forward (GVect g) (s :- t) = do
    case s of
      Vector p xs -> dive $ locate p >> parseSequence xs g t
      other       -> locate (getPos other) >> unexpectedSexp "vector" other
  backward GPos (_ :- s :- t) =
    return (s :- t)
  backward (GAtom g) t = do
    (a :- t') <- dive $ backward g t
    return (Atom dummyPos a :- t')
  backward (GList g) t = do
    (t', SeqCtx xs) <- runStateT (dive $ backward g t) (SeqCtx [])
    return (List dummyPos xs :- t')
  backward (GVect g) t = do
    (t', SeqCtx xs) <- runStateT (dive $ backward g t) (SeqCtx [])
    return (Vector dummyPos xs :- t')
data AtomGrammar a b where
  GSym     :: Text -> AtomGrammar (Atom :- t) t
  GKw      :: Kw   -> AtomGrammar (Atom :- t) t
  GBool    :: AtomGrammar (Atom :- t) (Bool :- t)
  GInt     :: AtomGrammar (Atom :- t) (Integer :- t)
  GReal    :: AtomGrammar (Atom :- t) (Scientific :- t)
  GString  :: AtomGrammar (Atom :- t) (Text :- t)
  GSymbol  :: AtomGrammar (Atom :- t) (Text :- t)
  GKeyword :: AtomGrammar (Atom :- t) (Kw :- t)
instance
  ( MonadPlus m
  , MonadContextError (Propagation Position) (GrammarError Position) m
  ) => InvertibleGrammar m AtomGrammar where
  forward (GSym sym') (atom :- t) =
    case atom of
      AtomSymbol sym | sym' == sym -> return t
      _ -> unexpectedAtom (AtomSymbol sym') atom
  forward (GKw kw') (atom :- t) =
    case atom of
      AtomKeyword kw | kw' == kw -> return t
      _ -> unexpectedAtom (AtomKeyword kw') atom
  forward GBool (atom :- t) =
    case atom of
      AtomBool a -> return $ a :- t
      _          -> unexpectedAtomType "bool" atom
  forward GInt (atom :- t) =
    case atom of
      AtomInt a -> return $ a :- t
      _         -> unexpectedAtomType "int"  atom
  forward GReal (atom :- t) =
    case atom of
      AtomReal a -> return $ a :- t
      _          -> unexpectedAtomType "real" atom
  forward GString (atom :- t) =
    case atom of
      AtomString a -> return $ a :- t
      _            -> unexpectedAtomType "string" atom
  forward GSymbol (atom :- t) =
    case atom of
      AtomSymbol a -> return $ a :- t
      _            -> unexpectedAtomType "symbol" atom
  forward GKeyword (atom :- t) =
    case atom of
      AtomKeyword a -> return $ a :- t
      _             -> unexpectedAtomType "keyword" atom
  backward (GSym sym) t      = return (AtomSymbol sym :- t)
  backward (GKw kw) t        = return (AtomKeyword kw :- t)
  backward GBool (a :- t)    = return (AtomBool a :- t)
  backward GInt (a :- t)     = return (AtomInt a :- t)
  backward GReal (a :- t)    = return (AtomReal a :- t)
  backward GString (a :- t)  = return (AtomString a :- t)
  backward GSymbol (a :- t)  = return (AtomSymbol a :- t)
  backward GKeyword (a :- t) = return (AtomKeyword a :- t)
parseSequence :: (MonadContextError (Propagation Position) (GrammarError Position) m, InvertibleGrammar (StateT SeqCtx m) g) => [Sexp] -> g a b -> a -> m b
parseSequence xs g t = do
  (a, SeqCtx rest) <- runStateT (forward g t) (SeqCtx xs)
  unless (null rest) $
    unexpectedStr $ "leftover elements: " `mappend`
      (Lazy.toStrict $ Lazy.unwords $ map prettySexp rest)
  return a
data SeqGrammar a b where
  GElem :: Grammar SexpGrammar (Sexp :- t) t'
        -> SeqGrammar t t'
  GRest :: Grammar SexpGrammar (Sexp :- t) (a :- t)
        -> SeqGrammar t ([a] :- t)
  GProps :: Grammar PropGrammar t t'
         -> SeqGrammar t t'
newtype SeqCtx = SeqCtx { getItems :: [Sexp] }
instance
  ( MonadPlus m
  , MonadState SeqCtx m
  , MonadContextError (Propagation Position) (GrammarError Position) m
  ) => InvertibleGrammar m SeqGrammar where
  forward (GElem g) t = do
    step
    xs <- gets getItems
    case xs of
      []    -> unexpectedStr "end of sequence"
      x:xs' -> do
        modify $ \s -> s { getItems = xs' }
        forward g (x :- t)
  forward (GRest g) t = do
    xs <- gets getItems
    modify $ \s -> s { getItems = [] }
    go xs t
    where
      go []     t = return $ [] :- t
      go (x:xs) t = do
        step
        y  :- t'  <- forward g (x :- t)
        ys :- t'' <- go xs t'
        return $ (y:ys) :- t''
  forward (GProps g) t = do
    xs <- gets getItems
    modify $ \s -> s { getItems = [] }
    props <- go xs M.empty
    (res, PropCtx ctx) <- runStateT (forward g t) (PropCtx props)
    when (not $ M.null ctx) $
      unexpectedStr $ "property-list keys: " `mappend`
        (Lazy.toStrict $ Lazy.unwords $
          map (prettySexp . Atom dummyPos . AtomKeyword) (M.keys ctx))
    return res
    where
      go [] props = return props
      go (Atom _ (AtomKeyword kwd):x:xs) props = step >> go xs (M.insert kwd x props)
      go other _ =
        unexpectedStr $ "malformed property-list: " `mappend`
          (Lazy.toStrict $ Lazy.unwords $ map prettySexp other)
  backward (GElem g) t = do
    step
    (x :- t') <- backward g t
    modify $ \s -> s { getItems = x : getItems s }
    return t'
  backward (GRest g) (ys :- t) = do
    xs :- t' <- go ys t
    put (SeqCtx xs)
    return t'
    where
      go []     t = return $ [] :- t
      go (y:ys) t = do
        step
        x  :- t'  <- backward g (y :- t)
        xs :- t'' <- go ys t'
        return $ (x : xs) :- t''
  backward (GProps g) t = do
    step
    (t', PropCtx props) <- runStateT (backward g t) (PropCtx M.empty)
    let plist = foldr (\(name, sexp) acc -> Atom dummyPos (AtomKeyword name) : sexp : acc) [] (M.toList props)
    put $ SeqCtx plist
    return t'
data PropGrammar a b where
  GProp    :: Kw
           -> Grammar SexpGrammar (Sexp :- t) (a :- t)
           -> PropGrammar t (a :- t)
  GOptProp :: Kw
           -> Grammar SexpGrammar (Sexp :- t) (a :- t)
           -> PropGrammar t (Maybe a :- t)
newtype PropCtx = PropCtx { getProps :: Map Kw Sexp }
instance
  ( MonadPlus m
  , MonadState PropCtx m
  , MonadContextError (Propagation Position) (GrammarError Position) m
  ) => InvertibleGrammar m PropGrammar where
  forward (GProp kwd g) t = do
    ps <- gets getProps
    case M.lookup kwd ps of
      Nothing -> unexpectedStr $
        mconcat [ "key "
                , Lazy.toStrict . prettySexp . Atom dummyPos . AtomKeyword $ kwd
                , " not found"
                ]
      Just x  -> do
        put (PropCtx $ M.delete kwd ps)
        forward g $ x :- t
  forward (GOptProp kwd g) t = do
    ps <- gets getProps
    case M.lookup kwd ps of
      Nothing ->
        return (Nothing :- t)
      Just x  -> do
        put (PropCtx $ M.delete kwd ps)
        (a :- t') <- forward g (x :- t)
        return (Just a :- t')
  backward (GProp kwd g) t = do
    x :- t' <- backward g t
    modify $ \ps -> ps { getProps = M.insert kwd x (getProps ps) }
    return t'
  backward (GOptProp _ _) (Nothing :- t) = do
    return t
  backward (GOptProp kwd g) (Just x :- t) = do
    x' :- t' <- backward g (x :- t)
    modify $ \ps -> ps { getProps = M.insert kwd x' (getProps ps) }
    return t'
runParse
  :: (Functor m, MonadPlus m, MonadContextError (Propagation Position) (GrammarError Position) m, InvertibleGrammar m g)
  => Grammar g (Sexp :- ()) (a :- ())
  -> Sexp
  -> m a
runParse gram input =
  (\(x :- _) -> x) <$> forward gram (input :- ())
runGen
  :: (Functor m, MonadPlus m, MonadContextError (Propagation Position) (GrammarError Position) m, InvertibleGrammar m g)
  => Grammar g (Sexp :- ()) (a :- ())
  -> a
  -> m Sexp
runGen gram input =
  (\(x :- _) -> x) <$> backward gram (input :- ())