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

import Control.Monad (void, when)
import Text.Parsec (parse)
import Text.Parsec.Char (anyChar, char, 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 _ = 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 str = not (str == "")

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

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"