{-# LANGUAGE ForeignFunctionInterface #-}
module Test.HTestU
  ( TestResult
  , runBattery
  , toResults
  , runBatteryToResults
  , c_smallCrush
  , c_crush
  , c_bigCrush
  ) where

import System.Random (RandomGen)

import Foreign.Marshal.Alloc (free)
import Foreign.Marshal.Array (peekArray)
import Foreign.Storable (peek)
import Foreign.Ptr (freeHaskellFunPtr)

import System.IO.Unsafe (unsafePerformIO)

import Test.HTestU.Wrapping (Battery, genToWrappedCallback, c_createGenerator, c_deleteGenerator)
import Test.HTestU.BatteryResult (BatteryResultStruct(..))
import Test.HTestU.Streaming (RandomStream)

-- | Type for presenting a result of a test, instead of a p-value
data TestResult = Fail | Suspect | OK deriving (Eq, Show)

-- | P-value serving as a border for a failure in TestU01, greater => failure or suspicious value
failurePvalue :: Double
failurePvalue = 0.0000000001 -- 10^(-10)

-- | P-value serving as a border for a suspicious value in TestU01, greater => failure
suspectPvalue :: Double
suspectPvalue = 0.001 -- 10^(-3)

-- | Prettifying a p-value to a test result
pValueToResult :: Double -> TestResult
pValueToResult pvalue | pvalue < failurePvalue || pvalue > 1.0 - failurePvalue = Fail
                      | pvalue < suspectPvalue || pvalue > 1.0 - suspectPvalue = Suspect
                      | otherwise = OK

-- | Prettifying a list of result p-values
toResults :: [Double] -> [TestResult]
toResults = map pValueToResult

-- | Run a given battery and present pretty results
runBatteryToResults :: RandomGen g => (g -> RandomStream) -> g -> Battery -> [TestResult]
runBatteryToResults = ((toResults .) .) . runBattery

-- | Run a given battery and present resulting p-values
-- NOTE: returns [Double] instead of IO [Double], because it is transparent (for the same gen result should be the same)
runBattery :: RandomGen g => (g -> RandomStream) -> g -> Battery -> [Double]
runBattery streamer gen crush = unsafePerformIO $ do
  callback <- genToWrappedCallback streamer gen
  generatorPtr <- c_createGenerator callback
  batteryResult <- crush generatorPtr
  (BR ps tests) <- peek batteryResult
  c_deleteGenerator generatorPtr
  freeHaskellFunPtr callback
  free batteryResult
  cDoublePvalues <- peekArray (fromIntegral tests) ps
  return $ map realToFrac cDoublePvalues
{-# NOINLINE runBattery #-}

-- | Runs SmallCrush battery, 10 tests
foreign import ccall safe "bbattery_SmallCrush" c_smallCrush :: Battery

-- | Runs Crush battery, 96 tests
foreign import ccall safe "bbattery_Crush" c_crush :: Battery

-- | Runs BigCrush Battery, 106 tests
foreign import ccall safe "bbattery_BigCrush" c_bigCrush :: Battery