module GitHUD.Config.Parse (
  parseConfigFile
  , commentParser
  , itemParser
  , fallThroughItemParser
  , configItemsFolder
  , ConfigItem(..)
  , colorConfigToColor
  , intensityConfigToIntensity
  , stringConfigToStringList
  , redirectionParser
  , strConfigToRedirection
  , boolConfigToBool
  , intConfigToInt
  ) where

import Control.Monad (void, when)
import System.Posix.Daemon (Redirection(ToFile, DevNull))
import Text.Parsec (parse)
import Text.Parsec.Char (anyChar, char, digit, newline, noneOf, letter, spaces, string)
import Text.Parsec.Combinator (choice, eof, many1, manyTill, optional, sepBy)
import Text.Parsec.Prim (many, try, unexpected, (<|>), (<?>))
import Text.Parsec.String (parseFromFile, Parser)

import GitHUD.Config.Types
import GitHUD.Terminal.Types

data ConfigItem = Item String String
                | Comment
                | ErrorLine deriving (Eq, Show)

parseConfigFile :: FilePath -> IO Config
parseConfigFile filePath = do
  eitherParsed <- parseFromFile configFileParser filePath
  return $ either
    (const defaultConfig)
    id
    eitherParsed

configFileParser :: Parser Config
configFileParser = do
  items <- many configItemParser
  return $ foldl configItemsFolder defaultConfig items

configItemParser :: Parser ConfigItem
configItemParser = choice [
  commentParser
  , itemParser
  , fallThroughItemParser
  ] <?> "config file line"

endItem :: Parser ()
endItem = choice [
  void newline
  , eof
  ] <?> "end of item"

commentParser :: Parser ConfigItem
commentParser = try $ do
  char '#'
  manyTill anyChar (try endItem)
  return Comment

itemParser :: Parser ConfigItem
itemParser = try $ do
  key <- manyTill validKeyChar (char '=')
  when (key == "") $ unexpected "A key in the config file should not be empty"
  value <- manyTill anyChar (try endItem)
  return $ Item key value

validKeyChar :: Parser Char
validKeyChar = letter <|> (char '_')

-- | Must not be able to process an empty string
-- This is mandated by the use of 'many' in configFileParser
-- Therefore the definition `manyTill anyChar eof` is invalid, thus using newline
fallThroughItemParser :: Parser ConfigItem
fallThroughItemParser = do
  manyTill anyChar (try newline)
  return ErrorLine

configItemsFolder :: Config -> ConfigItem -> Config
configItemsFolder conf (Item "show_part_repo_indicator" value) =
  conf { confShowPartRepoIndicator = boolConfigToIntensity value }
configItemsFolder conf (Item "show_part_merge_branch_commits_diff" value) =
  conf { confShowPartMergeBranchCommitsDiff = boolConfigToIntensity value }
configItemsFolder conf (Item "show_part_local_branch" value) =
  conf { confShowPartLocalBranch = boolConfigToIntensity value }
configItemsFolder conf (Item "show_part_commits_to_origin" value) =
  conf { confShowPartCommitsToOrigin = boolConfigToIntensity value }
configItemsFolder conf (Item "show_part_local_changes_state" value) =
  conf { confShowPartLocalChangesState = boolConfigToIntensity value }
configItemsFolder conf (Item "show_part_stashes" value) =
  conf { confShowPartStashes = boolConfigToIntensity value }

configItemsFolder conf (Item "git_repo_indicator" repoIndicator) = conf { confRepoIndicator = repoIndicator }

configItemsFolder conf (Item "no_tracked_upstream_text" value) =
  conf { confNoTrackedUpstreamString = value }
configItemsFolder conf (Item "no_tracked_upstream_text_color" value) =
  conf { confNoTrackedUpstreamStringColor = colorConfigToColor value }
configItemsFolder conf (Item "no_tracked_upstream_text_intensity" value) =
  conf { confNoTrackedUpstreamStringIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "no_tracked_upstream_indicator" value) =
  conf { confNoTrackedUpstreamIndicator = value }
configItemsFolder conf (Item "no_tracked_upstream_indicator_color" value) =
  conf { confNoTrackedUpstreamIndicatorColor = colorConfigToColor value }
configItemsFolder conf (Item "no_tracked_upstream_indicator_intensity" value) =
  conf { confNoTrackedUpstreamIndicatorIntensity = intensityConfigToIntensity value }

configItemsFolder conf (Item "merge_branch_commits_indicator" value) =
  conf { confMergeBranchCommitsIndicator = value }
configItemsFolder conf (Item "merge_branch_commits_pull_prefix" value) =
  conf { confMergeBranchCommitsOnlyPull = value }
configItemsFolder conf (Item "merge_branch_commits_push_prefix" value) =
  conf { confMergeBranchCommitsOnlyPush = value }
configItemsFolder conf (Item "merge_branch_commits_push_pull_infix" value) =
  conf { confMergeBranchCommitsBothPullPush = value }
configItemsFolder conf (Item "merge_branch_ignore_branches" value) =
  conf { confMergeBranchIgnoreBranches = stringConfigToStringList value }

configItemsFolder conf (Item "local_branch_prefix" value) =
  conf { confLocalBranchNamePrefix = value }
configItemsFolder conf (Item "local_branch_suffix" value) =
  conf { confLocalBranchNameSuffix = value }
configItemsFolder conf (Item "local_branch_color" value) =
  conf { confLocalBranchColor = colorConfigToColor value }
configItemsFolder conf (Item "local_branch_intensity" value) =
  conf { confLocalBranchIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "local_detached_prefix" value) =
  conf { confLocalDetachedPrefix = value }
configItemsFolder conf (Item "local_detached_color" value) =
  conf { confLocalDetachedColor = colorConfigToColor value }
configItemsFolder conf (Item "local_detached_intensity" value) =
  conf { confLocalDetachedIntensity = intensityConfigToIntensity value }

configItemsFolder conf (Item "local_commits_push_suffix" value) =
  conf { confLocalCommitsPushSuffix = value }
configItemsFolder conf (Item "local_commits_push_suffix_color" value) =
  conf { confLocalCommitsPushSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "local_commits_push_suffix_intensity" value) =
  conf { confLocalCommitsPushSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "local_commits_pull_suffix" value) =
  conf { confLocalCommitsPullSuffix = value }
configItemsFolder conf (Item "local_commits_pull_suffix_color" value) =
  conf { confLocalCommitsPullSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "local_commits_pull_suffix_intensity" value) =
  conf { confLocalCommitsPullSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "local_commits_push_pull_infix" value) =
  conf { confLocalCommitsPushPullInfix = value }
configItemsFolder conf (Item "local_commits_push_pull_infix_color" value) =
  conf { confLocalCommitsPushPullInfixColor = colorConfigToColor value }
configItemsFolder conf (Item "local_commits_push_pull_infix_intensity" value) =
  conf { confLocalCommitsPushPullInfixIntensity = intensityConfigToIntensity value }

configItemsFolder conf (Item "change_index_add_suffix" value) =
  conf { confChangeIndexAddSuffix = value }
configItemsFolder conf (Item "change_index_add_suffix_color" value) =
  conf { confChangeIndexAddSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "change_index_add_suffix_intensity" value) =
  conf { confChangeIndexAddSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "change_index_mod_suffix" value) =
  conf { confChangeIndexModSuffix = value }
configItemsFolder conf (Item "change_index_mod_suffix_color" value) =
  conf { confChangeIndexModSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "change_index_mod_suffix_intensity" value) =
  conf { confChangeIndexModSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "change_index_del_suffix" value) =
  conf { confChangeIndexDelSuffix = value }
configItemsFolder conf (Item "change_index_del_suffix_color" value) =
  conf { confChangeIndexDelSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "change_index_del_suffix_intensity" value) =
  conf { confChangeIndexDelSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "change_local_add_suffix" value) =
  conf { confChangeLocalAddSuffix = value }
configItemsFolder conf (Item "change_local_add_suffix_color" value) =
  conf { confChangeLocalAddSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "change_local_add_suffix_intensity" value) =
  conf { confChangeLocalAddSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "change_local_mod_suffix" value) =
  conf { confChangeLocalModSuffix = value }
configItemsFolder conf (Item "change_local_mod_suffix_color" value) =
  conf { confChangeLocalModSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "change_local_mod_suffix_intensity" value) =
  conf { confChangeLocalModSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "change_local_del_suffix" value) =
  conf { confChangeLocalDelSuffix = value }
configItemsFolder conf (Item "change_local_del_suffix_color" value) =
  conf { confChangeLocalDelSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "change_local_del_suffix_intensity" value) =
  conf { confChangeLocalDelSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "change_renamed_suffix" value) =
  conf { confChangeRenamedSuffix = value }
configItemsFolder conf (Item "change_renamed_suffix_color" value) =
  conf { confChangeRenamedSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "change_renamed_suffix_intensity" value) =
  conf { confChangeRenamedSuffixIntensity = intensityConfigToIntensity value }
configItemsFolder conf (Item "change_conflicted_suffix" value) =
  conf { confChangeConflictedSuffix = value }
configItemsFolder conf (Item "change_conflicted_suffix_color" value) =
  conf { confChangeConflictedSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "change_conflicted_suffix_intensity" value) =
  conf { confChangeConflictedSuffixIntensity = intensityConfigToIntensity value }

configItemsFolder conf (Item "stash_suffix" value) =
  conf { confStashSuffix = value }
configItemsFolder conf (Item "stash_suffix_color" value) =
  conf { confStashSuffixColor = colorConfigToColor value }
configItemsFolder conf (Item "stash_suffix_intensity" value) =
  conf { confStashSuffixIntensity = intensityConfigToIntensity value }

configItemsFolder conf (Item "run_fetcher_daemon" value) =
  conf { confRunFetcherDaemon = boolConfigToBool value }
configItemsFolder conf (Item "githudd_sleep_seconds" value) =
  conf { confGithuddSleepSeconds = intConfigToInt value }
configItemsFolder conf (Item "githudd_pid_file_path" value) =
  conf { confGithuddPidFilePath = value }
configItemsFolder conf (Item "githudd_socket_file_path" value) =
  conf { confGithuddSocketFilePath = value }
configItemsFolder conf (Item "githudd_log_file_path" value) =
  conf { confGithuddLogFilePath = strConfigToRedirection value }

configItemsFolder conf _ = conf

colorConfigToColor :: String -> Color
colorConfigToColor str =
  either
    (const NoColor)
    id
    (parse colorParser "" str)

colorParser :: Parser Color
colorParser = choice [
    string "Black"   >> return Black
  , string "Red"     >> return Red
  , string "Green"   >> return Green
  , string "Yellow"  >> return Yellow
  , string "Blue"    >> return Blue
  , string "Magenta" >> return Magenta
  , string "Cyan"    >> return Cyan
  , string "White"   >> return White
  , string "NoColor" >> return NoColor
  ] <?> "color"

intensityConfigToIntensity :: String -> ColorIntensity
intensityConfigToIntensity str =
  either
    (const Vivid)
    id
    (parse intensityParser "" str)

intensityParser :: Parser ColorIntensity
intensityParser = choice [
    string "Dull" >> return Dull
  , string "Vivid" >> return Vivid
  ] <?> "intensity"

boolConfigToIntensity :: String -> Bool
boolConfigToIntensity str =
  either
    (const True)
    id
    (parse boolParser "" str)

stringConfigToStringList :: String -> [String]
stringConfigToStringList str =
  either
    (const [])
    id
    (parse stringListParser "" str)

stringListParser :: Parser [String]
stringListParser = do
  branchNameList <- sepBy stripedBranchName (char ',')
  return $ filter noEmptyStringFilter branchNameList

noEmptyStringFilter :: String -> Bool
noEmptyStringFilter = (/=) ""

stripedBranchName :: Parser String
stripedBranchName = do
  spaces
  branchName <- many (noneOf [',', ' '])
  spaces
  return branchName

intParser :: Parser Int
intParser = read <$> many1 digit

intConfigToInt :: String -> Int
intConfigToInt str =
  either
    (const 30)
    id
    (parse intParser "" str)

boolParser :: Parser Bool
boolParser = choice [
    string "False" >> return False
  , string "F" >> return False
  , string "false" >> return False
  , string "f" >> return False
  , string "No" >> return False
  , string "N" >> return False
  , string "no" >> return False
  , string "n" >> return False
  , string "True" >> return True
  , string "T" >> return True
  , string "true" >> return True
  , string "t" >> return True
  , string "Yes" >> return True
  , string "Y" >> return True
  , string "yes" >> return True
  , string "y" >> return True
  ] <?> "bool"

boolConfigToBool :: String -> Bool
boolConfigToBool str =
  either
    (const False)
    id
    (parse boolParser "" str)

strConfigToRedirection :: String -> Redirection
strConfigToRedirection str =
  either
    (const DevNull)
    id
    (parse redirectionParser "" str)

redirectionParser :: Parser Redirection
redirectionParser =
  choice [
    try (string "/dev/null") >> return DevNull
  , ToFile <$> many1 anyChar
  ] <?> "Redirection"