{-# LANGUAGE DeriveDataTypeable #-}
module Data.ULID.Random (
    ULIDRandom,
    mkCryptoULIDRandom,
    mkULIDRandom,
    getULIDRandom
) where

import           Control.DeepSeq
import           Control.Monad
import           Crypto.Random
import           Data.Binary
import           Data.Binary.Roll
import qualified Data.ByteString     as BS
import           Data.Data
import           Data.Word
import           System.Random


import qualified Data.ULID.Crockford as CR


newtype ULIDRandom = ULIDRandom BS.ByteString
    deriving (Eq, Typeable, Data)

numBytes = 10 -- 80 bits

-- | Generate a ULID Random based on a cryptographically secure random number generator.
-- | see: https://hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html
mkCryptoULIDRandom :: CryptoRandomGen g => g -> Either GenError (ULIDRandom, g)
mkCryptoULIDRandom g = do
    (b, g2) <- genBytes numBytes g
    return (ULIDRandom b, g2)

-- | Generate a ULID Random based on a standard random number generator.
-- | see: https://hackage.haskell.org/package/random-1.1/docs/System-Random.html
mkULIDRandom :: RandomGen g => g -> (ULIDRandom, g)
mkULIDRandom g = let
    (g1, g2) = split g
    genbytes = (BS.pack) . take numBytes . randoms
    in (ULIDRandom $ genbytes g, g2)

-- | Generate a ULID Random based on the global random number generator.
getULIDRandom :: IO ULIDRandom
getULIDRandom = fst <$> mkULIDRandom <$> newStdGen -- Note: the call to newStdGen splits the generator, so this is safe to call multiple times

instance Show ULIDRandom where
    show (ULIDRandom r) =  (CR.encode) 16.roll.(BS.unpack) $ r

instance Read ULIDRandom where
    readsPrec _ = map (\(c,r)->(ULIDRandom $ (BS.pack) $ unroll numBytes c, r)) . (CR.decode) 16

instance Binary ULIDRandom where
    put (ULIDRandom r) = mapM_ put (BS.unpack $ r)
    get = ULIDRandom <$> (BS.pack) <$> replicateM numBytes get

instance NFData ULIDRandom where
    rnf (ULIDRandom r) = rnf r