module Test.DocTest.Driver (
   T,
   printLine,
   printPrefix,
   Count(..),
   run,
   runWith,
   example,
   property,
   ) where

import qualified Test.DocTest.Base as DocTest
import qualified Test.QuickCheck as QC

import System.Exit (exitFailure)

import Text.Printf (printf)

import qualified Control.Monad.Trans.Writer.Strict as MW
import qualified Control.Monad.Trans.Reader as MR
import qualified Control.Monad.Trans.Class as MT
import Control.Monad.IO.Class (liftIO)
import Control.Monad (when, void)

import Data.Monoid (Monoid(mempty,mappend))
import Data.Semigroup (Semigroup((<>)))


type T = MR.ReaderT QC.Args (MW.WriterT Count IO)

data Count = Count {numTotal, numFailures :: !Int}

instance Semigroup Count where
   Count t0 f0 <> Count t1 f1 = Count (t0+t1) (f0+f1)

instance Monoid Count where
   mempty = Count 0 0
   mappend = (<>)


printLine :: String -> T ()
printLine = liftIO . putStrLn

printPrefix :: String -> T ()
printPrefix = liftIO . putStr


run :: T () -> IO ()
run = runWith QC.stdArgs

runWith :: QC.Args -> T () -> IO ()
runWith args act = do
   count <- MW.execWriterT $ MR.runReaderT act args
   putStrLn ""
   void $ printf "Total: %d\n" $ numTotal count
   void $ printf "Failures: %d\n" $ numFailures count
   when (numFailures count > 0) exitFailure


tell :: Count -> T ()
tell = MT.lift . MW.tell

example :: (Show a) => a -> DocTest.ExpectedResult -> T ()
example actual expected = do
   tell $ Count 1 0
   case DocTest.checkResult expected (lines $ show actual) of
      DocTest.Equal -> printLine "passed"
      DocTest.NotEqual ls ->
         printPrefix (unlines $ "*** Failed!":ls) >> tell (Count 0 1)

property :: (QC.Testable prop) => prop -> T ()
property prop = do
   tell $ Count 1 0
   args <- MR.ask
   result <- liftIO $ QC.quickCheckWithResult args prop
   when (not $ QC.isSuccess result) $ tell (Count 0 1)