content: {-# LANGUAGE ScopedTypeVariables #-}
  module Reanimate.Driver.Check
    ( checkEnvironment
    , hasRSvg
    , hasInkscape
    , hasMagick
    , hasFFmpegRSvg
    ) where
  
  import           Control.Exception            (SomeException, handle)
  import           Control.Monad                (forM_)
  import           Data.Maybe                   (fromMaybe, listToMaybe)
  import           Data.Version                 (Version (Version), parseVersion, showVersion)
  import           POVRay                       (povrayApp)
  import           Reanimate.Driver.Magick      (magickCmd)
  import           Reanimate.Misc               (runCmd_)
  import           System.Console.ANSI.Codes
  import           System.Directory             (findExecutable)
  import           System.IO                    (hClose, hPutStr)
  import           System.IO.Temp               (withSystemTempDirectory, withTempFile)
  import           Text.ParserCombinators.ReadP (readP_to_S)
  import           Text.Printf                  (printf)
  
  --------------------------------------------------------------------------
  -- Check environment
  
  checkEnvironment :: IO ()
  checkEnvironment = do
      putStrLn "reanimate checks:"
      runCheck "Has ffmpeg" hasFFmpeg
      runCheck "Has ffmpeg(rsvg)" hasFFmpegRSvg
      runCheck "Has dvisvgm" hasDvisvgm
      runCheck "Has povray" hasPovray
      runCheck "Has blender" hasBlender
      runCheck "Has rsvg-convert" hasRSvg
      runCheck "Has inkscape" hasInkscape
      runCheck "Has imagemagick" hasMagick
      runCheck "Has LaTeX" hasLaTeX
      runCheck ("Has LaTeX package '"++ "babel" ++ "'") $ hasTeXPackage "latex"
        "[english]{babel}"
      forM_ latexPackages $ \pkg ->
        runCheck ("Has LaTeX package '"++ pkg ++ "'") $ hasTeXPackage "latex" $
          "{"++pkg++"}"
      runCheck "Has XeLaTeX" hasXeLaTeX
      forM_ xelatexPackages $ \pkg ->
        runCheck ("Has XeLaTeX package '"++ pkg ++ "'") $ hasTeXPackage "xelatex" $
          "{"++pkg++"}"
    where
      latexPackages =
        ["preview"
        ,"amsmath"
        --,"amssymb"
        --,"dsfont"
        --,"setspace"
        --,"relsize"
        --,"textcomp"
        --,"mathrsfs"
        --,"calligra"
        --,"wasysym"
        --,"ragged2e"
        --,"physics"
        --,"xcolor"
        --,"textcomp"
        --,"xfrac"
        --,"microtype"
        ]
      xelatexPackages =
        ["ctex"]
      runCheck msg fn = do
        printf "  %-35s" (msg ++ ":")
        val <- fn
        case val of
          Left err -> putStrLnColor Red err
          Right ok -> putStrLnColor Green ok
  
  putStrLnColor :: Color -> String -> IO ()
  putStrLnColor color msg =
    putStrLn $ setSGRCode [SetColor Foreground Vivid color] ++ msg ++ setSGRCode [Reset]
  
  -- latex, dvisvgm, xelatex
  
  hasLaTeX :: IO (Either String String)
  hasLaTeX = hasProgram "latex"
  
  hasXeLaTeX :: IO (Either String String)
  hasXeLaTeX = hasProgram "xelatex"
  
  hasDvisvgm :: IO (Either String String)
  hasDvisvgm = hasProgram "dvisvgm"
  
  hasPovray :: IO (Either String String)
  hasPovray = hasProgram povrayApp
  
  hasFFmpeg :: IO (Either String String)
  hasFFmpeg = checkMinVersion minVersion <$> ffmpegVersion
    where
      minVersion = Version [4,1,3] []
  
  hasFFmpegRSvg :: IO (Either String String)
  hasFFmpegRSvg = do
    mbPath <- findExecutable "ffmpeg"
    case mbPath of
      Nothing -> return $ Left "n/a"
      Just path -> do
        ret <- runCmd_ path ["-version"]
        pure $ case ret of
          Right out | "--enable-librsvg" `elem` words out
            -> Right "yes"
          _ -> Left "no"
  
  hasBlender :: IO (Either String String)
  hasBlender = checkMinVersion minVersion <$> blenderVersion
    where
      minVersion = Version [2,80] []
  
  hasRSvg :: IO (Either String String)
  hasRSvg = checkMinVersion minVersion <$> rsvgVersion
    where
      minVersion = Version [2,44,0] []
  
  hasInkscape :: IO (Either String String)
  hasInkscape = checkMinVersion minVersion <$> inkscapeVersion
    where
      minVersion = Version [1,0] []
  
  hasMagick :: IO (Either String String)
  hasMagick = checkMinVersion minVersion <$> magickVersion
    where
      minVersion = Version [6,0,0] []
  
  ffmpegVersion :: IO (Maybe Version)
  ffmpegVersion = extractVersion "ffmpeg" ["-version"] $ \line ->
        case take 3 $ words line of
          ["ffmpeg", "version", vs] -> vs
          _                         -> ""
  
  blenderVersion :: IO (Maybe Version)
  blenderVersion = extractVersion "blender" ["--version"] $ \line ->
      case take 2 (words line) of
        ["Blender", vs] -> vs
        _               -> ""
  
  rsvgVersion :: IO (Maybe Version)
  rsvgVersion = extractVersion "rsvg-convert" ["--version"] $ \line ->
    case words line of
      ["rsvg-convert", "version", vs] -> vs
      _                               -> ""
  
  inkscapeVersion :: IO (Maybe Version)
  inkscapeVersion = extractVersion "inkscape" ["--version"] $ \line ->
      case take 2 $ words line of
        ["Inkscape", vs] -> vs
        _                -> ""
  
  magickVersion :: IO (Maybe Version)
  magickVersion = extractVersion magickCmd ["-version"] $ \line ->
      case take 3 $ words line of
        ["Version:", "ImageMagick", vs] -> vs
        _                               -> ""
  
  checkMinVersion :: Version -> Maybe Version -> Either String String
  checkMinVersion _minVersion Nothing = Left "no"
  checkMinVersion minVersion (Just vs)
    | vs < minVersion = Left $ "too old: " ++ showVersion vs ++ " < " ++ showVersion minVersion
    | otherwise       = Right (showVersion vs)
  
  extractVersion :: FilePath -> [String] -> (String -> String) -> IO (Maybe Version)
  extractVersion execPath args outputFilter = do
    mbPath <- findExecutable execPath
    case mbPath of
      Nothing -> return Nothing
      Just path -> do
        ret <- runCmd_ path args
        case ret of
          Left{} -> return $ Just noVersion
          Right out ->
            pure $ Just $ fromMaybe noVersion $ parseVS $ outputFilter out
    where
      noVersion = Version [] []
      parseVS vs = listToMaybe $ reverse
        [ v | (v, _) <- readP_to_S parseVersion vs ]
  
  hasTeXPackage :: FilePath -> String -> IO (Either String String)
  hasTeXPackage exec pkg = handle (\(_::SomeException) -> return $ Left "n/a") $
      withSystemTempDirectory "reanimate" $ \tmp_dir -> withTempFile tmp_dir "test.tex" $ \tex_file tex_handle -> do
        hPutStr tex_handle tex_document
        hPutStr tex_handle $ "\\usepackage" ++ pkg ++ "\n"
        hPutStr tex_handle "\\begin{document}\n"
        hPutStr tex_handle "blah\n"
        hPutStr tex_handle tex_epilogue
        hClose tex_handle
        ret <- runCmd_ exec ["-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file]
        return $ case ret of
          Right{} -> Right "OK"
          Left{}  -> Left "missing"
    where
      tex_document = "\\documentclass[preview]{standalone}\n"
      tex_epilogue =
        "\n\
        \\\end{document}"
  
  hasProgram :: String -> IO (Either String String)
  hasProgram exec = do
    mbPath <- findExecutable exec
    return $ case mbPath of
      Nothing   -> Left $ "'" ++ exec ++ "' not found"
      Just path -> Right path
  