{-# LANGUAGE TemplateHaskell #-}

module SpecUtils
  ( checkExample,
    checkExampleDiff,
    check,
  )
where

import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import PyF.Internal.QQ
import System.Exit
import System.Process
import Test.Hspec

-- * Utils

-- | Runs a python formatter example
--
-- For conveniance, it exports a few python symbols, `inf`, `nan` and pi.
--
-- >>> runPythonExample "{3.14159:.1f}
-- "3.1
runPythonExample :: String -> IO (Maybe String)
runPythonExample s = do
  let pythonPath = "python3"
      args = ["-c", "from math import pi;nan = float('NaN');inf = float('inf');print(f\'''" ++ s ++ "''', end='')"]
  (ecode, stdout, _stderr) <- readProcessWithExitCode pythonPath args ""
  pure $ case ecode of
    ExitSuccess -> Just stdout
    ExitFailure _ -> Nothing

-- | `pyCheck formatString reference` compares a format string against
-- a reference (if `Just`) and against the python implementation
--
-- This TH expression will return an expression compatible with `Hspec` `SpecM`.
--
-- This expression is a failure if python cannot format this formatString
-- or if the python result does not match the (provided) reference.
pyCheck :: String -> Maybe String -> Q Exp
pyCheck s exampleStr = do
  pythonRes <- Language.Haskell.TH.Syntax.runIO (runPythonExample s)
  case pythonRes of
    Nothing -> [|expectationFailure $ "Expression: `" ++ s ++ "` fails in python"|]
    Just res -> do
      let qexp = [|$(toExpPython s) `shouldBe` res|]
      case exampleStr of
        Nothing -> qexp
        Just e ->
          if res == e
            then qexp
            else [|expectationFailure $ "Provided result `" ++ e ++ "` does not match the python result `" ++ res ++ "`"|]

-- * Exported

-- | `checkExample formatString result` checks if, once formated,
--     `formatString` is equal to result. It also checks that the result is
--     the same as the one provided by python.
checkExample :: String -> String -> Q Exp
checkExample s res = pyCheck s (Just res)

-- | `checkExampleDiff formatString result` checks if, once formated,
--     `formatString` is equal to result. It does not check the result
--     against the python implementation
checkExampleDiff :: String -> String -> Q Exp
checkExampleDiff s res = [|$(toExpPython s) `shouldBe` res|]

-- | `check formatString` checks only with the python implementation
check :: String -> Q Exp
check s = pyCheck s Nothing
