{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
module Vgrep.Environment.Config where

import Control.Lens.Compat
import Control.Monad.IO.Class
import Data.Maybe
import Data.Monoid
import Graphics.Vty.Attributes
    ( Attr
    , blue
    , bold
    , defAttr
    , green
    , standout
    , withBackColor
    , withForeColor
    , withStyle
    )

import           Vgrep.Command
import           Vgrep.Environment.Config.Monoid
import           Vgrep.Environment.Config.Sources
import qualified Vgrep.Key                        as Key
import           Vgrep.KeybindingMap              (KeybindingMap (..))
import qualified Vgrep.KeybindingMap              as KeybindingMap


--------------------------------------------------------------------------
-- * Types
--------------------------------------------------------------------------

data Config = Config
    { _colors      :: Colors
    -- ^ Color configuration

    , _tabstop     :: Int
    -- ^ Tabstop width (default: 8)

    , _editor      :: String
    -- ^ Executable for @e@ key (default: environment variable @$EDITOR@,
    -- or @vi@ if @$EDITOR@ is not set)

    , _keybindings :: Keybindings

    } deriving (Eq, Show)

data Colors = Colors
    { _lineNumbers   :: Attr
    -- ^ Line numbers (default: blue)

    , _lineNumbersHl :: Attr
    -- ^ Highlighted line numbers (default: bold blue)

    , _normal        :: Attr
    -- ^ Normal text (default: terminal default)

    , _normalHl      :: Attr
    -- ^ Highlighted text (default: bold)

    , _fileHeaders   :: Attr
    -- ^ File names in results view (default: terminal default color on
    -- green background)

    , _selected      :: Attr
    -- ^ Selected entry (default: terminal default, inverted)

    } deriving (Eq, Show)

data Keybindings = Keybindings
    { _resultsKeybindings :: KeybindingMap
    -- ^ Keybindings in effect when results list is focused.

    , _pagerKeybindings   :: KeybindingMap
    -- ^ Keybindings in effect when pager is focused.

    , _globalKeybindings  :: KeybindingMap
    -- ^ Global keybindings are in effect both for pager and results list, but
    -- can be overridden by either one.

    } deriving (Eq, Show)


--------------------------------------------------------------------------
-- * Auto-generated Lenses
--------------------------------------------------------------------------

makeLenses ''Config
makeLenses ''Colors
makeLenses ''Keybindings


--------------------------------------------------------------------------
-- * Read Config from Monoid
--------------------------------------------------------------------------

-- | Convert a 'ConfigMonoid' to a 'Config'. Missing (@'mempty'@) values in the
-- 'ConfigMonoid' are supplied from the 'defaultConfig'.
fromConfigMonoid :: ConfigMonoid -> Config
fromConfigMonoid ConfigMonoid{..} = Config
    { _colors  = fromColorsMonoid _mcolors
    , _tabstop = fromFirst (_tabstop defaultConfig) _mtabstop
    , _editor  = fromFirst (_editor  defaultConfig) _meditor
    , _keybindings = fromKeybindingsMonoid _mkeybindings }

-- | Convert a 'ColorsMonoid' to a 'Colors' config.
fromColorsMonoid :: ColorsMonoid -> Colors
fromColorsMonoid ColorsMonoid{..} = Colors
    { _lineNumbers   = fromFirst (_lineNumbers   defaultColors) _mlineNumbers
    , _lineNumbersHl = fromFirst (_lineNumbersHl defaultColors) _mlineNumbersHl
    , _normal        = fromFirst (_normal        defaultColors) _mnormal
    , _normalHl      = fromFirst (_normalHl      defaultColors) _mnormalHl
    , _fileHeaders   = fromFirst (_fileHeaders   defaultColors) _mfileHeaders
    , _selected      = fromFirst (_selected      defaultColors) _mselected }

fromFirst :: a -> First a -> a
fromFirst a = fromMaybe a . getFirst

fromKeybindingsMonoid :: KeybindingsMonoid -> Keybindings
fromKeybindingsMonoid KeybindingsMonoid{..} = Keybindings
    { _resultsKeybindings = fromMaybe mempty _mresultsKeybindings <> _resultsKeybindings defaultKeybindings
    , _pagerKeybindings   = fromMaybe mempty _mpagerKeybindings   <> _pagerKeybindings   defaultKeybindings
    , _globalKeybindings  = fromMaybe mempty _mglobalKeybindings  <> _globalKeybindings  defaultKeybindings }


--------------------------------------------------------------------------
-- * Default Config
--------------------------------------------------------------------------

defaultConfig :: Config
defaultConfig = Config
    { _colors = defaultColors
    , _tabstop = 8
    , _editor = "vi"
    , _keybindings = defaultKeybindings }

defaultColors :: Colors
defaultColors = Colors
    { _lineNumbers   = defAttr `withForeColor` blue
    , _lineNumbersHl = defAttr `withForeColor` blue
                               `withStyle` bold
    , _normal        = defAttr
    , _normalHl      = defAttr `withStyle` bold
    , _fileHeaders   = defAttr `withBackColor` green
    , _selected      = defAttr `withStyle` standout }

defaultKeybindings :: Keybindings
defaultKeybindings = Keybindings
    { _resultsKeybindings = KeybindingMap.fromList
        [ (Key.key Key.Up,          ResultsUp)
        , (Key.key Key.Down,        ResultsDown)
        , (Key.key Key.PageUp,      ResultsPageUp)
        , (Key.key Key.PageDown,    ResultsPageDown)
        , (Key.key Key.Enter,       PagerGotoResult)
        , (Key.key (Key.Char 'k'),  ResultsUp)
        , (Key.key (Key.Char 'j'),  ResultsDown)
        , (Key.key (Key.Char 'b') `Key.withModifier` Key.Ctrl, ResultsPageUp)
        , (Key.key (Key.Char 'f') `Key.withModifier` Key.Ctrl, ResultsPageDown)
        , (Key.key (Key.Char 'f'),  DisplayResultsOnly)
        , (Key.key Key.Tab,         SplitFocusPager) ]
    , _pagerKeybindings = KeybindingMap.fromList
        [ (Key.key Key.Up,          PagerUp)
        , (Key.key Key.Down,        PagerDown)
        , (Key.key Key.PageUp,      PagerPageUp)
        , (Key.key Key.PageDown,    PagerPageDown)
        , (Key.key Key.Left,        PagerScrollLeft)
        , (Key.key Key.Right,       PagerScrollRight)
        , (Key.key (Key.Char 'k'),  PagerUp)
        , (Key.key (Key.Char 'j'),  PagerDown)
        , (Key.key (Key.Char 'h'),  PagerScrollLeft)
        , (Key.key (Key.Char 'l'),  PagerScrollRight)
        , (Key.key (Key.Char 'u') `Key.withModifier` Key.Ctrl, PagerHalfPageUp)
        , (Key.key (Key.Char 'd') `Key.withModifier` Key.Ctrl, PagerHalfPageDown)
        , (Key.key (Key.Char 'b') `Key.withModifier` Key.Ctrl, PagerPageUp)
        , (Key.key (Key.Char 'f') `Key.withModifier` Key.Ctrl, PagerPageDown)
        , (Key.key (Key.Char 'f'),  DisplayPagerOnly)
        , (Key.key Key.Tab,         SplitFocusResults)
        , (Key.key (Key.Char 'q'),  DisplayResultsOnly) ]
    , _globalKeybindings = KeybindingMap.fromList
        [ (Key.key (Key.Char 'e'),  OpenFileInEditor)
        , (Key.key (Key.Char 'q'),  Exit) ]
    }


--------------------------------------------------------------------------
-- * Config Loader
--------------------------------------------------------------------------

-- | Gathers 'ConfigMonoid's from various sources and builds a 'Config'
-- based on the 'defaultConfig':
--
-- * Config from environment variables
-- * The configuration specified in the config file
-- * External config, e.g. from command line
--
-- where the latter ones override the earlier ones.
loadConfig
    :: MonadIO io
    => ConfigMonoid -- ^ External config from command line
    -> io Config
loadConfig configFromArgs = do
    configs <- sequence
        [ pure configFromArgs
        , configFromFile
        , editorConfigFromEnv ]
    pure (fromConfigMonoid (mconcat configs))