{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Reanimate.LaTeX
  ( latex
  , latexWithHeaders
  , latexChunks
  , xelatex
  , xelatexWithHeaders
  , ctex
  , ctexWithHeaders
  , latexAlign
  )
where
import qualified Data.ByteString               as B
import           Data.Text                                ( Text )
import qualified Data.Text                     as T
import qualified Data.Text.Encoding            as T
import           Graphics.SvgTree                         ( Tree(..)
                                                          , parseSvgFile
                                                          )
import           Reanimate.Cache
import           Reanimate.Misc
import           Reanimate.Svg
import           Reanimate.Parameters
import           System.FilePath                          ( replaceExtension
                                                          , takeFileName
                                                          , (</>)
                                                          )
import           System.IO.Unsafe                         ( unsafePerformIO )
latex :: T.Text -> Tree
latex = latexWithHeaders []
latexWithHeaders :: [T.Text] -> T.Text -> Tree
latexWithHeaders = someTexWithHeaders "latex" "dvi" []
someTexWithHeaders :: String -> String -> [String] -> [T.Text] -> T.Text -> Tree
someTexWithHeaders _exec _dvi _args _headers tex | pNoExternals = mkText tex
someTexWithHeaders exec dvi args headers tex =
  (unsafePerformIO . (cacheMem . cacheDiskSvg) (latexToSVG dvi exec args))
    script
 where
  script = mkTexScript exec args headers tex
latexChunks :: [T.Text] -> [Tree]
latexChunks chunks | pNoExternals = map mkText chunks
latexChunks chunks                = worker (svgGlyphs $ latex $ T.concat chunks) chunks
 where
  merge lst = mkGroup [ fmt svg | (fmt, _, svg) <- lst ]
  worker [] [] = []
  worker _ [] = error "latex chunk mismatch"
  worker everything (x : xs) =
    let width = length $ svgGlyphs (latex x)
    in merge (take width everything) : worker (drop width everything) xs
xelatex :: Text -> Tree
xelatex = xelatexWithHeaders []
xelatexWithHeaders :: [T.Text] -> T.Text -> Tree
xelatexWithHeaders = someTexWithHeaders "xelatex" "xdv" ["-no-pdf"]
ctex :: T.Text -> Tree
ctex = ctexWithHeaders []
ctexWithHeaders :: [T.Text] -> T.Text -> Tree
ctexWithHeaders headers = xelatexWithHeaders ("\\usepackage[UTF8]{ctex}" : headers)
latexAlign :: Text -> Tree
latexAlign tex = latex $ T.unlines ["\\begin{align*}", tex, "\\end{align*}"]
postprocess :: Tree -> Tree
postprocess = simplify
latexToSVG :: String -> String -> [String] -> Text -> IO Tree
latexToSVG dviExt latexExec latexArgs tex = do
  latexBin <- requireExecutable latexExec
  dvisvgm  <- requireExecutable "dvisvgm"
  withTempDir $ \tmp_dir -> withTempFile "tex" $ \tex_file ->
    withTempFile "svg" $ \svg_file -> do
      let dvi_file =
            tmp_dir </> replaceExtension (takeFileName tex_file) dviExt
      B.writeFile tex_file (T.encodeUtf8 tex)
      runCmd
        latexBin
        (  latexArgs
        ++ [ "-interaction=nonstopmode"
           , "-halt-on-error"
           , "-output-directory=" ++ tmp_dir
           , tex_file
           ]
        )
      runCmd
        dvisvgm
        [ dvi_file
        , "--precision=5"
        , "--exact"    
        , "--no-fonts" 
        , "--scale=0.1,-0.1"
        , "--verbosity=0"
        , "-o"
        , svg_file
        ]
      svg_data <- B.readFile svg_file
      case parseSvgFile svg_file svg_data of
        Nothing  -> error "Malformed svg"
        Just svg -> return $ postprocess $ unbox $ replaceUses svg
mkTexScript :: String -> [String] -> [Text] -> Text -> Text
mkTexScript latexExec latexArgs texHeaders tex =
  T.unlines
    $  [ "% " <> T.pack (unwords (latexExec : latexArgs))
       , "\\documentclass[preview]{standalone}"
       , "\\usepackage{amsmath}"
       , "\\usepackage{gensymb}"
       ]
    ++ texHeaders
    ++ [ "\\usepackage[english]{babel}"
       , "\\linespread{1}"
       , "\\begin{document}"
       , tex
       , "\\end{document}"
       ]