{-# LANGUAGE TupleSections, MultiWayIf, DeriveDataTypeable #-}
-- | The Interpreter module is responible for invoking the Hint interpreter to
-- evaluate mutants.
module Test.MuCheck.Interpreter (evaluateMutants, evalMethod, evalMutant, evalTest, summarizeResults, MutantSummary(..)) where

import qualified Language.Haskell.Interpreter as I
import Control.Monad.Trans (liftIO)
import Control.Monad (liftM)
import Data.Typeable
import Test.MuCheck.Utils.Print (catchOutput)
import Data.Either (partitionEithers)
import System.Directory (createDirectoryIfMissing)
import System.Environment (withArgs)

import Test.MuCheck.TestAdapter
import Test.MuCheck.Utils.Common
import Test.MuCheck.AnalysisSummary


-- | Data type to hold results of a single test execution
data MutantSummary = MSumError Mutant String [Summary]         -- ^ Capture the error if one occured
                   | MSumAlive Mutant [Summary]                -- ^ The mutant was alive
                   | MSumKilled Mutant [Summary]               -- ^ The mutant was kileld
                   | MSumOther Mutant [Summary]                -- ^ Undetermined - we will treat it as killed as it is not a success.
                   deriving (Show, Typeable)

-- | Given the list of tests suites to check, run the test suite on mutants.
evaluateMutants :: (Summarizable a, Show a) =>
    (Mutant -> TestStr -> InterpreterOutput a -> Summary)           -- ^ The summary function
 -> [Mutant]                                                        -- ^ The mutants to be evaluated
 -> [TestStr]                                                       -- ^ The tests to be used by mutation analysis
 -> IO (MAnalysisSummary, [MutantSummary])                          -- ^ Returns a tuple of full run summary and individual mutant summary
evaluateMutants testSummaryFn mutants tests = do
  results <- mapM (evalMutant tests) mutants -- [InterpreterOutput t]
  let singleTestSummaries = map (summarizeResults testSummaryFn tests) $ zip mutants results
      ma  = fullSummary tests results
  return (ma, singleTestSummaries)

-- | The `summarizeResults` function evaluates the results of a test run
-- using the supplied `isSuccess` and `testSummaryFn` functions from the adapters
summarizeResults :: Summarizable a =>
     (Mutant -> TestStr -> InterpreterOutput a -> Summary)        -- ^ The summary function
  -> [TestStr]                                                    -- ^ Tests we used to run analysis
  -> (Mutant, [InterpreterOutput a])                              -- ^ The mutant and its corresponding output of test runs.
  -> MutantSummary                                                -- ^ Returns a summary of the run for the mutant
summarizeResults testSummaryFn tests (mutant, ioresults) = case last results of -- the last result should indicate status because we dont run if there is error.
  Left err -> MSumError mutant (show err) logS
  Right out -> myresult out
  where results = map _io ioresults
        myresult out | isSuccess out = MSumAlive mutant logS
                     | isFailure out = MSumKilled mutant logS
                     | otherwise     = MSumOther mutant logS
        logS :: [Summary]
        logS = zipWith (testSummaryFn mutant) tests ioresults

-- | Run all tests on a mutant
evalMutant :: (Typeable t, Summarizable t) =>
    [TestStr]                                                     -- ^ The tests to be used
  -> Mutant                                                       -- ^ Mutant being tested
  -> IO [InterpreterOutput t]                                     -- ^ Returns the result of test runs
evalMutant tests mutant = do
  -- Hint does not provide us a way to evaluate the module without
  -- writing it to disk first, so we go for this hack.
  -- We write the temporary file to disk, run interpreter on it, get
  -- the result (we dont remove the file now, but can be added)
  createDirectoryIfMissing True ".mutants"
  let mutantFile = ".mutants/" ++ hash mutant ++ ".hs"
  writeFile mutantFile mutant
  let logF = mutantFile ++ ".log"
  stopFast (evalTest mutantFile logF) tests

-- | Stop mutant runs at the first sign of problems (invalid mutants or test
-- failure).
stopFast :: (Typeable t, Summarizable t) =>
     (String -> IO (InterpreterOutput t))  -- ^ The function that given a test, runs it, and returns the result
  -> [TestStr]                             -- ^ The tests to be run
  -> IO [InterpreterOutput t]              -- ^ Returns the output of all tests. If there is an error, then it will be at the last test.
stopFast _ [] = return []
stopFast fn (x:xs) = do
  v <- fn x
  case _io v of
    Left _ -> return [v] -- load error
    Right out -> if isSuccess out
      then liftM (v :) $ stopFast fn xs
      else return [v] -- test failed (mutant detected)

-- | Run one single test on a mutant
evalTest :: (Typeable a, Summarizable a) =>
    String                                 -- ^ The mutant _file_ that we have to evaluate (_not_ the content)
 -> String                                 -- ^ The file where we will write the stdout and stderr during the run. 
 -> TestStr                                -- ^ The test to be run
 -> IO (InterpreterOutput a)               -- ^ Returns the output of given test run
evalTest mutantFile logF test = do
  val <- withArgs [] $ catchOutput logF $ I.runInterpreter (evalMethod mutantFile test)
  return Io {_io = val, _ioLog = logF}

-- | Given the filename, modulename, test to evaluate, evaluate, and return result as a pair.
--
-- > t = I.runInterpreter (evalMethod
-- >        "Examples/QuickCheckTest.hs"
-- >        "quickCheckResult idEmp")
evalMethod :: (I.MonadInterpreter m, Typeable t) =>
     String                               -- ^ The mutant _file_ to load
  -> TestStr                              -- ^ The test to be run
  -> m t                                  -- ^ Returns the monadic computation to be run by I.runInterpreter
evalMethod fileName evalStr = do
  I.loadModules [fileName]
  ms <- I.getLoadedModules
  I.setTopLevelModules ms
  I.interpret evalStr (I.as :: (Typeable a => IO a)) >>= liftIO


-- | Summarize the entire run. Passed results are per mutant
fullSummary :: (Show b, Summarizable b) =>
     [TestStr]                              -- ^ The list of tests we used
  -> [[InterpreterOutput b]]                -- ^ The test ouput (per mutant, (per test))
  -> MAnalysisSummary                       -- ^ Returns the full summary of the run
fullSummary _tests results = MAnalysisSummary {
  _maNumMutants = length results,
  _maAlive = length alive,
  _maKilled = length fails,
  _maErrors= length errors}
  where res = map (map _io) results
        lasts = map last res -- get the last test runs
        (errors, completed) = partitionEithers lasts
        fails = filter isFailure completed -- look if others failed or not
        alive = filter isSuccess completed