{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
module Foundation.Check.Gen
    ( Gen
    , runGen
    , GenParams(..)
    , GenRng
    , genRng
    , genWithRng
    , genWithParams
    ) where

import           Basement.Imports
import           Foundation.Collection
import           Foundation.Random
import qualified Foundation.Random.XorShift as XorShift
import           Foundation.String
import           Foundation.Numerical
import           Foundation.Hashing.SipHash
import           Foundation.Hashing.Hasher

data GenParams = GenParams
    { genMaxSizeIntegral :: Word -- maximum number of bytes
    , genMaxSizeArray    :: Word -- number of elements, as placeholder
    , genMaxSizeString   :: Word -- maximum number of chars
    }

newtype GenRng = GenRng XorShift.State

type GenSeed = Word64

genRng :: GenSeed -> [String] -> (Word64 -> GenRng)
genRng seed groups = \iteration -> GenRng $ XorShift.initialize rngSeed (rngSeed * iteration)
  where
    (SipHash rngSeed) = hashEnd $ hashMixBytes hashData iHashState
    hashData = toBytes UTF8 $ intercalate "::" groups
    iHashState :: Sip1_3
    iHashState = hashNewParam (SipKey seed 0x12345678)

genGenerator :: GenRng -> (GenRng, GenRng)
genGenerator (GenRng rng) =
    let (newSeed1, rngNext) = randomGenerateWord64 rng
        (newSeed2, rngNext') = randomGenerateWord64 rngNext
     in (GenRng $ XorShift.initialize newSeed1 newSeed2, GenRng rngNext')

-- | Generator monad
newtype Gen a = Gen { runGen :: GenRng -> GenParams -> a }

instance Functor Gen where
    fmap f g = Gen (\rng params -> f (runGen g rng params))

instance Applicative Gen where
    pure a     = Gen (\_ _ -> a)
    fab <*> fa = Gen $ \rng params ->
        let (r1,r2) = genGenerator rng
            ab      = runGen fab r1 params
            a       = runGen fa r2 params
         in ab a

instance Monad Gen where
    return  = pure
    ma >>= mb = Gen $ \rng params ->
            let (r1,r2) = genGenerator rng
                a       = runGen ma r1 params
             in runGen (mb a) r2 params

genWithRng :: forall a . (forall randomly . MonadRandom randomly => randomly a) -> Gen a
genWithRng f = Gen $ \(GenRng rng) _ ->
    let (a, _) = withRandomGenerator rng f in a

genWithParams :: (GenParams -> Gen a) -> Gen a
genWithParams f = Gen $ \rng params -> runGen (f params) rng params
