content: {-# LANGUAGE ExplicitForAll #-}
  module Faker.Combinators where
  
  import Control.Monad
  import Data.Foldable
  import Data.List (sort)
  import Faker
  import System.Random
  
  -- | Generates a random element in the given inclusive range.
  --
  -- @
  -- λ> item \<- 'generate' $ 'fromRange' (1,10)
  -- λ> item
  -- 2
  -- @
  --
  fromRange :: forall a m. (Monad m, Random a) => (a, a) -> FakeT m a
  fromRange rng =
    FakeT
      (\r ->
         let (x, _) = randomR rng (getRandomGen r)
          in pure x)
  
  -- | Generates a random element over the natural range of `a`.
  --
  -- @
  -- λ> import Data.Word
  -- λ> item :: forall a m. Word8 \<- 'generate' 'pickAny'
  -- λ> item
  -- 57
  -- @
  --
  pickAny :: forall a m. (Monad m, Random a) => FakeT m a
  pickAny =
    FakeT
      (\settings ->
         let (x, _) = random (getRandomGen settings)
          in pure x)
  
  -- | Tries to generate a value that satisfies a predicate.
  --
  -- @
  -- λ> import qualified Faker.Address as AD
  -- λ> item :: forall a m. Text \<- 'generate' $ 'suchThatMaybe' AD.country (\x -> (T.length x > 5))
  -- λ> item
  -- Just Ecuador
  -- @
  --
  suchThatMaybe :: forall a m. Monad m => FakeT m a -> (a -> Bool) -> FakeT m (Maybe a)
  gen `suchThatMaybe` p = do
    x <- gen
    return $
      if p x
        then Just x
        else Nothing
  
  -- | Generates a value that satisfies a predicate.
  --
  -- @
  -- λ> import qualified Faker.Address as AD
  -- λ> item :: forall a m. Text \<- 'generate' $ 'suchThat' AD.country (\\x -> (T.length x > 5))
  -- λ> item
  -- Ecuador
  -- @
  --
  suchThat :: forall a m. Monad m => FakeT m a -> (a -> Bool) -> FakeT m a
  gen `suchThat` p = do
    mx <- gen `suchThatMaybe` p
    case mx of
      Just x -> return x
      Nothing -> gen `suchThat` p
  
  -- | Randomly uses one of the given generators. The input structure
  -- must be non-empty.
  --
  -- @
  -- λ> import qualified Faker.Address as FA
  -- λ> let fakes = [FA.country, FA.postcode, FA.state]
  -- λ> generate (oneof fakes)
  -- Montana
  -- @
  --
  oneof :: forall t a m. (Monad m, Foldable t) => t (FakeT m a) -> FakeT m a
  oneof xs = helper
    where
      items = toList xs
      helper =
        case items of
          [] -> error "Faker.Combinators.oneof should be non-empty"
          xs' -> fromRange (0, length xs' - 1) >>= (items !!)
  
  -- | Generates one of the given values. The input list must be non-empty.
  --
  -- @
  -- λ> let fakeInt = elements [1..100]
  -- λ> generate fakeInt
  -- 22
  -- @
  --
  elements :: forall t a m. (Monad m, Foldable t) => t a -> FakeT m a
  elements xs =
    case items of
      [] -> error "Faker.Combinators.element used with empty list"
      ys -> (ys !!) `fmap` fromRange (0, length xs - 1)
    where
      items = toList xs
  
  -- | Generates a list of the given length.
  listOf :: forall a m. Monad m => Int -> FakeT m a -> FakeT m [a]
  listOf = replicateM
  
  -- | Generates an ordered list.
  orderedList :: forall a m. (Monad m, Ord a) => Int -> FakeT m a -> FakeT m [a]
  orderedList n gen = sort <$> listOf n gen
  
  -- | Chooses one of the given generators, with a weighted random distribution.
  -- The input list must be non-empty.
  frequency :: forall a m. Monad m => [(Int, FakeT m a)] -> FakeT m a
  frequency [] = error "Faker.Combinators.frequency used with empty list"
  frequency xs0 = fromRange (1, tot) >>= (`pick` xs0)
    where
      tot = sum (map fst xs0)
      pick n ((k, x):xs)
        | n <= k = x
        | otherwise = pick (n - k) xs
      pick _ _ = error "FakeT m.pick used with empty list"
  
  -- | Generate a value of an enumeration in the range [from, to].
  --
  -- @since 0.2.0
  --
  -- @
  -- λ> data Animal = Cat | Dog | Zebra | Elephant | Giarfee deriving (Eq,Ord,Enum, Show)
  -- λ> generate (fakeEnumFromTo Cat Zebra)
  -- Zebra
  -- @
  --
  fakeEnumFromTo :: forall a m. (Monad m, Enum a) => a -> a -> FakeT m a
  fakeEnumFromTo from to = toEnum <$> fromRange (fromEnum from, fromEnum to)
  
  -- | A sumtype can just use this function directly. Defined as
  -- fakeBoundedEnum = `fakeEnumFromTo` `minBound` `maxBound`
  --
  -- @since 0.7.1
  --
  fakeBoundedEnum :: forall a m. (Monad m, Enum a, Bounded a) => FakeT m a
  fakeBoundedEnum = fakeEnumFromTo minBound maxBound
  