{-# 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           Text.Pandoc.Definition (Format(..), Pandoc(..), MetaValue(..), 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 [fp] [] ignoreEnv) >>= renderConfig


-- | Default configuration values.
--
-- @since 0.5.0.0
defaultConfiguration :: Configuration
defaultConfiguration =
    Configuration
        { defaultDirectory  = "plots/"
        , defaultWithSource = False
        , defaultDPI        = 80
        , defaultSaveFormat = PNG
        , 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

        , matplotlibExe       = if isWindows then "python" else "python3"
        , matlabExe           = "matlab"
        , plotlyPythonExe     = if isWindows then "python" else "python3"
        , plotlyRExe          = "Rscript"
        , mathematicaExe      = "math"
        , octaveExe           = "octave"
        , ggplot2Exe          = "Rscript"
        , gnuplotExe          = "gnuplot"
        , graphvizExe         = "dot"

        , matplotlibTightBBox   = False
        , matplotlibTransparent = False
        }


-- | 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 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    -- ^ The default directory where figures will be saved.
    , _defaultWithSource :: !Bool        -- ^ The default behavior of whether or not to include links to source code and high-res
    , _defaultDPI        :: !Int         -- ^ The default dots-per-inch value for generated figures. Renderers might ignore this.
    , _defaultSaveFormat :: !SaveFormat  -- ^ The default save format of generated figures.
    , _captionFormat     :: !Format      -- ^ Caption format in Pandoc notation, e.g. "markdown+tex_math_dollars".

    , _logPrec           :: !LoggingPrecursor

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

defaultConfigPrecursor :: ConfigPrecursor
defaultConfigPrecursor =
    ConfigPrecursor
        { _defaultDirectory  = defaultDirectory defaultConfiguration
        , _defaultWithSource = defaultWithSource defaultConfiguration
        , _defaultDPI        = defaultDPI defaultConfiguration
        , _defaultSaveFormat = defaultSaveFormat 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)
        }


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}

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 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)
        _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

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


renderConfig :: ConfigPrecursor -> IO Configuration
renderConfig ConfigPrecursor{..} = do
    let defaultDirectory  = _defaultDirectory
        defaultWithSource = _defaultWithSource
        defaultDPI        = _defaultDPI
        defaultSaveFormat = _defaultSaveFormat
        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

    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)

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


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