{-# 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.Default.Class     (Default, def)
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.Types

-- | 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 = def


-- | 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".

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

instance Default ConfigPrecursor where
    def = ConfigPrecursor
        { _defaultDirectory  = defaultDirectory def
        , _defaultWithSource = defaultWithSource def
        , _defaultDPI        = defaultDPI def
        , _defaultSaveFormat = defaultSaveFormat def
        , _captionFormat     = captionFormat def

        , _matplotlibPrec    = def
        , _matlabPrec        = def
        , _plotlyPythonPrec  = def
        , _plotlyRPrec       = def
        , _mathematicaPrec   = def
        , _octavePrec        = def
        , _ggplot2Prec       = def
        , _gnuplotPrec       = def
        , _graphvizPrec      = def
        }


-- 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 Default MatplotlibPrecursor where
    def = MatplotlibPrecursor Nothing (matplotlibTightBBox def) (matplotlibTransparent def) (matplotlibExe def)

instance Default MatlabPrecursor        where def = MatlabPrecursor       Nothing (matlabExe def)
instance Default PlotlyPythonPrecursor  where def = PlotlyPythonPrecursor Nothing (plotlyPythonExe def)
instance Default PlotlyRPrecursor       where def = PlotlyRPrecursor      Nothing (plotlyRExe def)
instance Default MathematicaPrecursor   where def = MathematicaPrecursor  Nothing (mathematicaExe def)
instance Default OctavePrecursor        where def = OctavePrecursor       Nothing (octaveExe def)
instance Default GGPlot2Precursor       where def = GGPlot2Precursor      Nothing (ggplot2Exe def)
instance Default GNUPlotPrecursor       where def = GNUPlotPrecursor      Nothing (gnuplotExe def)
instance Default GraphvizPrecursor      where def = GraphvizPrecursor     Nothing (graphvizExe def)

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

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

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

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

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

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

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

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

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


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

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

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

        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

        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