{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
module Verify ( module Verify
              , module Test.QuickCheck
              , succeeded
              , failed
              , result
              , Result(..)
              , monadicIO
              , liftIO
              , liftIOResult
              , liftResult
              , liftBool
              )
    where

import Test.QuickCheck hiding ( Result(..) )
import qualified Test.QuickCheck as QC
import Test.QuickCheck.Property 
import Test.QuickCheck.Monadic ( monadicIO ) 

import qualified Codec.Binary.UTF8.String as UTF8

import Control.Applicative
import Control.Monad.State.Strict
import Control.Monad.Trans ( liftIO )

import Data.IORef
import Data.Word

import Numeric ( showHex )

import System.IO

type Test = StateT TestState IO

instance Applicative Test where
    pure = return
    ( <*> ) = ap

data TestState = TestState
    { results_ref :: IORef [QC.Result]
    }

run_test :: Test () -> IO ()
run_test t = do
    s <- newIORef [] >>= return . TestState
    s' <- runStateT t s >>= return . snd
    results <- readIORef $ results_ref s'
    let fail_results = [ fail_result | fail_result@(QC.Failure {}) <- results ]
    case fail_results of
        [] -> putStrLn "state: PASS"
        rs  -> do
            putStrLn "state: FAIL"
            putStrLn $ "fail_count: " ++ show (length rs)

verify :: Testable prop => String -> prop -> Test QC.Result
verify prop_name prop = do
    liftIO $ putStrLn $ "verify " ++ prop_name
    get >>= \s -> do
        r <- liftIO $ quickCheckResult prop 
        liftIO $ modifyIORef (results_ref s) (\rs -> r : rs)
        return r

data SingleColumnChar = SingleColumnChar Char
    deriving (Show, Eq)

instance Arbitrary SingleColumnChar where
    arbitrary = elements $ map SingleColumnChar [toEnum 0x21 .. toEnum 0x7E]

data DoubleColumnChar = DoubleColumnChar Char
    deriving (Eq)

instance Show DoubleColumnChar where
    show (DoubleColumnChar c) = "(0x" ++ showHex (fromEnum c) "" ++ ") ->" ++ UTF8.encodeString [c]

instance Arbitrary DoubleColumnChar where
    arbitrary = elements $ map DoubleColumnChar $ 
           [ toEnum 0x3040 .. toEnum 0x3098 ] 
        ++ [ toEnum 0x309B .. toEnum 0xA4CF]

instance Arbitrary Word where
    arbitrary = choose (0, 1024) >>= return . toEnum
