{-# LANGUAGE CPP               #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{- |
   Module      : Text.Pandoc.Filter
   Copyright   : Copyright (C) 2006-2024 John MacFarlane
   License     : GNU GPL, version 2 or above

   Maintainer  : John MacFarlane <jgm@berkeley@edu>
   Stability   : alpha
   Portability : portable

Programmatically modifications of pandoc documents.
-}
module Text.Pandoc.Filter
  ( Filter (..)
  , Environment (..)
  , applyFilters
  , applyJSONFilter
  ) where

import System.CPUTime (getCPUTime)
import Data.Aeson
import Data.Maybe (fromMaybe)
import GHC.Generics (Generic)
import Text.Pandoc.Class (PandocMonad, findFileWithDataFallback, getVerbosity,
                          report)
import Text.Pandoc.Definition (Pandoc)
import Text.Pandoc.Filter.Environment (Environment (..))
import Text.Pandoc.Logging
import Text.Pandoc.Citeproc (processCitations)
import Text.Pandoc.Scripting (ScriptingEngine (engineApplyFilter))
import qualified Text.Pandoc.Filter.JSON as JSONFilter
import qualified Data.Text as T
import System.FilePath (takeExtension)
import Control.Applicative ((<|>))
import Control.Monad.Trans (MonadIO (liftIO))
import Control.Monad (foldM, when)

-- | Type of filter and path to filter file.
data Filter = LuaFilter FilePath
            | JSONFilter FilePath
            | CiteprocFilter -- built-in citeproc
            deriving (Show, Generic)

instance FromJSON Filter where
 parseJSON node =
  (withObject "Filter" $ \m -> do
    ty <- m .: "type"
    fp <- m .:? "path"
    let missingPath = fail $ "Expected 'path' for filter of type " ++ show ty
    let filterWithPath constr = maybe missingPath (return . constr . T.unpack)
    case ty of
      "citeproc" -> return CiteprocFilter
      "lua"  -> filterWithPath LuaFilter fp
      "json" -> filterWithPath JSONFilter fp
      _      -> fail $ "Unknown filter type " ++ show (ty :: T.Text)) node
  <|>
  (withText "Filter" $ \t -> do
    let fp = T.unpack t
    if fp == "citeproc"
       then return CiteprocFilter
       else return $
         case takeExtension fp of
           ".lua"  -> LuaFilter fp
           _       -> JSONFilter fp) node

instance ToJSON Filter where
 toJSON CiteprocFilter = object [ "type" .= String "citeproc" ]
 toJSON (LuaFilter fp) = object [ "type" .= String "lua",
                                  "path" .= String (T.pack fp) ]
 toJSON (JSONFilter fp) = object [ "type" .= String "json",
                                   "path" .= String (T.pack fp) ]

-- | Modify the given document using a filter.
applyFilters :: (PandocMonad m, MonadIO m)
             => ScriptingEngine
             -> Environment
             -> [Filter]
             -> [String]
             -> Pandoc
             -> m Pandoc
applyFilters scrngin fenv filters args d = do
  expandedFilters <- mapM expandFilterPath filters
  foldM applyFilter d expandedFilters
 where
  applyFilter doc (JSONFilter f) =
    withMessages f $ JSONFilter.apply fenv args f doc
  applyFilter doc (LuaFilter f)  =
    withMessages f $ engineApplyFilter scrngin fenv args f doc
  applyFilter doc CiteprocFilter =
    withMessages "citeproc" $ processCitations doc
  withMessages f action = do
    verbosity <- getVerbosity
    when (verbosity == INFO) $ report $ RunningFilter f
    starttime <- liftIO getCPUTime
    res <- action
    endtime <- liftIO getCPUTime
    when (verbosity == INFO) $ report $ FilterCompleted f $ toMilliseconds $ endtime - starttime
    return res
  toMilliseconds picoseconds = picoseconds `div` 1000000000

-- | Expand paths of filters, searching the data directory.
expandFilterPath :: (PandocMonad m, MonadIO m) => Filter -> m Filter
expandFilterPath (LuaFilter fp) = LuaFilter <$> filterPath fp
expandFilterPath (JSONFilter fp) = JSONFilter <$> filterPath fp
expandFilterPath CiteprocFilter = return CiteprocFilter

filterPath :: PandocMonad m => FilePath -> m FilePath
filterPath fp = fromMaybe fp <$> findFileWithDataFallback "filters" fp

applyJSONFilter :: MonadIO m
                => Environment
                -> [String]
                -> FilePath
                -> Pandoc
                -> m Pandoc
applyJSONFilter = JSONFilter.apply
