module Data.KeywordArgs.Parse (configParser) where

import Data.Maybe (catMaybes)

import Text.Parsec ((<|>), many, try, manyTill, char, anyChar, many1)
import Text.Parsec.String (Parser)

import Text.ParserCombinators.Parsec.Prim (GenParser)
import Text.ParserCombinators.Parsec.Char (space, newline, oneOf, noneOf)

import Control.Monad (liftM2)

import Text.Parsec.Combinator (eof)

import Control.Applicative ((<*), (*>), (<$>))

-- | Returns a parser for input of a keyword, followed by a space,
-- followed by one or more arguments. Any comments (text preceeded
-- by a '#') will be ignored until the end of the line.
configParser :: Parser [(String, String)]
configParser = catMaybes <$> many line

line :: Parser (Maybe (String, String))
line = comment *> return Nothing <|> Just <$> configurationOption

configurationOption :: Parser (String, String)
configurationOption = do
  _ <- many space

  keyword <- manyTill1 anyChar keywordArgSeparator
  val     <- value

  return (keyword, val)

value :: Parser String
value =
  unquotedValue <|> quotedValue

  where
    endOfOption   = endOfLineOrInput <|> comment

    quotedValue   = quote
                    *> manyTill1 argumentChar quote
                    <* endOfOption

    unquotedValue = manyTill1 argumentChar endOfOption

comment :: Parser ()
comment =
  try (many space *> char '#')
  *> manyTill anyChar endOfLineOrInput
  *> return ()

endOfLineOrInput :: Parser ()
endOfLineOrInput = newline *> return () <|> eof

manyTill1 :: GenParser tok st a -> GenParser tok st end -> GenParser tok st [a]
manyTill1 p end = liftM2 (:) p (manyTill p end)

argumentChar :: Parser Char
argumentChar =  noneOf "#\""

keywordArgSeparator :: Parser ()
keywordArgSeparator = many1 (oneOf "\t ") *> return ()

quote :: Parser Char
quote = char '"'