module RangeTest (main) where

import Control.Monad
import Data.Int
import Data.Word
import Foreign.C.Types
import System.Random

-- Take many measurements and record the max/min/average random values.
approxBounds ::
  (RandomGen g, Random a, Ord a, Num a) =>
  (g -> (a, g)) -> Int -> a -> (a, a) -> g -> ((a, a, a), g)
-- Here we do a little hack to essentially pass in the type in the last argument:
approxBounds nxt iters unused (explo, exphi) initrng =
  if False
    then ((unused, unused, unused), undefined)
    --   else loop initrng iters 100 (-100) 0 -- Oops, can't use minBound/maxBound here.
    else loop initrng iters exphi explo 0
  where
    loop rng 0 mn mx sum' = ((mn, mx, sum'), rng)
    loop rng n mn mx sum' =
      case nxt rng of
        (x, rng') -> loop rng' (n - 1) (min x mn) (max x mx) (x + sum')

-- We check that:
--     (1) all generated numbers are in bounds
--     (2) we get "close" to the bounds
-- The with (2) is that we do enough trials to ensure that we can at
-- least hit the 90% mark.
checkBounds ::
  (Real a, Show a, Ord a) =>
  String -> (Bool, a, a) -> ((a, a) -> StdGen -> ((a, a, t), StdGen)) -> IO ()
checkBounds msg (exclusive, lo, hi) fun = do
  -- (lo,hi) is [inclusive,exclusive)
  putStr $ msg ++ ":  "
  (mn, mx, _) <- getStdRandom (fun (lo, hi))
  when (mn < lo) $ error $ "broke lower bound: " ++ show mn
  when (mx > hi) $ error $ "broke upper bound: " ++ show mx
  when (exclusive && mx >= hi) $ error $ "hit upper bound: " ++ show mx

  let epsilon = 0.1 * (toRational hi - toRational lo)

  when (toRational (hi - mx) > epsilon) $
    error $
      "didn't get close enough to upper bound: " ++ show mx
  when (toRational (mn - lo) > epsilon) $
    error $
      "didn't get close enough to lower bound: " ++ show mn
  putStrLn "Passed"

boundedRange :: (Num a, Bounded a) => (Bool, a, a)
boundedRange = (False, minBound, maxBound)

trials :: Int
trials = 5000

-- Keep in mind here that on some architectures (e.g. ARM) CChar, CWchar, and CSigAtomic
-- are unsigned
main :: IO ()
main =
  do
    checkBounds "Int" boundedRange (approxBounds random trials (undefined :: Int))
    checkBounds
      "Integer"
      (False, fromIntegral (minBound :: Int), fromIntegral (maxBound :: Int))
      (approxBounds random trials (undefined :: Integer))
    checkBounds "Int8" boundedRange (approxBounds random trials (undefined :: Int8))
    checkBounds "Int16" boundedRange (approxBounds random trials (undefined :: Int16))
    checkBounds "Int32" boundedRange (approxBounds random trials (undefined :: Int32))
    checkBounds "Int64" boundedRange (approxBounds random trials (undefined :: Int64))
    checkBounds "Word" boundedRange (approxBounds random trials (undefined :: Word))
    checkBounds "Word8" boundedRange (approxBounds random trials (undefined :: Word8))
    checkBounds "Word16" boundedRange (approxBounds random trials (undefined :: Word16))
    checkBounds "Word32" boundedRange (approxBounds random trials (undefined :: Word32))
    checkBounds "Word64" boundedRange (approxBounds random trials (undefined :: Word64))
    checkBounds "Double" (False, 0.0, 1.0) (approxBounds random trials (undefined :: Double))
    checkBounds "Float" (False, 0.0, 1.0) (approxBounds random trials (undefined :: Float))

    checkBounds "CChar" boundedRange (approxBounds random trials (undefined :: CChar))
    checkBounds "CSChar" boundedRange (approxBounds random trials (undefined :: CSChar))
    checkBounds "CUChar" boundedRange (approxBounds random trials (undefined :: CUChar))
    checkBounds "CShort" boundedRange (approxBounds random trials (undefined :: CShort))
    checkBounds "CUShort" boundedRange (approxBounds random trials (undefined :: CUShort))
    checkBounds "CInt" boundedRange (approxBounds random trials (undefined :: CInt))
    checkBounds "CUInt" boundedRange (approxBounds random trials (undefined :: CUInt))
    checkBounds "CLong" boundedRange (approxBounds random trials (undefined :: CLong))
    checkBounds "CULong" boundedRange (approxBounds random trials (undefined :: CULong))
    checkBounds "CPtrdiff" boundedRange (approxBounds random trials (undefined :: CPtrdiff))
    checkBounds "CSize" boundedRange (approxBounds random trials (undefined :: CSize))
    checkBounds "CWchar" boundedRange (approxBounds random trials (undefined :: CWchar))
    checkBounds "CSigAtomic" boundedRange (approxBounds random trials (undefined :: CSigAtomic))
    checkBounds "CLLong" boundedRange (approxBounds random trials (undefined :: CLLong))
    checkBounds "CULLong" boundedRange (approxBounds random trials (undefined :: CULLong))
    checkBounds "CIntPtr" boundedRange (approxBounds random trials (undefined :: CIntPtr))
    checkBounds "CUIntPtr" boundedRange (approxBounds random trials (undefined :: CUIntPtr))
    checkBounds "CIntMax" boundedRange (approxBounds random trials (undefined :: CIntMax))
    checkBounds "CUIntMax" boundedRange (approxBounds random trials (undefined :: CUIntMax))

    -- Then check all the range-restricted versions:
    checkBounds
      "Int R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: Int))
    checkBounds
      "Integer R"
      (False, -100000000000000000000, 100000000000000000000)
      (approxBounds (randomR (-100000000000000000000, 100000000000000000000)) trials (undefined :: Integer))
    checkBounds
      "Int8 R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: Int8))
    checkBounds
      "Int8 Rsmall"
      (False, -50, 50)
      (approxBounds (randomR (-50, 50)) trials (undefined :: Int8))
    checkBounds
      "Int8 Rmini"
      (False, 3, 4)
      (approxBounds (randomR (3, 4)) trials (undefined :: Int8))
    checkBounds
      "Int8 Rtrivial"
      (False, 3, 3)
      (approxBounds (randomR (3, 3)) trials (undefined :: Int8))

    checkBounds
      "Int16 R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: Int16))
    checkBounds
      "Int32 R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: Int32))
    checkBounds
      "Int64 R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: Int64))
    checkBounds
      "Word R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: Word))
    checkBounds
      "Word8 R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: Word8))
    checkBounds
      "Word16 R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: Word16))
    checkBounds
      "Word32 R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: Word32))
    checkBounds
      "Word64 R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: Word64))
    checkBounds
      "Double R"
      (False, 10.0, 77.0)
      (approxBounds (randomR (10, 77)) trials (undefined :: Double))
    checkBounds
      "Float R"
      (False, 10.0, 77.0)
      (approxBounds (randomR (10, 77)) trials (undefined :: Float))

    checkBounds
      "CChar R"
      (False, 0, 100)
      (approxBounds (randomR (0, 100)) trials (undefined :: CChar))
    checkBounds
      "CSChar R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: CSChar))
    checkBounds
      "CUChar R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: CUChar))
    checkBounds
      "CShort R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: CShort))
    checkBounds
      "CUShort R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: CUShort))
    checkBounds
      "CInt R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: CInt))
    checkBounds
      "CUInt R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: CUInt))
    checkBounds
      "CLong R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: CLong))
    checkBounds
      "CULong R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: CULong))
    checkBounds
      "CPtrdiff R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: CPtrdiff))
    checkBounds
      "CSize R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: CSize))
    checkBounds
      "CWchar R"
      (False, 0, 100)
      (approxBounds (randomR (0, 100)) trials (undefined :: CWchar))
    checkBounds
      "CSigAtomic R"
      (False, 0, 100)
      (approxBounds (randomR (0, 100)) trials (undefined :: CSigAtomic))
    checkBounds
      "CLLong R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: CLLong))
    checkBounds
      "CULLong R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: CULLong))
    checkBounds
      "CIntPtr R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: CIntPtr))
    checkBounds
      "CUIntPtr R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: CUIntPtr))
    checkBounds
      "CIntMax R"
      (False, -100, 100)
      (approxBounds (randomR (-100, 100)) trials (undefined :: CIntMax))
    checkBounds
      "CUIntMax R"
      (False, 0, 200)
      (approxBounds (randomR (0, 200)) trials (undefined :: CUIntMax))

-- Untested:
-- instance Random Char where
-- instance Random Bool where
-- instance Random Integer where
