-- |
-- Module      : Data.Git.Config
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unix
--
-- config related types and methods.
--
{-# LANGUAGE OverloadedStrings #-}
module Data.Git.Config
    ( Config(..)
    , Section(..)
    -- * reading methods
    , readConfig
    , readGlobalConfig
    -- * methods
    , listSections
    , get
    ) where

import Control.Applicative
import Control.Monad (mplus)
import Data.Git.Path
import Data.List (find)
import Filesystem.Path.CurrentOS
import Filesystem (getHomeDirectory)
import qualified Data.Set as S

newtype Config = Config [Section]
    deriving (Show,Eq)

data Section = Section
    { sectionName :: String
    , sectionKVs  :: [(String, String)]
    } deriving (Show,Eq)

parseConfig :: String -> Config
parseConfig = Config . reverse . toSections . foldl accSections ([], Nothing) . lines
  where toSections (l,Nothing) = l
        toSections (l,Just s)  = s : l

        -- a new section in the config file
        accSections (sections, mcurrent) ('[':sectNameE)
            | last sectNameE == ']' =
                let sectName = take (length sectNameE - 1) sectNameE
                 in case mcurrent of
                    Nothing      -> (sections, Just $ Section sectName [])
                    Just current -> (sectionFinalize current : sections, Just $ Section sectName [])
            | otherwise             =
                (sections, mcurrent)
        -- a normal line without having any section defined yet
        accSections acc@(_, Nothing) _ = acc
        -- potentially a k-v line in an existing section
        accSections (sections, Just current) kvLine =
            case break (== '=') kvLine of
                (k,'=':v) -> (sections, Just $ sectionAppend current (strip k, strip v))
                (_,_)     -> (sections, Just current) -- not a k = v line
        -- append a key-value
        sectionAppend (Section n l) kv = Section n (kv:l)
        sectionFinalize (Section n l) = Section n $ reverse l

        strip s = dropSpaces $ reverse $ dropSpaces $ reverse s
          where dropSpaces = dropWhile (\c -> c == ' ' || c == '\t')

readConfigPath filepath = parseConfig <$> readFile (encodeString filepath)
readConfig gitRepo = readConfigPath (configPath gitRepo)

readGlobalConfig = getHomeDirectory >>= readConfigPath . (\homeDir -> homeDir </> ".gitconfig")

listSections :: [Config] -> [String]
listSections = S.toList . foldr accSections S.empty
  where accSections (Config sections) set = foldr S.insert set (map sectionName sections)

-- | Get a configuration element in a stack of config file, starting from the top.
get :: [Config] -- ^ stack of config
    -> String   -- ^ section name
    -> String   -- ^ key name
    -> Maybe String
get []            _       _   = Nothing
get (Config c:cs) section key = findOne `mplus` get cs section key
  where findOne = find (\s -> sectionName s == section) c >>= lookup key . sectionKVs
