{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
module Text.Haiji.Parse
       ( Jinja2(..)
       , parseString
       , parseFile
       ) where

#if MIN_VERSION_base(4,8,0)
#else
import Control.Applicative
#endif
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import Data.Attoparsec.Text
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.IO as LT
import Language.Haskell.TH.Syntax hiding (lift)

import Text.Haiji.Syntax

data Jinja2 =
  Jinja2
  { jinja2Base :: [AST 'Fully]
  , jinja2Child :: [AST 'Fully]
  } deriving (Eq, Show)

toJinja2 :: [AST 'Fully] -> Jinja2
toJinja2 (Base base : asts) = tmpl { jinja2Child = jinja2Child tmpl ++ asts } where
  tmpl = toJinja2 base
toJinja2 asts = Jinja2 { jinja2Base = asts, jinja2Child = [] }

parseString :: QuasiWithFile q => String -> q Jinja2
parseString = (toJinja2 <$>) . either error readAllFile . parseOnly parser . T.pack

class Quasi q => QuasiWithFile q where
  withFile :: FilePath -> q LT.Text

instance QuasiWithFile IO where
  withFile file = LT.readFile file

instance QuasiWithFile Q where
  withFile file = runQ (addDependentFile file >> runIO (withFile file))

parseFileWith :: QuasiWithFile q => (LT.Text -> LT.Text) -> FilePath -> q Jinja2
parseFileWith f file = withFile file >>= parseString . LT.unpack . f

readAllFile :: QuasiWithFile q => [AST 'Partially] -> q [AST 'Fully]
readAllFile asts = concat <$> mapM parseFileRecursively asts

parseFileRecursively :: QuasiWithFile q => AST 'Partially -> q [AST 'Fully]
parseFileRecursively (Literal l) = return [ Literal l ]
parseFileRecursively (Eval v) = return [ Eval v ]
parseFileRecursively (Condition p ts fs) =
  ((:[]) .) . Condition p
  <$> readAllFile ts
  <*> runMaybeT (maybe mzero return fs >>= lift . readAllFile)
parseFileRecursively (Foreach k xs loopBody elseBody) =
  ((:[]) .) . Foreach k xs
  <$> readAllFile loopBody
  <*> runMaybeT (maybe mzero return elseBody >>= lift . readAllFile)
parseFileRecursively (Include includeFile) = jinja2Base <$> parseIncludeFile includeFile
parseFileRecursively (Raw content) = return [ Raw content ]
parseFileRecursively (Extends extendsfile) = (:[]) . Base . jinja2Base <$> parseFile extendsfile
parseFileRecursively (Block base name scoped body) =
  (:[]) . Block base name scoped <$> readAllFile body
parseFileRecursively Super = return [ Super ]
parseFileRecursively (Comment c) = return [ Comment c ]
parseFileRecursively (Set lhs rhs scopes) = (:[]) . Set lhs rhs <$> readAllFile scopes

parseFile :: QuasiWithFile q => FilePath -> q Jinja2
parseFile = parseFileWith deleteLastOneLF where
  deleteLastOneLF :: LT.Text -> LT.Text
  deleteLastOneLF xs
    | "\n" `LT.isSuffixOf` xs       = LT.init xs
    | otherwise                     = xs

parseIncludeFile :: QuasiWithFile q => FilePath -> q Jinja2
parseIncludeFile = parseFileWith deleteLastOneLF where
  deleteLastOneLF xs
    | LT.null xs         = xs
    | LT.last xs == '\n' = LT.init xs
    | otherwise          = xs