module Data.Configger
    ( Config
    , load
    , from
    , merge
    , mergeSection
    , items
    , set
    , get
    , values ) where
import Control.Dangerous
import Control.Monad.Trans
import Data.Maybe
import Data.String.Utils
import Text.ParserCombinators.Parsec

type Config = [(String, [(String, String)])]

-- Merge two configurations
merge :: Config -> Config -> Config
merge a b = foldr mergeSection b a

-- Merge a section into a configuration
mergeSection :: (String, [(String, String)]) -> Config -> Config
mergeSection (a, as) ((b, bs):rest) | a == b = (a, as ++ bs) : rest
mergeSection a (b:bs) = b : mergeSection a bs
mergeSection a [] = [a]

-- Add a setting
set :: String -> String -> String -> Config -> Config
set s k v conf = mergeSection (s, [(k, v)]) conf

-- Get the first value of a setting
get :: String -> String -> Config -> Maybe String
get s k conf = lookup s conf >>= lookup k

-- Get all values of a setting
values :: String -> String -> Config -> [String]
values s k conf = let
    vars = fromMaybe [] (lookup s conf)
    ours = filter ((k ==) . fst) vars
    in map snd ours

-- Get the items in a section of a configuration
items :: String -> Config -> [(String, String)]
items name conf = fromMaybe [] (lookup name conf)

-- Load a configuration from a file
load :: (Errorable m, MonadIO m) => FilePath -> String -> m Config
load defaults filename = do
    text <- liftIO $ readFile filename
    dangerize $ parse (file defaults) filename text

-- Load a configuration from a string
-- (Won't be able to throw as nice error messages)
from :: (Errorable m) => String -> String -> m Config
from str defaults = dangerize $ parse (file defaults) "(unknown)" str


-- | Parsers

file :: String -> GenParser Char st Config
file defaults = do
    free <- sepEndBy setting (many1 eol)
    named <- sepEndBy section (many1 eol)
    return $ mergeSection (defaults, free) named

section :: GenParser Char st (String, [(String, String)])
section = do
    w >> char '[' >> w
    name <- word
    w >> char ']' >> many1 eol >> return ()
    vars <- sepEndBy setting eol
    return (name, vars)

setting :: GenParser Char st (String, String)
setting = do
    var <- w >> word
    w >> oneOf ":=" >> w
    val <- w >> word
    return (var, val)

word :: GenParser Char st String
word = do
    str <- many1 (noneOf ":=[]\n")
    return $ strip str

w :: GenParser Char st ()
w = many (oneOf " \t") >> return ()

eol :: GenParser Char st ()
eol = w >> char '\n' >> return ()