module Tester.Suite(Result, runTests, Suite(..), TestResult(..), unsafeRunTests) where

import Control.Arrow((>>>))

import Data.Bifoldable(bifoldMap)
import Data.List.NonEmpty(NonEmpty)
import Data.Map((!), Map)
import Data.Monoid(Monoid(mappend, mempty))
import Data.Validation(Validation)

import Data.Set as Set

import System.IO.Unsafe(unsafePerformIO)

import Tester.Dialect(FlagCells)
import Tester.RunSettings(cellsToSettings, testNums)

a |> f = f a

type Result f s = Validation (NonEmpty f) s

data TestResult
  = TestSuccess
  | TestFailure { msg :: String } deriving (Eq, Show)

data Suite a b c
  = Suite {
    testMap    :: Map Int a,
    runTest    :: a -> Result b c,
    failsToStr :: NonEmpty b -> String,
    succToStr  :: c -> String
  }

instance Monoid TestResult where
  mempty = TestSuccess
  mappend   (TestFailure m1)   (TestFailure m2) = TestFailure $ m1 `mappend` m2
  mappend x@(TestFailure _)  _                  = x
  mappend _                  x@(TestFailure _)  = x
  mappend _                  _                  = mempty

runTests :: FlagCells -> (Suite a b c) -> [TestResult]
runTests c s@(Suite _ _ failsToStr _) = fmap (resultToTR failsToStr) $ generateResults c s

unsafeRunTests :: FlagCells -> (Suite a b c) -> [TestResult]
unsafeRunTests c s@(Suite _ _ failsToStr succToStr) = seq (unsafePerformIO evilIO) testResults
  where
    results     = generateResults c s
    testResults = fmap (resultToTR failsToStr) results
    strs        = fmap (bifoldMap failsToStr succToStr) results
    evilIO      = mapM_ putStrLn strs

generateResults :: FlagCells -> (Suite a b c) -> [Result b c]
generateResults cells (Suite testMap runTest _ _) =
  cells |> (cellsToSettings >>> testNums >>> Set.toList >>> (fmap $ (testMap!) >>> runTest))

resultToTR :: (NonEmpty a -> String)-> Result a b -> TestResult
resultToTR fToStr = bifoldMap (fToStr >>> TestFailure) $ const TestSuccess