{-# LANGUAGE OverloadedStrings #-}
module Text.TOML.Parser
  ( module Text.TOML.Value
  , document
  , keygroup
  , keyval
  , value
  , Token
  )where

import Control.Applicative

import qualified Data.ByteString.Char8 as B
import Data.Attoparsec.ByteString.Char8
import Data.Attoparsec.Combinator
import Data.Time.Format

import System.Locale

import Text.TOML.Value


type Token = Either [B.ByteString] (B.ByteString, TOMLV)

document :: Parser [Token]
document = smb *> many ekk <* endOfInput
  where 
    smb = skipMany blank
    ekk = (eitherP keygroup keyval) <* smb

keygroup :: Parser [B.ByteString]
keygroup = do
    skipMany blank
    between lbrace rbrace skey
  where skey = keyg `sepBy` period

keyval :: Parser (B.ByteString, TOMLV)
keyval = do
    k <- keyv
    v <- equal *> value
    return (k, v)

keyg = lexeme $ takeWhile1 $ notInClass " \t\n]."
keyv = lexeme $ takeWhile1 $ notInClass " \t\n="

value :: Parser TOMLV
value = (array <?> "array")
    <|> (bool  <?> "bool")
    <|> (str   <?> "string")
    <|> (date  <?> "date")
    <|> (num   <?> "number")
  where
    array = VArray <$> between lbrace rbrace (value `sepBy` comma)
    bool = VBool <$> (true *> return True <|> false *> return False)
    str = VString <$> between quote quote (many (notChar '"'))
    num = do
        n <- lexeme $ number
        case n of
            I n -> return $ VInteger n
            D d -> return $ VDouble d
    date = do
        dstr <- takeTill (=='Z') <* zee
        let mt = parseTime defaultTimeLocale (iso8601DateFormat (Just "%X")) (B.unpack dstr)
        case mt of
            Just t  -> return (VDate t)
            Nothing -> fail "parse date failed"

whatever p = p >> return ()
lexeme p = do { x <- p; many spc; return x }
spc   = char ' ' <|> char '\t'
comment = whatever $ char '#' *> takeTill (=='\n')
line p = p *> (lexeme endOfLine)
blank = line $ lexeme $ (try comment) <|> return ()

zee = lexeme $ string "Z"
quote = lexeme $ string "\""
lbrace = lexeme $ string "["
rbrace = lexeme $ string "]"
comma = lexeme $ string ","
period = lexeme $ string "."
equal = lexeme $ string "="
true = lexeme $ string "true"
false = lexeme $ string "false"

between a b p = do { a; e <- p; b; return e }