{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Control.Monad                 (unless)

import           Data.List                     (isInfixOf)
import           Data.Text                     (unpack)

import           Test.Tasty
import           Test.Tasty.HUnit

import qualified Text.Pandoc.Filter.FigureSpec as P
import qualified Text.Pandoc.Filter.Pyplot     as P
import qualified Text.Pandoc.Filter.Scripting  as P
import           Text.Pandoc.JSON

import           System.Directory              (createDirectory,
                                                createDirectoryIfMissing,
                                                doesDirectoryExist,
                                                doesFileExist, listDirectory,
                                                removeDirectoryRecursive,
                                                removePathForcibly)
import           System.FilePath               (isExtensionOf, (</>))
import           System.IO.Temp                (getCanonicalTemporaryDirectory)

main :: IO ()
main =
    defaultMain $
    testGroup
        "Text.Pandoc.Filter.Pyplot"
        [testFileCreation, testFileInclusion, testSaveFormat, testBlockingCallError]

plotCodeBlock :: P.PythonScript -> Block
plotCodeBlock script = CodeBlock (mempty, ["pyplot"], mempty) (unpack script)

addCaption :: String -> Block -> Block
addCaption caption (CodeBlock (id', cls, attrs) script) =
    CodeBlock (id', cls, attrs ++ [(P.captionKey, caption)]) script

addDirectory :: FilePath -> Block -> Block
addDirectory dir (CodeBlock (id', cls, attrs) script) =
    CodeBlock (id', cls, attrs ++ [(P.directoryKey, dir)]) script

addInclusion :: FilePath -> Block -> Block
addInclusion inclusionPath (CodeBlock (id', cls, attrs) script) =
    CodeBlock (id', cls, attrs ++ [(P.includePathKey, inclusionPath)]) script

addSaveFormat :: P.SaveFormat -> Block -> Block
addSaveFormat saveFormat (CodeBlock (id', cls, attrs) script) =
    CodeBlock (id', cls, attrs ++ [(P.saveFormatKey, P.extension saveFormat)]) script

addDPI :: Int -> Block -> Block
addDPI dpi (CodeBlock (id', cls, attrs) script) =
    CodeBlock (id', cls, attrs ++ [(P.dpiKey, show dpi)]) script

-- | Assert that a file exists
assertFileExists :: HasCallStack => FilePath -> Assertion
assertFileExists filepath = do
    fileExists <- doesFileExist filepath
    unless fileExists (assertFailure msg)
  where
    msg = mconcat ["File ", filepath, " does not exist."]

-- | Assert that the first list is contained,
-- wholly and intact, anywhere within the second.
assertIsInfix :: (Eq a, Show a, HasCallStack) => [a] -> [a] -> Assertion
assertIsInfix xs ys = unless (xs `isInfixOf` ys) (assertFailure msg)
  where
    msg = mconcat ["Expected ", show xs, " to be an infix of ", show ys]

-- Ensure a directory is empty but exists.
ensureDirectoryExistsAndEmpty :: FilePath -> IO ()
ensureDirectoryExistsAndEmpty dir = do
    exists <- doesDirectoryExist dir
    if exists
        then removePathForcibly dir
        else return ()
    createDirectory dir

-------------------------------------------------------------------------------
-- Test that plot files and source files are created when the filter is run
testFileCreation :: TestTree
testFileCreation =
    testCase "writes output files in appropriate directory" $ do
        tempDir <- (</> "test-file-creation") <$> getCanonicalTemporaryDirectory
        ensureDirectoryExistsAndEmpty tempDir
        let codeBlock = (addDirectory tempDir $ plotCodeBlock "import matplotlib.pyplot as plt\n")
        _ <- P.makePlot' codeBlock
        filesCreated <- length <$> listDirectory tempDir
        assertEqual "" filesCreated 3

-------------------------------------------------------------------------------
-- Test that included files are found within the source
testFileInclusion :: TestTree
testFileInclusion =
    testCase "includes plot inclusions" $ do
        tempDir <- (</> "test-file-inclusion") <$> getCanonicalTemporaryDirectory
        ensureDirectoryExistsAndEmpty tempDir
        let codeBlock =
                (addInclusion "test/fixtures/include.py" $
                 addDirectory tempDir $ plotCodeBlock "import matplotlib.pyplot as plt\n")
        _ <- P.makePlot' codeBlock
        inclusion <- readFile "test/fixtures/include.py"
        sourcePath <- head . filter (isExtensionOf "txt") <$> listDirectory tempDir
        src <- readFile (tempDir </> sourcePath)
        assertIsInfix inclusion src

-------------------------------------------------------------------------------
-- Test that the files are saved in the appropriate format
testSaveFormat :: TestTree
testSaveFormat =
    testCase "saves in the appropriate format" $ do
        tempDir <- (</> "test-safe-format") <$> getCanonicalTemporaryDirectory
        ensureDirectoryExistsAndEmpty tempDir
        let codeBlock =
                (addSaveFormat P.JPG $
                 addDirectory tempDir $
                 plotCodeBlock
                     "import matplotlib.pyplot as plt\nplt.figure()\nplt.plot([1,2], [1,2])")
        _ <- P.makePlot' codeBlock
        numberjpgFiles <-
            length <$> filter (isExtensionOf (P.extension P.JPG)) <$>
            listDirectory tempDir
        assertEqual "" numberjpgFiles 2

-------------------------------------------------------------------------------
-- Test that a script containing a blockign call to matplotlib.pyplot.show
-- returns the appropriate error
testBlockingCallError :: TestTree
testBlockingCallError =
    testCase "raises an exception for blocking calls" $ do
        tempDir <- getCanonicalTemporaryDirectory
        let codeBlock = plotCodeBlock "import matplotlib.pyplot as plt\nplt.show()"
        result <- P.makePlot' codeBlock
        case result of
            Right block -> assertFailure "did not catch the expected blocking call"
            Left error ->
                if error == P.BlockingCallError
                    then pure ()
                    else assertFailure "did not catch the expected blocking call"
-------------------------------------------------------------------------------