{-# LANGUAGE GADTs #-}

module Data.AppSettingsInternal where

import Data.Maybe (fromMaybe)
import Data.List (isPrefixOf)
import Data.Char (isDigit)
import Text.Read (readMaybe)
import qualified Data.Map as M

import Data.Serialization

-- I can't put the comments on the constructors:
-- http://trac.haskell.org/haddock/ticket/43

-- | The type of a setting.
-- It contains the setting name
-- (key in the configuration file) and its default value.
--
-- It is advised to have a module in your project handling settings.
-- In this module, you'd have all the settings declared at the
-- toplevel, and exported.
-- The rest of the application can then do
--
-- @
-- getSetting \<setting\>
-- setSetting \<conf\> \<setting\> \<value\>
-- @
--
-- and so on.
--
-- 'Setting' declares a simple setting. A value for that setting will be stored
-- in the configuration file in a single line.
--
-- 'ListSetting' however declares a list setting.
-- While it is perfectly fine to store lists
-- using the usual Setting constructor, if you have a list
-- of more complex items, you will get very long lines and a
-- configuration file very difficult to edit or review by hand.
--
-- The ListSetting will store settings using one line per item
-- in the list:
--
-- > testList :: Setting [String]
-- > testList = ListSetting "testList" ["list1", "list2", "list3"]
--
-- Now the configuration file looks like that:
--
-- > testList_1="list1"
-- > testList_2="list2"
-- > testList_3="list3"
--
-- Also note that an empty ListSetting is stored like so:
--
-- > testList=
data Setting a where
	Setting :: String -> a -> Setting a

	ListSetting :: (Read a, Show a) => String -> [a] -> Setting [a]

isKeyForListSetting :: String -> String -> Bool
isKeyForListSetting settingKey key = (settingKey ++ "_") `isPrefixOf` key
	&& (all isDigit $ drop (length settingKey+1) key)


-- TODO maybe another getSetting that'll tell you
-- if the setting is invalid in the config file instead of silently
-- give you the default?

-- | Most of the time you can use the second function you get from 'readSettings',
-- wrapped in a 'GetSetting' newtype, however sometimes it's nicer to just pass
-- a single 'Conf' to other functions if you're going to read or
-- write to the configuration. The GetSetting lets you only read.
getSetting' :: (Read a) => Conf -> Setting a -> a
getSetting' conf (Setting key defaultV) = fromMaybe defaultV $ getSettingValueFromConf conf key
getSetting' conf (ListSetting key defaultV) = case getSettingValueFromConf conf $ key ++ "_1" of
	-- an empty list for the XX list key is written as XX= in the config file.
	Nothing -> case M.lookup key conf of
		Nothing -> defaultV
		_ -> []
	Just x -> x : decodeListSetting conf key 2

getSettingValueFromConf :: Read a => Conf -> String -> Maybe a
getSettingValueFromConf conf key = do
	asString <- M.lookup key conf
	readMaybe $ value asString

decodeListSetting  :: Read a => Conf -> String -> Int -> [a]
decodeListSetting conf key index = case getSettingValueFromConf conf fullKey of
		Nothing -> []
		Just v -> v:decodeListSetting conf key (index+1)
	where fullKey = key ++ "_" ++ show index