{- Test By Convention: test output renderers.
 - Copyright   :  (C)opyright 2009-2011 {mwotton, peteg42} at gmail dot com
 - License     :  BSD3
 -}
module Test.TBC.Renderers
    ( tap
    , quiet
    ) where

-------------------------------------------------------------------
-- Dependencies.
-------------------------------------------------------------------

import System.Exit ( ExitCode(ExitSuccess, ExitFailure) )
import Test.TBC.Core ( Renderer, RenderFns(..), Result(..), Test(..)
                     , info )

-------------------------------------------------------------------
-- TAP renderer.
-------------------------------------------------------------------

-- FIXME what do all these fields mean? Some are rubbery.
data TapState
    = TapState
      { tsRun :: !Int
      , tsPassed :: !Int -- ^ Number of tests passed
      , tsToDo :: !Int -- ^ Number of tests run with result 'TestResultToDo'
      , tsTestsSkipped :: !Int -- ^ Number of identified tests that got skipped
      , tsTestFilesSkipped :: !Int -- ^ Number of potential test files that got skipped
      , tsCompilationFailures :: !Int -- ^ Number of test files that failed to compile
      }

tapState0 :: TapState
tapState0 = TapState
            { tsRun = 0
            , tsPassed = 0
            , tsToDo = 0
            , tsTestsSkipped = 0
            , tsTestFilesSkipped = 0
            , tsCompilationFailures = 0
            }

success :: TapState -> Bool
success s = tsPassed s == tsRun s && tsCompilationFailures s == 0


-- | A TAP generator (noisy).
tap :: Renderer TapState
tap verbosity =
    RenderFns
    { rInitialState = return tapState0
    , rCompilationFailure = tcf
    , rSkip = tskip
    , rStop = tstop
    , rTest = tt
    , rFinal = tf
    }
  where
    tid i t = show i ++ " - " ++ show (tLocation t) ++ " " ++ tName t

    tcf f ts cout s =
      do mapM_ putStrLn $ (("not ok # compilation failed: " ++ f)
                          : cout )
                            ++ ( "# Tests skipped:"
                               : [ "# " ++ tName t | t <- ts ] )
         return s{ tsCompilationFailures = tsCompilationFailures s + 1 }

    tskip f s =
      do info verbosity $ "Skipping " ++ f
         return s{ tsTestFilesSkipped = tsTestFilesSkipped s + 1 }

    tstop f s =
      do putStrLn $ "Stopping at " ++ f
         return s

    tt t s r =
      case r of
        TestResultSkip ->
          do putStrLn $ "ok " ++ tid i t ++ " # SKIP FIXME is this OK or not?"
             return s{ tsTestsSkipped = tsTestsSkipped s + 1 }
        TestResultToDo ->
          do putStrLn $ "ok " ++ tid i t
             return s{ tsToDo = tsToDo s + 1 }
        TestResultStop ->
          do putStrLn $ "ok " ++ tid i t ++ " # STOP FIXME is this OK or not?"
             return s -- FIXME ????
        TestResultFailure strs ->
          do mapM_ putStrLn $ ("not ok " ++ tid i t)
                              : [ '#':' ':l | l <- strs ]
             return s{ tsRun = tsRun s + 1 }
        TestResultSuccess ->
          do putStrLn $ "ok " ++ tid i t
             return s{ tsRun = tsRun s + 1
                     , tsPassed = tsPassed s + 1 }
      where
        i = tsRun s

    tf s =
      do putStrLn $ "0.." ++ show (tsRun s + tsTestsSkipped s - 1)
         return (if success s then ExitSuccess else ExitFailure 1)

-------------------------------------------------------------------

-- | UNIX style: only reports failures.
quiet :: Renderer TapState
quiet verbosity =
    RenderFns
    { rInitialState = return tapState0
    , rCompilationFailure = tcf
    , rSkip = tskip
    , rStop = tstop
    , rTest = tt
    , rFinal = tf
    }
  where
    tid t = show (tLocation t) ++ " " ++ tName t

    tcf f _ts cout s =
      do putStrLn $ "** Compilation failed: " ++ f
         mapM_ putStrLn cout
         return s{ tsCompilationFailures = tsCompilationFailures s + 1 }

    tskip f s =
      do info verbosity $ "Skipping " ++ f
         return s{ tsTestFilesSkipped = tsTestFilesSkipped s + 1 }

    tstop f s =
      do putStrLn $ "Stopping at " ++ f
         return s

    -- FIXME in a big way
    tt t s r =
      case r of
        TestResultFailure strs ->
          do mapM_ putStrLn $ ("** Test failed: " ++ tid t)
                              : [ '#':' ':l | l <- strs ]
             return s{ tsRun = tsRun s + 1 }
        TestResultSuccess ->
             return s{ tsRun = tsRun s + 1
                     , tsPassed = tsPassed s + 1 }
        TestResultSkip ->
          do putStrLn $ "ok " ++ tid t ++ " # SKIP FIXME is this OK or not?"
             return s{ tsTestsSkipped = tsTestsSkipped s + 1 }
        TestResultToDo ->
          do putStrLn $ "ok " ++ tid t
             return s{ tsToDo = tsToDo s + 1 }
        TestResultStop ->
          do putStrLn $ "ok " ++ tid t ++ " # STOP FIXME is this OK or not?"
             return s -- FIXME ????

    tf s =
      do putStrLn $ "Passed " ++ show (tsPassed s) ++ " / " ++ show (tsRun s)
                       ++ skipped ++ cfail
         return (if success s then ExitSuccess else ExitFailure 1)
      where
        cfail
            | tsCompilationFailures s == 0 = ""
            | otherwise = " (Failed to compile " ++ show (tsCompilationFailures s) ++ ")"

        skipped
            | tsTestsSkipped s == 0 = ""
            | otherwise = " (Skipped " ++ show (tsTestsSkipped s) ++ ")"