{-# LANGUAGE CPP             #-}
{-# LANGUAGE RecordWildCards #-}

module Template.HSML.Internal.Parser
#ifdef TESTING
where
#else
(  hsmlParser
, shsmlParser

,  hsmlTemplate
, shsmlTemplate
) where
#endif

------------------------------------------------------------------------------
import qualified Language.Haskell.Exts.Parser    as HE
import qualified Language.Haskell.Exts.Extension as HE
------------------------------------------------------------------------------
import           Control.Applicative
import           Control.Monad
import           Control.Arrow
------------------------------------------------------------------------------
import           Data.Monoid ((<>))
------------------------------------------------------------------------------
import qualified Text.Parsec.String as P
import qualified Text.Parsec.Prim   as P
------------------------------------------------------------------------------
import qualified Template.HSML.Internal.Types         as I
import qualified Template.HSML.Internal.Types.Syntax  as IS
import qualified Template.HSML.Internal.Parser.Syntax as IS
------------------------------------------------------------------------------

hsmlTemplate :: String -> Either String I.Template
hsmlTemplate str = 
    case P.parse hsmlParser ".HSML" str of
        Right tpl -> Right tpl
        Left  err -> Left $ show err

hsmlParser :: P.Parser I.Template
hsmlParser = IS.hsmlSyntax >>= transform

shsmlTemplate :: String -> Either String I.Template
shsmlTemplate str =
    case P.parse shsmlParser ".HSML" str of
        Right tpl -> Right tpl
        Left  err -> Left $ show err 

shsmlParser :: P.Parser I.Template
shsmlParser = IS.shsmlSyntax >>= transform

------------------------------------------------------------------------------

transform :: IS.Syntax -> P.Parser I.Template
transform IS.Syntax{..} = do
    args <- mapM transformArg syntaxArgs
    (decs, sections) <- transformChunks syntaxChunks
    return I.Template
      { I.templateArgs = args
      , I.templateDecs = decs
      , I.templateSections = sections
      }

transformArg :: I.RArg -> P.Parser I.Arg
transformArg (I.RArg name mtype) =
    case mtype of
        Just stype ->
          case parseType stype of
              Right t -> return . I.Arg name $ Just t
              Left  r -> fail $ "Could not parse type \"" <> stype <> "\", reason: " <> r
        Nothing -> return $ I.Arg name Nothing

transformExp :: I.RExp -> P.Parser I.Exp
transformExp sexp =
    case parseExp sexp of
        Right e -> return e 
        Left  r -> fail $ "Could not parse expression \"" <> sexp <> "\", reason: " <> r

transformChunks :: [IS.Chunk] -> P.Parser ([I.Dec], [I.Section])
transformChunks chunks = (reverse *** reverse) <$> foldM transformChunk ([], []) chunks

transformChunk :: ([I.Dec], [I.Section]) -> IS.Chunk -> P.Parser ([I.Dec], [I.Section])
transformChunk (ds, ss) (IS.ElementNode name rattributes chunks) = do
    attributes <- mapM transformAttribute rattributes
    (decs, sections) <- transformChunks chunks
    return (ds, I.ElementNode name attributes sections decs : ss)
transformChunk (ds, ss) (IS.ElementLeaf name rattributes) = do
    attributes <- mapM transformAttribute rattributes
    return (ds, I.ElementLeaf name attributes : ss)
transformChunk (ds, ss) (IS.Text    text) = return (ds, I.Text text : ss)
transformChunk (ds, ss) (IS.TextRaw text) = return (ds, I.TextRaw text : ss)
transformChunk (ds, ss) (IS.Haskell rhs) = 
    case parseExp rhs of
        Right e -> return (ds, I.Expression e : ss)
        Left er -> 
          case parseDec rhs of
              Right d -> return (d : ds, ss)
              Left dr -> fail $ concat 
                [ "Could not parse haskell \"", rhs
                , "\", as expression, because: ", er
                , "; as declaration, because: ", dr
                ]

transformAttribute :: I.RAttribute -> P.Parser I.Attribute
transformAttribute (I.Attribute rname rvalue) =
    I.Attribute <$> transformName rname <*> transformValue rvalue 
    where
      transformName (I.AttributeNameExp rexp) = I.AttributeNameExp <$> transformExp rexp
      transformName (I.AttributeNameText text) = return $ I.AttributeNameText text

      transformValue (I.AttributeValueExp rexp) = I.AttributeValueExp <$> transformExp rexp
      transformValue (I.AttributeValueText text) = return $ I.AttributeValueText text

parseType :: String -> Either String I.Type
parseType = toEither . HE.parseTypeWithMode parseMode
{-# INLINE parseType #-}

parseExp :: String -> Either String I.Exp
parseExp = toEither . HE.parseExpWithMode parseMode
{-# INLINE parseExp #-}

parseDec :: String -> Either String I.Dec
parseDec = toEither . HE.parseDeclWithMode parseMode
{-# INLINE parseDec #-}

toEither :: HE.ParseResult a -> Either String a
toEither pr =
    case pr of
        HE.ParseOk     x   -> Right x
        HE.ParseFailed _ e -> Left e
{-# INLINE toEither #-}      

parseMode :: HE.ParseMode
parseMode = HE.defaultParseMode
  { HE.extensions = HE.TemplateHaskell : HE.QuasiQuotes : HE.haskell2010
  }
{-# INLINE parseMode #-}