{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : $header$
-- Copyright   : (c) Laurent P René de Cotret, 2019 - present
-- License     : GNU GPL, version 2 or above
-- Maintainer  : laurent.decotret@outlook.com
-- Stability   : internal
-- Portability : portable
--
-- This module defines base types in use in pandoc-plot
module Text.Pandoc.Filter.Plot.Monad.Types
  ( Toolkit (..),
    Renderer (..),
    AvailabilityCheck (..),
    Script,
    CheckResult (..),
    InclusionKey (..),
    FigureSpec (..),
    OutputSpec (..),
    SaveFormat (..),
    cls,
    extension,
    toolkits,
    inclusionKeys,
    Executable (..),
    exeFromPath,
    pathToExe,
    -- Utilities
    isWindows,
  )
where

import Data.Char (toLower)
import Data.List (intersperse)
import Data.String (IsString (..))
import Data.Text (Text, pack, unpack)
import Data.Yaml (FromJSON (..), ToJSON (toJSON), withText)
import GHC.Generics (Generic)
import System.FilePath (isAbsolute, splitFileName, (</>))
import System.Info (os)
import Text.Pandoc.Definition (Attr)

-- | List of supported toolkits.
toolkits :: [Toolkit]
toolkits = enumFromTo minBound maxBound

-- | Enumeration of supported toolkits
data Toolkit
  = Matplotlib
  | Matlab
  | PlotlyPython
  | PlotlyR
  | Mathematica
  | Octave
  | GGPlot2
  | GNUPlot
  | Graphviz
  | Bokeh
  | Plotsjl
  | PlantUML
  | SageMath
  | D2
  | Asymptote
  | Mermaid
  deriving (Bounded, Eq, Enum, Generic, Ord)

-- | This instance should only be used to display toolkit names
instance Show Toolkit where
  show Matplotlib = "Python/Matplotlib"
  show Matlab = "MATLAB"
  show PlotlyPython = "Python/Plotly"
  show PlotlyR = "R/Plotly"
  show Mathematica = "Mathematica"
  show Octave = "GNU Octave"
  show GGPlot2 = "ggplot2"
  show GNUPlot = "gnuplot"
  show Graphviz = "graphviz"
  show Bokeh = "Python/Bokeh"
  show Plotsjl = "Julia/Plots.jl"
  show PlantUML = "PlantUML"
  show SageMath = "SageMath"
  show D2 = "D2"
  show Asymptote = "Asymptote"
  show Mermaid = "Mermaid"

-- | Class name which will trigger the filter
cls :: Toolkit -> Text
cls Matplotlib = "matplotlib"
cls Matlab = "matlabplot"
cls PlotlyPython = "plotly_python"
cls PlotlyR = "plotly_r"
cls Mathematica = "mathplot"
cls Octave = "octaveplot"
cls GGPlot2 = "ggplot2"
cls GNUPlot = "gnuplot"
cls Graphviz = "graphviz"
cls Bokeh = "bokeh"
cls Plotsjl = "plotsjl"
cls PlantUML = "plantuml"
cls SageMath = "sageplot"
cls D2 = "d2"
cls Asymptote = "asy"
cls Mermaid = "mermaid"

-- | Executable program, and sometimes the directory where it can be found.
data Executable
  = AbsExe FilePath Text
  | RelExe Text

exeFromPath :: FilePath -> Executable
exeFromPath fp
  | isAbsolute fp =
      let (dir, name) = splitFileName fp
       in AbsExe dir (pack name)
  | otherwise = RelExe (pack fp)

pathToExe :: Executable -> FilePath
pathToExe (AbsExe dir name) = dir </> unpack name
pathToExe (RelExe name) = unpack name

-- | Source context for plotting scripts
type Script = Text

-- | Result of checking scripts for problems
data CheckResult
  = CheckPassed
  | CheckFailed Text
  deriving (Eq)

instance Semigroup CheckResult where
  (<>) CheckPassed a = a
  (<>) a CheckPassed = a
  (<>) (CheckFailed msg1) (CheckFailed msg2) = CheckFailed (msg1 <> msg2)

instance Monoid CheckResult where
  mempty = CheckPassed

-- | Description of any possible inclusion key, both in documents
-- and in configuration files.
data InclusionKey
  = DirectoryK
  | CaptionK
  | SaveFormatK
  | WithSourceK
  | CaptionFormatK
  | PreambleK
  | DpiK
  | SourceCodeLabelK
  | StrictModeK
  | ExecutableK
  | CommandLineArgsK
  | DependenciesK
  | FileK
  | MatplotlibTightBBoxK
  | MatplotlibTransparentK
  deriving (Bounded, Eq, Enum)

-- | Keys that pandoc-plot will look for in code blocks.
-- These are only exported for testing purposes.
instance Show InclusionKey where
  show DirectoryK = "directory"
  show CaptionK = "caption"
  show SaveFormatK = "format"
  show WithSourceK = "source"
  show CaptionFormatK = "caption_format"
  show PreambleK = "preamble"
  show DpiK = "dpi"
  show SourceCodeLabelK = "source_label"
  show StrictModeK = "strict"
  show ExecutableK = "executable"
  show CommandLineArgsK = "command_line_arguments"
  show DependenciesK = "dependencies"
  show FileK = "file"
  show MatplotlibTightBBoxK = "tight_bbox"
  show MatplotlibTransparentK = "transparent"

-- | List of all keys related to pandoc-plot that
-- can be specified in source material.
inclusionKeys :: [InclusionKey]
inclusionKeys = enumFromTo (minBound :: InclusionKey) maxBound

-- | Datatype containing all parameters required to specify a figure.
--
-- It is assumed that once a @FigureSpec@ has been created, no configuration
-- can overload it; hence, a @FigureSpec@ completely encodes a particular figure.
data FigureSpec = FigureSpec
  { -- | Renderer to use for this figure.
    renderer_ :: !Renderer,
    -- | Executable to use in rendering this figure.
    fsExecutable :: Executable,
    -- | Figure caption.
    caption :: !Text,
    -- | Append link to source code in caption.
    withSource :: !Bool,
    -- | Source code for the figure.
    script :: !Script,
    -- | Save format of the figure.
    saveFormat :: !SaveFormat,
    -- | Directory where to save the file.
    directory :: !FilePath,
    -- | Dots-per-inch of figure.
    dpi :: !Int,
    -- | Files/directories on which this figure depends, e.g. data files.
    dependencies :: ![FilePath],
    -- | Renderer-specific extra attributes.
    extraAttrs :: ![(Text, Text)],
    -- | Attributes not related to @pandoc-plot@ will be propagated.
    blockAttrs :: !Attr
  }

-- | Generated figure file format supported by pandoc-plot.
-- Note that not all formats are supported by all toolkits.
data SaveFormat
  = -- | Portable network graphics
    PNG
  | -- | Portable document format
    PDF
  | -- | Scalable vector graphics
    SVG
  | -- | JPEG/JPG compressed image
    JPG
  | -- | Encapsulated postscript
    EPS
  | -- | GIF format
    GIF
  | -- | Tagged image format
    TIF
  | -- | WebP image format
    WEBP
  | -- | HTML for interactive plots.
    HTML
  | -- | LaTeX text and pdf graphics
    LaTeX
  deriving (Bounded, Enum, Ord, Eq, Show, Generic)

instance IsString SaveFormat where
  fromString s
    | s `elem` ["png", "PNG", ".png"] = PNG
    | s `elem` ["pdf", "PDF", ".pdf"] = PDF
    | s `elem` ["svg", "SVG", ".svg"] = SVG
    | s `elem` ["eps", "EPS", ".eps"] = EPS
    | s `elem` ["gif", "GIF", ".gif"] = GIF
    | s `elem` ["jpg", "jpeg", "JPG", "JPEG", ".jpg", ".jpeg"] = JPG
    | s `elem` ["tif", "tiff", "TIF", "TIFF", ".tif", ".tiff"] = TIF
    | s `elem` ["webp", "WEBP", ".webp"] = WEBP
    | s `elem` ["html", "HTML", ".html"] = HTML
    | s `elem` ["latex", "LaTeX", ".tex"] = LaTeX
    | otherwise =
        errorWithoutStackTrace $
          mconcat
            [ s,
              " is not one of the valid save formats : ",
              mconcat $ intersperse ", " $ show <$> saveFormats,
              " (and lowercase variations). "
            ]
    where
      saveFormats = enumFromTo minBound maxBound :: [SaveFormat]

-- | Use the IsString instance to parse JSON so that the parsing is flexible
-- with respect to uppercase/lowercase (#42)
instance FromJSON SaveFormat where
  parseJSON = withText "SaveFormat" (pure . fromString . unpack)

instance ToJSON SaveFormat where
  toJSON = toJSON . extension

-- | Save format file extension
extension :: SaveFormat -> String
extension LaTeX = ".tex"
extension fmt = mconcat [".", fmap toLower . show $ fmt]

isWindows :: Bool
isWindows = os `elem` ["mingw32", "win32", "cygwin32"] -- Aliases taken from cabal's Distribution.System module

-- | Internal description of all information
-- needed to output a figure.
data OutputSpec = OutputSpec
  { -- | Figure spec
    oFigureSpec :: FigureSpec,
    -- | Path to the script to render
    oScriptPath :: FilePath,
    -- | Figure output path
    oFigurePath :: FilePath,
    -- | Executable to use during rendering
    oExecutable :: Executable,
    -- | Current working directory
    oCWD :: FilePath
  }

data AvailabilityCheck
  = CommandSuccess (Executable -> Text)
  | ExecutableExists

data Renderer = Renderer
  { rendererToolkit :: Toolkit,
    rendererCapture :: FigureSpec -> FilePath -> Script,
    rendererCommand :: OutputSpec -> Text,
    rendererAvailability :: AvailabilityCheck,
    rendererSupportedSaveFormats :: [SaveFormat],
    rendererChecks :: [Script -> CheckResult],
    rendererLanguage :: Text,
    rendererComment :: Text -> Text,
    rendererScriptExtension :: String
  }
