{-|
Module      : Git.Fmt.Config
Description : Configuration data structures.

Copyright   : (c) Henry J. Wylde, 2015
License     : BSD3
Maintainer  : public@hjwylde.com

Configuration data structures.
-}

{-# LANGUAGE OverloadedStrings #-}

module Git.Fmt.Config (
    -- * Config
    Config(..),
    emptyConfig, programFor, unsafeProgramFor, supported,

    -- * Program
    Program(..),
    emptyProgram, substitute, usesInputVariable, usesOutputVariable, inputVariableName,
    outputVariableName,

    -- * Helper functions
    defaultFileName,
) where

import Data.Aeson.Types
import Data.HashMap.Lazy (toList)
import Data.List.Extra   (find)
import Data.Maybe        (fromJust, isJust)
import Data.Text         (Text, isInfixOf, replace)

-- | A list of programs.
data Config = Config {
    programs :: [Program]
    }
    deriving (Eq, Show)

instance FromJSON Config where
    parseJSON (Object obj)  = Config <$> mapM (\(key, value) ->
            parseJSON value >>= \program -> return program { name = key }
            ) (toList obj)
    parseJSON value         = typeMismatch "Config" value

-- | The empty config (no programs).
emptyConfig :: Config
emptyConfig = Config []

-- | Attempts to find a program for the given extension.
programFor :: Config -> Text -> Maybe Program
programFor config ext = find (\program -> ext `elem` extensions program) (programs config)

-- | Finds a program for the given extension or errors.
unsafeProgramFor :: Config -> Text -> Program
unsafeProgramFor config = fromJust . programFor config

-- | Checks if the given extension is supported (e.g., there is a program for it).
supported :: Config -> Text -> Bool
supported config = isJust . programFor config

-- | A program has a semantic name, associated extensions and command.
--   The command string may contain variables to be replaced by surrounding them with '{{..}}'.
data Program = Program {
    name       :: Text,
    extensions :: [Text],
    command    :: Text
    }
    deriving (Eq, Show)

instance FromJSON Program where
    parseJSON (Object obj)  = Program "" <$> obj .: "extensions" <*> obj .: "command"
    parseJSON value         = typeMismatch "Program" value

-- | The empty program (the command fails).
emptyProgram :: Program
emptyProgram = Program "" [] "false"

-- | Substitutes the mapping throughout the command.
substitute :: Text -> [(Text, Text)] -> Text
substitute = foldr (uncurry replace)

-- | Checks whether the text uses the input variable.
usesInputVariable :: Text -> Bool
usesInputVariable = isInfixOf inputVariableName

-- | Checks whether the text uses the output variable.
usesOutputVariable :: Text -> Bool
usesOutputVariable = isInfixOf outputVariableName

-- | The input variable name.
inputVariableName :: Text
inputVariableName = "{{input}}"

-- | The output variable name.
outputVariableName :: Text
outputVariableName = "{{output}}"

-- | The file name of the default config.
defaultFileName :: FilePath
defaultFileName = ".omnifmt.yaml"