-- | This module provides a basic framework to render LaTeX formulae inside Pandoc documents
--   for Hakyll pages.
--
--   See the latex-svg page on GitHub for more information.
--
--      https://github.com/phadej/latex-svg#readme
--
module Hakyll.Contrib.LaTeX (
    initFormulaCompilerSVG,
    initFormulaCompilerSVGPure,
    CacheSize,
    compileFormulaeSVG,
    ) where

import Data.Char              (isSpace)
import Hakyll.Core.Compiler   (Compiler, unsafeCompiler)
import Text.Pandoc.Definition (Pandoc)

import Image.LaTeX.Render
import Image.LaTeX.Render.Pandoc

import qualified Data.Cache.LRU.IO as LRU

-- | Number of formula images to keep in memory during a @watch@ session.
type CacheSize = Integer

-- | Creates a formula compiler with caching. Can be used as in the following minimal example:
--
-- @
-- main = do
--     renderFormulae <- 'initFormulaCompilerSVG' 1000 'defaultEnv"
--     hakyll $
--         match "posts/*.markdown" $ do
--             route $ setExtension "html"
--             compile $ pandocCompilerWithTransformM
--                  defaultHakyllReaderOptions
--                  defaultHakyllWriterOptions
--                  (renderFormulae 'defaultPandocFormulaOptions')
-- @
--
initFormulaCompilerSVG
    :: CacheSize
    -> EnvironmentOptions
    -> IO (PandocFormulaOptions -> Pandoc -> Compiler Pandoc)
initFormulaCompilerSVG cs eo = do
    mImageForFormula <- curry <$> memoizeLru (Just cs) (uncurry drawFormula)
    let eachFormula x y = do
          putStrLn $ "    formula (" ++ environment x ++ ") \"" ++ equationPreview y ++ "\""
          mImageForFormula x y
    return $ \fo -> unsafeCompiler . convertAllFormulaeSVGWith eachFormula fo
  where
    drawFormula x y = do
        putStrLn "      drawing..."
        imageForFormula eo x y
--
-- | Creates a formula compiler. Can be used as in the following minimal example:
--
-- @
-- main = hakyll $ do
--     let renderFormulae = 'initFormulaCompilerSVGPure' 'defaultEnv"
--     match "posts/*.markdown" $ do
--         route $ setExtension "html"
--         compile $ pandocCompilerWithTransformM
--              defaultHakyllReaderOptions
--              defaultHakyllWriterOptions
--              (renderFormulae 'defaultPandocFormulaOptions')
-- @
--
initFormulaCompilerSVGPure
    :: EnvironmentOptions
    -> PandocFormulaOptions -> Pandoc -> Compiler Pandoc
initFormulaCompilerSVGPure eo fo pandoc = do
    let mImageForFormula = drawFormula
    let eachFormula x y = do
          putStrLn $ "    formula (" ++ environment x ++ ") \"" ++ equationPreview y ++ "\""
          mImageForFormula x y

    unsafeCompiler (convertAllFormulaeSVGWith eachFormula fo pandoc)
  where
    drawFormula x y = do
        putStrLn "      drawing..."
        imageForFormula eo x y

-- | A formula compiler that does not use caching, which works in a more drop-in fashion, as in:
--
-- > compile $ pandocCompilerWithTransformM (compileFormulaeSVG defaultEnv defaultPandocFormulaOptions)
--
compileFormulaeSVG
    :: EnvironmentOptions
    -> PandocFormulaOptions
    -> Pandoc -> Compiler Pandoc
compileFormulaeSVG eo po =
    let eachFormula x y = do
          putStrLn $ "    formula (" ++ environment x ++ ") \"" ++ equationPreview y ++ "\""
          putStrLn   "      drawing..."
          imageForFormula eo x y
    in unsafeCompiler . convertAllFormulaeSVGWith eachFormula po

equationPreview :: String -> String
equationPreview x'
    | length x <= 16 = x
    | otherwise      = take 16 $ filter (/= '\n') x ++ "..."
  where
    x = dropWhile isSpace x'

memoizeLru :: Ord a => Maybe Integer -> (a -> IO b) -> IO (a -> IO b)
memoizeLru msize action = do
    lru <- LRU.newAtomicLRU msize
    return $ \arg -> do
        mret <- LRU.lookup arg lru
        case mret of
            Just ret -> return ret
            Nothing -> do
                ret <- action arg
                LRU.insert arg ret lru
                return ret