{-#LANGUAGE TemplateHaskell#-}
{-| This module provides syntax for concise definitions of config files from
    Data.ConfigFile through template haskell. For example, the following splice,

@    
$(genConf "Example" [$conf|
                  elbow/center -> ec :: Double
                  elbow/min    -> emi :: Double
                  elbow/max    -> ema :: Double
                |])
@
    resolves into declaration of the following datatype

@
data Example = Example {ec  :: Double
                       ,emi :: Double
                       ,ema :: Double}
@
    and a function
@
loadExample :: (MonadIO m) => FilePath -> m (Either CPError Example) , 
@
     which uses Data.ConfigFile to read a values for the above struct from
     a file. 

     Known bugs: Does not support comments in the quotes. 


-} 
module Data.ConfigFile.TH(genConf,conf) where


import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Quote

import Control.Applicative hiding (many,(<|>))
import Control.Monad (when,mzero)
import Data.Char(isUpper,isSpace)
import Text.Parsec.Char
import Text.ParserCombinators.Parsec hiding (between)
import qualified Text.Parsec.Token as P
import Text.Parsec.Language



tok = P.makeTokenParser haskellDef

pCd = (,,,) <$> (spaces *> P.identifier tok) 
            <*> (char '/' *> P.identifier tok <* spaces)    
            <*> (string "->" *> spaces *> ntpe <* spaces)
            <*> (string "::" *> spaces *> tpe <* spaces)
    where
     ntpe = P.identifier tok >>= \x -> if (not.isUpper.head $x) then (return x) else fail $ x ++" is not a valid field name"
     tpe = P.identifier tok >>= \x -> if (isUpper.head $x) then (return x) else fail $ "Identifier " ++ x ++ " does not name a type"

toList = ListE . map (TupE . map (LitE . StringL) . \(a,b,c,d) -> [a,b,c,d] )

            
-- |Quasiquoter for quickly writing config-file specifications

conf = QuasiQuoter undefined undefined undefined (sToExpQ)

sToExpQ str = let
                 hd':rst = filter (not.null) $  lines str
                 hd      = takeWhile (not . isSpace) . dropWhile isSpace $ hd'
              in genConf hd . map parseIt . filter (not.all isSpace)  $ rst

parseIt :: [Char] -> (String, String, String, String)
parseIt x = case runParser pCd  () "Quasiquote" x of
                     Right x -> x
                     Left  e -> error (show e++show x) 

-- | TH macro for generating a data type and corresponding code to load it from a config file.
genConf :: Monad m => String -> [(String, String, String, String)] -> m [Dec]
genConf name cs 
    = do
       return $ decls
    where
       decls = [DataD [] (mkName name) [] [RecC (mkName name) fields] []
               ,FunD (mkName $ "load"++name) 
                     [Clause [VarP (mkName "file")]
                             (NormalB (AppE (var "runErrorT") (DoE loads))) 
                             []]
               ]
       loads = [BindS (pat "cp") open] ++ map load1 cs ++
                [NoBindS $ AppE (var "return") (bind)]

       bind = RecConE (mkName name) [(mkName nam,var nam) | (_,_,nam,_) <- cs]
       open = AppE (var "join") 
                (AppE (var "liftIO")
                   (appM "readfile" [var "emptyCP",var "file"])) 

       load1 (section,vname,name,tpe)  = BindS (VarP $ mkName name) $ appM "get" 
                                                               [var "cp"
                                                               ,slit section
                                                               ,slit vname] 
       field1 (section,var,name,tpe) = (mkName name,NotStrict,ConT (mkName tpe))

       fields :: [Language.Haskell.TH.Syntax.VarStrictType]
       fields = map field1 cs
       slit = LitE . StringL
       pat = VarP . mkName 
var = VarE . mkName 
appM a xs = foldl AppE (var a) xs