{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-|
Module      : $header$
Copyright   : (c) Laurent P René de Cotret, 2020
License     : GNU GPL, version 2 or above
Maintainer  : laurent.decotret@outlook.com
Stability   : internal
Portability : portable

Reading configuration from file
-}

module Text.Pandoc.Filter.Plot.Configuration (
      configuration
    , configurationPathMeta
    , defaultConfiguration
) where

import           Data.Maybe             (fromMaybe)
import           Data.Text              (Text, pack, unpack)
import qualified Data.Text.IO           as TIO
import           Data.Yaml
import           Data.Yaml.Config       (ignoreEnv, loadYamlSettings)

import           System.FilePath        (normalise)

import           Text.Pandoc.Definition (Format(..), Pandoc(..), MetaValue(..), Inline(..), lookupMeta)

import Text.Pandoc.Filter.Plot.Monad

-- | Read configuration from a YAML file. The
-- keys are exactly the same as for code blocks.
--
-- If a key is not present, its value will be set
-- to the default value. Parsing errors result in thrown exceptions.
configuration :: FilePath -> IO Configuration
configuration fp = (loadYamlSettings [normalise fp] [] ignoreEnv) >>= renderConfig


-- | Default configuration values.
--
-- @since 0.5.0.0
defaultConfiguration :: Configuration
defaultConfiguration =
    Configuration
        { defaultDirectory    = "plots/"
        , defaultWithSource   = False
        , defaultDPI          = 80
        , defaultSaveFormat   = PNG
        , defaultDependencies = mempty
        , captionFormat       = Format "markdown+tex_math_dollars"

        , logVerbosity        = Warning
        , logSink             = StdErr

        , matplotlibPreamble  = mempty
        , plotlyPythonPreamble= mempty
        , plotlyRPreamble     = mempty
        , matlabPreamble      = mempty
        , mathematicaPreamble = mempty
        , octavePreamble      = mempty
        , ggplot2Preamble     = mempty
        , gnuplotPreamble     = mempty
        , graphvizPreamble    = mempty
        , bokehPreamble       = mempty
        , plotsjlPreamble     = mempty

        , matplotlibExe       = python
        , matlabExe           = "matlab"
        , plotlyPythonExe     = python
        , plotlyRExe          = "Rscript"
        , mathematicaExe      = "math"
        , octaveExe           = "octave"
        , ggplot2Exe          = "Rscript"
        , gnuplotExe          = "gnuplot"
        , graphvizExe         = "dot"
        , bokehExe            = python
        , plotsjlExe          = "julia"

        , matplotlibTightBBox   = False
        , matplotlibTransparent = False
        }
        where
            python = if isWindows then "python" else "python3"

-- | Extact path to configuration from the metadata in a Pandoc document.
-- The path to the configuration file should be under the @plot-configuration@ key.
-- In case there is no such metadata, return the default configuration.
--
-- For example, at the top of a markdown file:
--
-- @
--     ---
--     title: My document
--     author: John Doe
--     plot-configuration: /path/to/file.yml
--     ---     
-- @
--
-- The same can be specified via the command line using Pandoc's @-M@ flag:
--
-- > pandoc --filter pandoc-plot -M plot-configuration="path/to/file.yml" ...
--
-- @since 0.6.0.0
configurationPathMeta :: Pandoc -> Maybe FilePath
configurationPathMeta (Pandoc meta _) =
        lookupMeta "plot-configuration" meta >>= getPath
    where
        getPath (MetaString t)        = Just (unpack t)
        getPath (MetaInlines [Str s]) = Just (unpack s)
        getPath _                     = Nothing


-- We define a precursor type because preambles are best specified as file paths,
-- but we want to read those files before building a full
-- @Configuration@ value.
data ConfigPrecursor = ConfigPrecursor
    { _defaultDirectory    :: !FilePath
    , _defaultWithSource   :: !Bool
    , _defaultDPI          :: !Int
    , _defaultSaveFormat   :: !SaveFormat
    , _defaultDependencies :: ![FilePath]
    , _captionFormat       :: !Format

    , _logPrec             :: !LoggingPrecursor

    , _matplotlibPrec      :: !MatplotlibPrecursor
    , _matlabPrec          :: !MatlabPrecursor
    , _plotlyPythonPrec    :: !PlotlyPythonPrecursor
    , _plotlyRPrec         :: !PlotlyRPrecursor
    , _mathematicaPrec     :: !MathematicaPrecursor
    , _octavePrec          :: !OctavePrecursor
    , _ggplot2Prec         :: !GGPlot2Precursor
    , _gnuplotPrec         :: !GNUPlotPrecursor
    , _graphvizPrec        :: !GraphvizPrecursor
    , _bokehPrec           :: !BokehPrecursor
    , _plotsjlPrec         :: !PlotsjlPrecursor
    }

defaultConfigPrecursor :: ConfigPrecursor
defaultConfigPrecursor =
    ConfigPrecursor
        { _defaultDirectory    = defaultDirectory defaultConfiguration
        , _defaultWithSource   = defaultWithSource defaultConfiguration
        , _defaultDPI          = defaultDPI defaultConfiguration
        , _defaultSaveFormat   = defaultSaveFormat defaultConfiguration
        , _defaultDependencies = defaultDependencies defaultConfiguration
        , _captionFormat       = captionFormat defaultConfiguration

        , _logPrec             = LoggingPrecursor (logVerbosity defaultConfiguration) Nothing -- _logFilePath=Nothing implies log to stderr

        , _matplotlibPrec      = MatplotlibPrecursor   Nothing (matplotlibTightBBox defaultConfiguration) (matplotlibTransparent defaultConfiguration) (matplotlibExe defaultConfiguration)
        , _matlabPrec          = MatlabPrecursor       Nothing (matlabExe defaultConfiguration)
        , _plotlyPythonPrec    = PlotlyPythonPrecursor Nothing (plotlyPythonExe defaultConfiguration)
        , _plotlyRPrec         = PlotlyRPrecursor      Nothing (plotlyRExe defaultConfiguration)
        , _mathematicaPrec     = MathematicaPrecursor  Nothing (mathematicaExe defaultConfiguration)
        , _octavePrec          = OctavePrecursor       Nothing (octaveExe defaultConfiguration)
        , _ggplot2Prec         = GGPlot2Precursor      Nothing (ggplot2Exe defaultConfiguration)
        , _gnuplotPrec         = GNUPlotPrecursor      Nothing (gnuplotExe defaultConfiguration)
        , _graphvizPrec        = GraphvizPrecursor     Nothing (graphvizExe defaultConfiguration)
        , _bokehPrec           = BokehPrecursor        Nothing (bokehExe defaultConfiguration)
        , _plotsjlPrec         = PlotsjlPrecursor      Nothing (plotsjlExe defaultConfiguration)
        }


data LoggingPrecursor = LoggingPrecursor { _logVerbosity :: !Verbosity
                                         , _logFilePath  :: !(Maybe FilePath)
                                         }

-- Separate YAML clauses have their own types.
data MatplotlibPrecursor = MatplotlibPrecursor
        { _matplotlibPreamble    :: !(Maybe FilePath)
        , _matplotlibTightBBox   :: !Bool
        , _matplotlibTransparent :: !Bool
        , _matplotlibExe         :: !FilePath
        }
data MatlabPrecursor        = MatlabPrecursor       {_matlabPreamble       :: !(Maybe FilePath), _matlabExe       :: !FilePath}
data PlotlyPythonPrecursor  = PlotlyPythonPrecursor {_plotlyPythonPreamble :: !(Maybe FilePath), _plotlyPythonExe :: !FilePath}
data PlotlyRPrecursor       = PlotlyRPrecursor      {_plotlyRPreamble      :: !(Maybe FilePath), _plotlyRExe      :: !FilePath}
data MathematicaPrecursor   = MathematicaPrecursor  {_mathematicaPreamble  :: !(Maybe FilePath), _mathematicaExe  :: !FilePath}
data OctavePrecursor        = OctavePrecursor       {_octavePreamble       :: !(Maybe FilePath), _octaveExe       :: !FilePath}
data GGPlot2Precursor       = GGPlot2Precursor      {_ggplot2Preamble      :: !(Maybe FilePath), _ggplot2Exe      :: !FilePath}
data GNUPlotPrecursor       = GNUPlotPrecursor      {_gnuplotPreamble      :: !(Maybe FilePath), _gnuplotExe      :: !FilePath}
data GraphvizPrecursor      = GraphvizPrecursor     {_graphvizPreamble     :: !(Maybe FilePath), _graphvizExe     :: !FilePath}
data BokehPrecursor         = BokehPrecursor        {_bokehPreamble        :: !(Maybe FilePath), _bokehExe        :: !FilePath}
data PlotsjlPrecursor       = PlotsjlPrecursor      {_plotsjlPreamble      :: !(Maybe FilePath), _plotsjlExe      :: !FilePath}

instance FromJSON LoggingPrecursor where
    parseJSON (Object v) =
        LoggingPrecursor <$> v .:? "verbosity" .!= (logVerbosity defaultConfiguration)
                         <*> v .:? "filepath"
    parseJSON _ = fail $ mconcat ["Could not parse logging configuration. "]

instance FromJSON MatplotlibPrecursor where
    parseJSON (Object v) =
        MatplotlibPrecursor
            <$> v .:? (tshow PreambleK)
            <*> v .:? (tshow MatplotlibTightBBoxK)   .!= (matplotlibTightBBox defaultConfiguration)
            <*> v .:? (tshow MatplotlibTransparentK) .!= (matplotlibTransparent defaultConfiguration)
            <*> v .:? (tshow ExecutableK)  .!= (matplotlibExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show Matplotlib, " configuration."]

instance FromJSON MatlabPrecursor where
    parseJSON (Object v) = MatlabPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (matlabExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show Matlab, " configuration."]

instance FromJSON PlotlyPythonPrecursor where
    parseJSON (Object v) = PlotlyPythonPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotlyPythonExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show PlotlyPython, " configuration."]

instance FromJSON PlotlyRPrecursor where
    parseJSON (Object v) = PlotlyRPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotlyRExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show PlotlyR, " configuration."]

instance FromJSON MathematicaPrecursor where
    parseJSON (Object v) = MathematicaPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (mathematicaExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show Mathematica, " configuration."]

instance FromJSON OctavePrecursor where
    parseJSON (Object v) = OctavePrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (octaveExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show Octave, " configuration."]

instance FromJSON GGPlot2Precursor where
    parseJSON (Object v) = GGPlot2Precursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (ggplot2Exe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show GGPlot2, " configuration."]

instance FromJSON GNUPlotPrecursor where
    parseJSON (Object v) = GNUPlotPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (gnuplotExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show GNUPlot, " configuration."]

instance FromJSON GraphvizPrecursor where
    parseJSON (Object v) = GraphvizPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (graphvizExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show Graphviz, " configuration."]

instance FromJSON BokehPrecursor where
    parseJSON (Object v) = BokehPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (bokehExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show Bokeh, " configuration."]

instance FromJSON PlotsjlPrecursor where
    parseJSON (Object v) = PlotsjlPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotsjlExe defaultConfiguration)
    parseJSON _ = fail $ mconcat ["Could not parse ", show Plotsjl, " configuration."]


instance FromJSON ConfigPrecursor where
    parseJSON (Null) = return defaultConfigPrecursor -- In case of empty file
    parseJSON (Object v) = do

        _defaultDirectory    <- v .:? (tshow DirectoryK)     .!= (_defaultDirectory defaultConfigPrecursor)
        _defaultWithSource   <- v .:? (tshow WithSourceK)    .!= (_defaultWithSource defaultConfigPrecursor)
        _defaultDPI          <- v .:? (tshow DpiK)           .!= (_defaultDPI defaultConfigPrecursor)
        _defaultSaveFormat   <- v .:? (tshow SaveFormatK)    .!= (_defaultSaveFormat defaultConfigPrecursor)
        _defaultDependencies <- v .:? (tshow DependenciesK)  .!= (_defaultDependencies defaultConfigPrecursor)
        _captionFormat       <- v .:? (tshow CaptionFormatK) .!= (_captionFormat defaultConfigPrecursor)

        _logPrec             <- v .:? "logging"              .!= _logPrec defaultConfigPrecursor

        _matplotlibPrec      <- v .:? (cls Matplotlib)       .!= _matplotlibPrec defaultConfigPrecursor
        _matlabPrec          <- v .:? (cls Matlab)           .!= _matlabPrec defaultConfigPrecursor
        _plotlyPythonPrec    <- v .:? (cls PlotlyPython)     .!= _plotlyPythonPrec defaultConfigPrecursor
        _plotlyRPrec         <- v .:? (cls PlotlyR)          .!= _plotlyRPrec defaultConfigPrecursor
        _mathematicaPrec     <- v .:? (cls Mathematica)      .!= _mathematicaPrec defaultConfigPrecursor
        _octavePrec          <- v .:? (cls Octave)           .!= _octavePrec defaultConfigPrecursor
        _ggplot2Prec         <- v .:? (cls GGPlot2)          .!= _ggplot2Prec defaultConfigPrecursor
        _gnuplotPrec         <- v .:? (cls GNUPlot)          .!= _gnuplotPrec defaultConfigPrecursor
        _graphvizPrec        <- v .:? (cls Graphviz)         .!= _graphvizPrec defaultConfigPrecursor
        _bokehPrec           <- v .:? (cls Bokeh)            .!= _bokehPrec defaultConfigPrecursor
        _plotsjlPrec         <- v .:? (cls Plotsjl)          .!= _plotsjlPrec defaultConfigPrecursor

        return $ ConfigPrecursor{..}
    parseJSON _          = fail "Could not parse configuration."


renderConfig :: ConfigPrecursor -> IO Configuration
renderConfig ConfigPrecursor{..} = do
    let defaultDirectory    = _defaultDirectory
        defaultWithSource   = _defaultWithSource
        defaultDPI          = _defaultDPI
        defaultSaveFormat   = _defaultSaveFormat
        defaultDependencies = _defaultDependencies
        captionFormat       = _captionFormat

        logVerbosity        = _logVerbosity _logPrec
        logSink             = maybe StdErr LogFile (_logFilePath _logPrec)

        matplotlibTightBBox   = _matplotlibTightBBox _matplotlibPrec
        matplotlibTransparent = _matplotlibTransparent _matplotlibPrec

        matplotlibExe   = _matplotlibExe _matplotlibPrec
        matlabExe       = _matlabExe _matlabPrec
        plotlyPythonExe = _plotlyPythonExe _plotlyPythonPrec
        plotlyRExe      = _plotlyRExe _plotlyRPrec
        mathematicaExe  = _mathematicaExe _mathematicaPrec
        octaveExe       = _octaveExe _octavePrec
        ggplot2Exe      = _ggplot2Exe _ggplot2Prec
        gnuplotExe      = _gnuplotExe _gnuplotPrec
        graphvizExe     = _graphvizExe _graphvizPrec
        bokehExe        = _bokehExe _bokehPrec
        plotsjlExe      = _plotsjlExe _plotsjlPrec

    matplotlibPreamble   <- readPreamble (_matplotlibPreamble _matplotlibPrec)
    matlabPreamble       <- readPreamble (_matlabPreamble _matlabPrec)
    plotlyPythonPreamble <- readPreamble (_plotlyPythonPreamble _plotlyPythonPrec)
    plotlyRPreamble      <- readPreamble (_plotlyRPreamble _plotlyRPrec)
    mathematicaPreamble  <- readPreamble (_mathematicaPreamble _mathematicaPrec)
    octavePreamble       <- readPreamble (_octavePreamble _octavePrec)
    ggplot2Preamble      <- readPreamble (_ggplot2Preamble _ggplot2Prec)
    gnuplotPreamble      <- readPreamble (_gnuplotPreamble _gnuplotPrec)
    graphvizPreamble     <- readPreamble (_graphvizPreamble _graphvizPrec)
    bokehPreamble        <- readPreamble (_bokehPreamble _bokehPrec)
    plotsjlPreamble      <- readPreamble (_plotsjlPreamble _plotsjlPrec)

    return Configuration{..}
    where
        readPreamble fp = fromMaybe mempty $ TIO.readFile <$> fp


tshow :: Show a => a -> Text
tshow = pack . show