{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}

module TM.Config where

import TM.Util (replaceAt, readOrCreateJSONFile)
import Data.List (find, findIndex)
import Data.Maybe (fromMaybe)
import Data.Aeson (FromJSON, ToJSON, decode)
import GHC.Generics (Generic)

data OptionValue = StringValue String | BoolValue Bool
  deriving Generic

instance FromJSON OptionValue
instance ToJSON OptionValue

data Option = Option { cname :: String 
                     , value :: OptionValue
                     , args  :: [String] }
                     deriving Generic

instance FromJSON Option
instance ToJSON   Option

type Config = [Option]

configDefaults :: Config
configDefaults = [ Option "full"    (BoolValue False)     ["-f", "--full"]
                 , Option "group"   (BoolValue True)      ["-g", "--group"]
                 , Option "storage" (StringValue "~/.tm") ["-s", "--storage"]
                 , Option "editor"  (StringValue "vim")   ["-e", "--editor"]
                 , Option "tag"     (StringValue "main")  ["-t", "--tag"]
                 , Option "parent"  (StringValue "0")     ["-p", "--parent"] ]

instance Show Option where
  show opt = cname opt ++ ":" ++ show (value opt)

instance Show OptionValue where
  show (BoolValue True)  = "on"
  show (BoolValue False) = "false"
  show (StringValue s)   = s

configFileName :: String
configFileName = "~/.tmrc"

getOption :: String -> Config -> Option
getOption s config =
  fromMaybe (error $ "No such an option '" ++ s ++ "'")
    (find (\o -> s == cname o) config)

readFlag :: String -> Config -> Bool
readFlag s config = 
  case value (getOption s config) of
    BoolValue True -> True
    _ -> False

readValue :: String -> Config -> String
readValue s config =
  case value (getOption s config) of
    StringValue v -> v
    _ -> ""

mergeConfig :: Config -> Config -> Config
mergeConfig = foldr setOption

setValue :: String -> OptionValue -> Config -> Config
setValue s v config = setOption opt config
  where opt = (getOption s config) { value = v }

setOption :: Option -> Config -> Config
setOption opt config =
  case findIndex (\o -> cname opt == cname o) config of
    Nothing -> opt:config
    Just idx -> replaceAt idx opt config

lookupByName :: String -> Config -> Maybe Option
lookupByName s = find (\o -> cname o == s)

lookupByArg :: String -> Config -> Maybe Option
lookupByArg s = find (elem s . args)

isFlag :: String -> Config -> Bool
isFlag s config =
  case value (getOption s config) of
    BoolValue _ -> True
    StringValue _ -> False

isValue :: String -> Config -> Bool
isValue s config = not (isFlag s config)

configure :: Config -> IO Config
configure defaults = do
  (configFileContent, _) <- readOrCreateJSONFile configFileName defaults
  let (Just decoded) = decode configFileContent :: Maybe [Option]
  return $ mergeConfig defaults decoded

