-- |
-- Module      : Test.LeanCheck.IO
-- Copyright   : (c) 2015-2017 Rudy Matela
-- License     : 3-Clause BSD  (see the file LICENSE)
-- Maintainer  : Rudy Matela <rudy@matela.com.br>
--
-- This module is part of LeanCheck,
-- a simple enumerative property-based testing library.
--
-- QuickCheck-like interface to "Test.LeanCheck"
{-# LANGUAGE CPP #-}
module Test.LeanCheck.IO
  ( check
  , checkFor
  , checkResult
  , checkResultFor
  )
where

#if __GLASGOW_HASKELL__ <= 704
import Prelude hiding (catch)
#endif

import Test.LeanCheck.Core
import Data.Maybe (listToMaybe)
import Data.List (find)
import Control.Exception (SomeException, catch, evaluate)

-- | Checks a property printing results on 'stdout'
--
-- > > check $ \xs -> sort (sort xs) == sort (xs::[Int])
-- > +++ OK, passed 200 tests.
-- > > check $ \xs ys -> xs `union` ys == ys `union` (xs::[Int])
-- > *** Failed! Falsifiable (after 4 tests):
-- > [] [0,0]
check :: Testable a => a -> IO ()
check p = checkResult p >> return ()

-- | Check a property for a given number of tests
--   printing results on 'stdout'
checkFor :: Testable a => Int -> a -> IO ()
checkFor n p = checkResultFor n p >> return ()

-- | Check a property
--   printing results on 'stdout' and
--   returning 'True' on success.
--
-- There is no option to silence this function:
-- for silence, you should use 'Test.LeanCheck.holds'.
checkResult :: Testable a => a -> IO Bool
checkResult p = checkResultFor 200 p

-- | Check a property for a given number of tests
--   printing results on 'stdout' and
--   returning 'True' on success.
--
-- There is no option to silence this function:
-- for silence, you should use 'Test.LeanCheck.holds'.
checkResultFor :: Testable a => Int -> a -> IO Bool
checkResultFor n p = do
  r <- resultIO n p
  putStrLn . showResult n $ r
  return (isOK r)
  where isOK (OK _) = True
        isOK _      = False

data Result = OK        Int
            | Falsified Int [String]
            | Exception Int [String] String
  deriving (Eq, Show)

resultsIO :: Testable a => Int -> a -> IO [Result]
resultsIO n = sequence . zipWith torio [1..] . take n . results
  where
    tor i (_,True) = OK i
    tor i (as,False) = Falsified i as
    torio i r@(as,_) = evaluate (tor i r)
       `catch` \e -> let _ = e :: SomeException
                     in return (Exception i as (show e))

resultIO :: Testable a => Int -> a -> IO Result
resultIO n p = do
  rs <- resultsIO n p
  return . maybe (last rs) id
         $ find isFailure rs
  where isFailure (OK _) = False
        isFailure _      = True

showResult :: Int -> Result -> String
showResult m (OK n)             = "+++ OK, passed " ++ show n ++ " tests"
                               ++ takeWhile (\_ -> n < m) " (exhausted)" ++ "."
showResult m (Falsified i ce)   = "*** Failed! Falsifiable (after "
                               ++ show i ++ " tests):\n" ++ unwords ce
showResult m (Exception i ce e) = "*** Failed! Exception '" ++ e ++ "' (after "
                               ++ show i ++ " tests):\n" ++ unwords ce