module QuickCheck where

import Smart
import ActiveHs.Base (WrapData2 (WrapData2), TestCase (TestCase))
import Lang
import Result
import Logger
import Qualify (qualify)
import Hash
import Specialize (specialize)

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

import Data.Char (isLower)
import Data.List (intercalate)
import Control.Monad (join,forM,void)
import Control.Monad.Trans (liftIO)
import Control.Concurrent.MVar (newMVar, swapMVar, takeMVar)

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

quickCheck
    :: String
    -> Hash
    -> Language
    -> TaskChan
    -> FilePath
    -> String
    -> [String]
    -> [([String],String)]      -- test cases
    -> IO Result
quickCheck qualifier m5 lang ch fn ft funnames testcases = do
    logStrMsg 3 (logger ch) $ "test cases: " ++ show testcases
    logStrMsg 3 (logger ch) $ "names to be qualified: " ++ show funnames

    case allRight $ map (qualify qualifier funnames . snd) testcases of
        Left err -> do
            logStrMsg 3 (logger ch) $ "Error in qualification: " ++ err
            return $ Error True err
        Right s_ -> do
            logStrMsg 3 (logger ch) $ "Qualified expressions: " ++ show s_
            interp False m5 lang ch fn "" $ Just $ \a ->
                do  ss <- forM (testcases `zip` s_) $ \((v,s1),s2) -> do
                      ts1 <- typeOf s1
                      ts2 <- typeOf s2
                      let [x1,x2] = map fixType [(s1,ts1),(s2,ts2)]
                      return $ mkTestCase (v,x1,x2)
                    let ts = "[" ++ intercalate ", " ss ++ "]"
                    liftIO $ logStrMsg 3 (logger ch) $ "Test cases: " ++ ts
                    liftIO $ logStrMsg 3 (logger ch) "Before interpretation"
                    m <- interpret ts (as :: [TestCase])
                    liftIO $ logStrMsg 3 (logger ch) "After interpretation"
                    liftIO $ qcs lang (logger ch) m

  where
    fixType (s,t) =
      case (specialize t) of
        Right (st,_) | t /= st -> unwords [s, "::", st]
        _ -> s

    mkTestCase (vars, s, s2)  
        = "TestCase (\\qcinner " 
        ++ unwords ["(" ++ v ++ ")" | v<-vars] 
        ++ " -> qcinner (" ++ showTr vars s ++ ", " ++ parens s ++ ", " ++ parens s2 ++ "))"

    showTr vars s = "unwords [" ++ intercalate ", " (map g $ words s) ++ "]"  -- !!!
     where

        vs = concatMap f vars

        f = filter (isLower . head) . words

        g x | x `elem` vs = {- "\"(\" ++ -} " show " ++ x -- ++ " ++ \")\""
        g x = show x

qcs :: Language -> Logger -> [TestCase] -> IO Result
qcs lang log = foldM (Message (translate lang "All test cases are completed.") Nothing) (qc lang log)

-- test = qc $ TestCase $ \f (QCNat x) (QCInt y) -> f (show x ++ " + " ++ show y, min 10 (x + y), x + y)
-- test' = qc $ TestCase $ \f (QCInt x) -> f ("sqr " ++ show x, min 10 (x*x), x*x)

qc :: Language -> Logger -> TestCase -> IO (Maybe Result)
qc lang log (TestCase p) = do
    v <- newMVar Nothing
    let ff (s, x, y)
            = QC.morallyDubiousIOProperty $ do
                r <- res
                if hasError [r]
                  then do 
                    void $ swapMVar v $ Just $ ShowFailedTestCase s r
                    return QC.failed
                  else return QC.succeeded
          where
            res :: IO Result
            res = do
                logStrMsg 4 log $ "compare: " ++ s
                compareClearGen lang "noId" $ WrapData2 x y
    _ <- quickCheckWithResult (stdArgs { chatty = False }) $ {- QC.noShrinking $ -} p ff
    takeMVar v

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

allRight :: [Either a b] -> Either a [b]
allRight x = case [s | Left s<-x] of
    (y:_) -> Left y
    []    -> Right [s | Right s<-x]

foldM :: Monad m => r -> (a -> m (Maybe r)) -> [a] -> m r
foldM end _ [] = return end
foldM end f (t:ts) = do
    x <- f t
    case x of
        Just r -> return r
        Nothing  -> foldM end f ts


