module Network.Anticiv.Config where

import Control.Monad
import Data.Chatty.Atoms
import Data.Chatty.TST
import Data.List
import Text.Chatty.Parser
import Text.Chatty.Parser.Carrier
import Text.Chatty.Scanner
import Text.CTPL

data ValueRef = StrVal (Atom String) | IntVal (Atom Int) | StrList (Atom [String]) | Ctpl0 (Atom String) deriving Eq
data ValueTemp = StrValT String | IntValT Int | StrListT [String] | CtplT String | Ctpl0T String deriving (Eq,Show)
data Key = RefLeaf ValueRef | TempLeaf ValueTemp | Group Config | Module Config | Vocab Config

type Config = TST Key

instance Show ValueRef

parseConf :: ChParser m => m Config
parseConf = do
  keys <- many parseKey
  return $ foldr (uncurry tstInsert) EmptyTST keys

oneOf :: ChParser m => [Char] -> m Char
oneOf ks = do
  k <- request
  if k `elem` ks then return k else pabort

ident :: ChParser m => m String
ident = some $ oneOf (['A'..'Z']++['a'..'z']++"-")

parseKey :: ChParser m => m (String, Key)
parseKey = do
  let grabl = do
        k <- request
        if k=='\n' then pabort else return k
      comment = do
        many white
        matchs "#"
        many grabl
        match '\n'
  many comment
  parseLeaf ??? parseGroup ??? parseModule ??? parseVocab

parseLeaf :: ChParser m => m (String, Key)
parseLeaf = do
  many white
  nm <- ident
  many white
  match '='
  let parseInt = do
        many white
        ds <- some digit
        return $ IntValT $ foldl1 (\l r -> l*10+r) ds
      grabq = (do
        k <- request
        case k of
          '"' -> pabort
          '\\' -> do
            k <- request
            ks <- grabq
            return (k:ks)
          _ -> do
            ks <- grabq
            return (k:ks)) ?? return []
      parseStr = do
        many white
        match '"'
        ks <- grabq
        match '"'
        return $ StrValT ks
      parseStrList = do
        many white
        match '('
        many white
        strs <- many parseStr
        many white
        match ')'
        return $ StrListT $ map (\(StrValT s) -> s) strs
      parseCtpl = do
        many white
        matchs "CTPL"
        many white
        match '"'
        cs <- grabq
        match '"'
        return $ CtplT cs
      parseCtpl0 = do
        many white
        matchs "CTPL0"
        many white
        match '"'
        cs <- grabq
        match '"'
        return $ Ctpl0T cs
  v <- parseInt ?? parseStr ?? parseStrList ??? parseCtpl ??? parseCtpl0
  many white
  match ';'
  return (nm,TempLeaf v)

parseGroup :: ChParser m => m (String, Key)
parseGroup = do
  many white
  matchs "Group"
  many white
  nm <- ident
  many white
  match '{'
  sub <- parseConf
  many white
  match '}'
  return (nm,Group sub)

parseModule :: ChParser m => m (String, Key)
parseModule = do
  many white
  matchs "Module"
  many white
  nm <- ident
  many white
  match '{'
  sub <- parseConf
  many white
  match '}'
  return (nm,Module sub)

parseVocab :: ChParser m => m (String, Key)
parseVocab = do
  many white
  matchs "Vocab"
  many white
  nm <- ident
  many white
  match '{'
  sub <- parseConf
  many white
  match '}'
  return (nm,Vocab sub)

readConf :: (ChScanner m,ChAtoms m) => m (Maybe Config)
readConf = do
  inp <- mscanL
  case runCarrierT inp parseConf of
    [] -> return Nothing
    (c:_) -> tmap atomify c >>= return . Just

instance Functor TST where
  fmap f EmptyTST = EmptyTST
  fmap f (TST c Nothing l m r) = TST c Nothing (fmap f l) (fmap f m) (fmap f r)
  fmap f (TST c (Just h) l m r) = TST c (Just $ f h) (fmap f l) (fmap f m) (fmap f r)

tmap :: Monad m => (a -> m b) -> TST a -> m (TST b)
tmap _ EmptyTST = return EmptyTST
tmap f (TST c Nothing l m r) = do
  l' <- tmap f l
  m' <- tmap f m
  r' <- tmap f r
  return $ TST c Nothing l' m' r'
tmap f (TST c (Just h) l m r) = do
  l' <- tmap f l
  m' <- tmap f m
  r' <- tmap f r
  h' <- f h
  return $ TST c (Just h') l' m' r'

atomify :: ChAtoms m => Key -> m Key
atomify (RefLeaf r) = return $ RefLeaf r
atomify (Group c) = tmap atomify c >>= return . Group
atomify (Module c) = tmap atomify c >>= return . Module
atomify (Vocab c) = return $ Vocab c
atomify (TempLeaf (IntValT i)) = do
  a <- newAtom
  putAtom a i
  return $ RefLeaf $ IntVal a
atomify (TempLeaf (StrValT s)) = do
  a <- newAtom
  putAtom a s
  return $ RefLeaf $ StrVal a
atomify (TempLeaf (StrListT l)) = do
  a <- newAtom
  putAtom a l
  return $ RefLeaf $ StrList a
atomify (TempLeaf (CtplT s)) = do
  a <- newAtom
  case compileCTPL s of
    Succ s' -> putAtom a s'
    SyntaxFault -> error "Error compiling CTPL script: Syntax fault."
    NoSuchProc s -> error ("Error compiling CTPL script: No such proc: "++s)
  return $ RefLeaf $ Ctpl0 a
atomify (TempLeaf (Ctpl0T s)) = do
  a <- newAtom
  putAtom a s
  return $ RefLeaf $ Ctpl0 a

getKey :: String -> Config -> Maybe Key
getKey s cx = case p of
  [] -> Nothing
  (c:_) -> Just c
  where p = runCarrierT s $ do
          let leaf c = do
                nm <- ident
                case tstLookup nm c of
                  Just k@RefLeaf{} -> return k
                  Just k@TempLeaf{} -> return k
                  _ -> pabort
              modu c = do
                match '%'
                nm <- ident
                match '/'
                case tstLookup nm c of
                  Just (Module k) -> kget k
                  _ -> pabort
              group c = do
                nm <- ident
                match '/'
                case tstLookup nm c of
                  Just (Group k) -> kget k
                  _ -> pabort
              vocab c = do
                nm <- ident
                match ':'
                case tstLookup nm c of
                  Just (Vocab k) -> kget k
                  _ -> pabort
              kget c = leaf c ??? modu c ??? group c ??? vocab c
          kget cx

mgetKey :: String -> String -> Config -> Maybe Key
mgetKey m s c =
  case getKey ("%"++m++"/"++s) c of
    Just k -> Just k
    Nothing -> getKey s c

getFirstKey :: [String] -> Config -> Maybe Key
getFirstKey [] _ = Nothing
getFirstKey (k:ks) c
  | Just x <- getKey k c = Just x
  | otherwise = getFirstKey ks c

mgetFirstKey :: String -> [String] -> Config -> Maybe Key
mgetFirstKey m ks c = flip getFirstKey c $ do
  k <- ks
  ["%"++m++"/"++k, k]