module Data.Tini.Ops
  ( empty, get, set, remove, modify
  , toList, fromList, merge
  ) where
import Data.List (foldl')
import Data.Tini.IniValue
import Data.Tini.Types
import Data.Tini.Utils (trim)

-- | An INI with no sections or properties.
empty :: Ini
empty = Ini []

-- | Returns the value at the given key, if it exists and is valid at
--   the function's result type.
--   See 'IniValue' for more information regarding how Haskell values are
--   encoded in INI files.
get :: IniValue a => Ini -> Key -> Maybe a
get (Ini ini) (Key s k) = lookup s ini >>= lookup (KeyPart k) >>= readValue

-- | Sets the given key to the given value.
--   If the key already exists, it will be overwritten.
--
--   New sections are added at the end of the given INI, and new properties
--   are added at the end of their respective sections.
set :: IniValue a => Ini -> Key -> a -> Ini
set ini key val = modify (const (Just (showValue val))) key ini

-- | Removes the given key from the given INI.
remove :: Key -> Ini -> Ini
remove = modify (const Nothing)

upd :: Eq k => (Maybe v -> Maybe v) -> k -> [(k, v)] -> [(k, v)]
upd f k xs =
  case break ((== k) . fst) xs of
    (pre, (_, v):suf) -> pre ++ maybe [] (\v' -> [(k, v')]) (f (Just v)) ++ suf
    (_, [])           -> xs  ++ maybe [] (\v -> [(k, v)])   (f Nothing)

-- | Modify the value at the given key in the given INI.
--
--   If the key exists, the given function will receive @Just value@ as its
--   argument, otherwise it will receive @Nothing@.
--
--   If the given function returns @Just new_value@, the given key will be
--   created or overwritten with @new_value@. If it returns @Nothing@, the
--   key will be deleted.
modify :: (Maybe String -> Maybe String) -> Key -> Ini -> Ini
modify f (Key s k) = Ini . upd (Just . upd (fmap clean . f) (KeyPart k) . maybe [] id) s . unIni
  where clean = trim . filter (/= '\n')

-- | Convert the given INI to s list of @(key, value)@ pairs.
toList :: Ini -> [(Key, String)]
toList (Ini secs) = [(Key sec k, v) | (sec, ps) <- secs, (KeyPart k, v) <- ps]

-- | Create an INI from the given list of @(key, value)@ pairs.
fromList :: [(Key, String)] -> Ini
fromList = foldl' (uncurry . set) empty

-- | Merge the given INIs. Values from the second INI override values from
--   the first in cases where a key exists in both.
--   Comments from the second INI are discarded.
merge :: Ini -> Ini -> Ini
merge l = foldl' (uncurry . set) l . toList