content: {-# LANGUAGE BangPatterns #-}
  {-# LANGUAGE CPP #-}
  {-# LANGUAGE InstanceSigs #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE PatternSynonyms #-}
  {-# LANGUAGE RecordWildCards #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  
  module Faker
    ( -- * Types
      Fake,
      FakeT (.., Fake),
      FakerSettings,
      FakerException (..),
      NonDeterministicSeed (..),
      defaultFakerSettings,
  
      -- * Setters
      setLocale,
      setRandomGen,
      setDeterministic,
      setNonDeterministic,
      setNonDeterministicSeed,
      setCacheField,
      setCacheFile,
      replaceCacheField,
      replaceCacheFile,
  
      -- * Getters
      getRandomGen,
      getLocale,
      getDeterministic,
      getNonDeterministicSeed,
      getCacheField,
      getCacheFile,
  
      -- * Generators
      generate,
      generateNonDeterministic,
      generateNonDeterministicWithFixedSeed,
      generateWithSettings,
    )
  where
  
  import Control.Exception (Exception)
  import Control.Monad (ap)
  import Control.Monad.IO.Class
  import qualified Data.HashMap.Strict as HM
  import Data.IORef
  import Data.Semigroup (Semigroup, (<>))
  import Data.Text (Text)
  import Data.Typeable
  import Data.Vector (Vector)
  import Data.Yaml (Value)
  import Faker.Internal.Types (AesonKey, CacheFieldKey, CacheFileKey)
  import System.Random (StdGen, mkStdGen, newStdGen, split)
  #if MIN_VERSION_aeson(2,0,0)
  import qualified Data.Aeson.Key as K
  #endif
  
  data FakerSettings = FakerSettings
    { -- | Locale settings for your fake data source.
      fslocale :: !Text,
      -- | Seed to initialize random generator state
      fsrandomGen :: !StdGen,
      -- | Controls whether you want
      -- deterministic out. This overrides
      -- 'fsrandomGen'.
      fsDeterministic :: !Bool,
      fsNonDeterministicBehavior :: !NonDeterministicSeed,
      fsCacheField :: (IORef (HM.HashMap CacheFieldKey (Vector Text))),
      fsCacheFile :: (IORef (HM.HashMap CacheFileKey Value))
    }
  
  newtype FakerGen = FakerGen
    { unFakerGen :: (Int, StdGen)
    }
    deriving (Show)
  
  instance Show FakerSettings where
    show (FakerSettings {..}) =
      show fslocale ++ show fsrandomGen ++ show fsDeterministic
  
  data FakerException
    = -- | This is thrown when it is not able to
      -- find the fake data source for your
      -- localization.
      InvalidLocale String
    | -- | The 'String' represents the field it is
      -- trying to resolve and the 'Key' field
      -- is something you passed on.
      InvalidField
        String
        AesonKey
    | -- | This is thrown when you have no
      -- data. This may likely happen for
      -- locales other than `en`.
      NoDataFound FakerSettings
    | -- | This is thrown when the parsing step
      -- fails. The 'String' represents the error
      -- message.
      ParseError String
    deriving (Typeable, Show)
  
  instance Exception FakerException
  
  -- | Default faker settings with locale of \"en\" and Deterministic output.
  defaultFakerSettings :: FakerSettings
  defaultFakerSettings =
    FakerSettings
      { fslocale = "en",
        fsrandomGen = mkStdGen 10000,
        fsDeterministic = True,
        fsCacheField = error "defaultFakerSettings: fsCacheField not initialized",
        fsCacheFile = error "defaultFakerSettings: fsCacheFile not initialized",
        fsNonDeterministicBehavior = NewSeed
      }
  
  -- | Sets the locale. Note that for any other locale apart from
  -- \"en\", you need to make sure that the data is acutally present. In
  -- case no data is found, 'NoDataFound' exception will be thrown. You
  -- can check the presence of the data in a particular locale by
  -- inspecting the `yml` file of the corresponding locale. The file
  -- would be bundled along with the particular Hackage release.
  setLocale :: Text -> FakerSettings -> FakerSettings
  setLocale localeTxt fs = fs {fslocale = localeTxt}
  
  -- | Sets the initial gen for random generator
  setRandomGen :: StdGen -> FakerSettings -> FakerSettings
  setRandomGen gen fs = fs {fsrandomGen = gen}
  
  -- | Get the initial gen for random generator
  getRandomGen :: FakerSettings -> StdGen
  getRandomGen settings = fsrandomGen settings
  
  -- | Get the Locale settings for your fake data source
  getLocale :: FakerSettings -> Text
  getLocale FakerSettings {..} = fslocale
  
  -- | Sets the 'NonDeterministicSeed'
  setNonDeterministicSeed :: NonDeterministicSeed -> FakerSettings -> FakerSettings
  setNonDeterministicSeed seed fs = fs { fsNonDeterministicBehavior = seed }
  
  -- | Set the output of fakedata to be deterministic. With this you
  -- will get the same ouput for the functions every time.
  --
  -- @
  -- λ> import qualified Faker.Name as FN
  -- λ> :t FN.name
  -- FN.name :: Fake Text
  -- λ> generateWithSettings (setDeterministic defaultFakerSettings) FN.name
  -- "Antony Langosh"
  -- λ> generateWithSettings (setDeterministic defaultFakerSettings) FN.name
  -- "Antony Langosh"
  -- @
  setDeterministic :: FakerSettings -> FakerSettings
  setDeterministic fs = fs {fsDeterministic = True}
  
  -- | Set the output of fakedata to be non deterministic. With this you
  -- will get different ouput for the fake functions.
  --
  -- @
  -- λ> generateWithSettings (setNonDeterministic defaultFakerSettings) FN.name
  -- "Macy Shanahan"
  -- λ> generateWithSettings (setNonDeterministic defaultFakerSettings) FN.name
  -- "Rudy Dickinson II"
  -- @
  setNonDeterministic :: FakerSettings -> FakerSettings
  setNonDeterministic fs = fs {fsDeterministic = False}
  
  -- | Check if the fake data output is deterministic or not. A True
  -- value indicates that it is deterministic.
  getDeterministic :: FakerSettings -> Bool
  getDeterministic FakerSettings {..} = fsDeterministic
  
  -- | Get the 'NonDeterministicSeed' from faker settings. Note that
  -- this setting is only applicable when use non deterministic output.
  getNonDeterministicSeed :: FakerSettings -> NonDeterministicSeed
  getNonDeterministicSeed FakerSettings {..} = fsNonDeterministicBehavior
  
  getCacheField :: FakerSettings -> IO (HM.HashMap CacheFieldKey (Vector Text))
  getCacheField FakerSettings {..} = readIORef fsCacheField
  
  setCacheField ::
    HM.HashMap CacheFieldKey (Vector Text) -> FakerSettings -> IO ()
  setCacheField cache fs = do
    writeIORef (fsCacheField fs) cache
  
  replaceCacheField ::
    HM.HashMap CacheFieldKey (Vector Text) -> FakerSettings -> IO FakerSettings
  replaceCacheField cache fs = do
    ref <- newIORef cache
    pure $ fs {fsCacheField = ref}
  
  getCacheFile :: FakerSettings -> IO (HM.HashMap CacheFileKey Value)
  getCacheFile FakerSettings {..} = readIORef fsCacheFile
  
  setCacheFile :: HM.HashMap CacheFileKey Value -> FakerSettings -> IO ()
  setCacheFile cache fs = writeIORef (fsCacheFile fs) cache
  
  replaceCacheFile ::
    HM.HashMap CacheFileKey Value -> FakerSettings -> IO FakerSettings
  replaceCacheFile cache fs = do
    ref <- newIORef cache
    pure $ fs {fsCacheFile = ref}
  
  newtype FakeT m a = FakeT
    { runFakeT :: FakerSettings -> m a
    }
  
  -- | Fake data type. This is the type you will be using to produce
  -- fake values.
  type Fake = FakeT IO
  
  pattern Fake :: (FakerSettings -> IO a) -> Fake a
  pattern Fake f = FakeT f
  
  unFake :: Fake a -> FakerSettings -> IO a
  unFake = runFakeT
  
  instance Monad m => Functor (FakeT m) where
    {-# INLINE fmap #-}
    fmap :: (a -> b) -> FakeT m a -> FakeT m b
    fmap f (FakeT h) =
      FakeT
        ( \r -> do
            a <- h r
            let b = f a
            pure b
        )
  
  instance Monad m => Applicative (FakeT m) where
    {-# INLINE pure #-}
    pure x = FakeT (\_ -> pure x)
    {-# INLINE (<*>) #-}
    (<*>) = ap
  
  instance Monad m => Monad (FakeT m) where
    {-# INLINE return #-}
    return :: a -> FakeT m a
    return x = FakeT (\_ -> return x)
    {-# INLINE (>>=) #-}
    (>>=) :: FakeT m a -> (a -> FakeT m b) -> FakeT m b
    f >>= k = generateNewFake f k
  
  generateNewFake :: Monad m => FakeT m a -> (a -> FakeT m b) -> FakeT m b
  generateNewFake (FakeT h) k =
    FakeT
      ( \settings -> do
          let deterministic = getDeterministic settings
              currentStdGen = getRandomGen settings
              newStdGen =
                if deterministic
                  then currentStdGen
                  else fst $ split currentStdGen
          item <- h settings
          let (FakeT k1) = k item
          k1 (setRandomGen newStdGen settings)
      )
  {-# SPECIALIZE INLINE generateNewFake :: Fake Text -> (Text -> Fake Text) -> Fake Text #-}
  
  instance MonadIO m => MonadIO (FakeT m) where
    liftIO :: IO a -> FakeT m a
    liftIO xs = FakeT (\_ -> liftIO xs)
  
  -- | @since 0.6.1
  instance (Semigroup a, Monad m) => Semigroup (FakeT m a) where
    mx <> my = (<>) <$> mx <*> my
  
  -- | @since 0.6.1
  instance (Monoid a, Monad m) => Monoid (FakeT m a) where
    mempty = pure mempty
    mappend mx my = mappend <$> mx <*> my
  
  -- | Generate fake value with 'defaultFakerSettings'. This produces
  -- deterministic output by default.
  --
  -- @
  -- λ> import qualified Faker.Name as FN
  -- λ> generate FN.name
  -- "Antony Langosh"
  -- @
  generate :: MonadIO m => FakeT m a -> m a
  generate (FakeT f) = do
    cacheField <- liftIO $ newIORef HM.empty
    cacheFile <- liftIO $ newIORef HM.empty
    f $ defaultFakerSettings {fsCacheField = cacheField, fsCacheFile = cacheFile}
  
  -- | Generate fake value with 'defaultFakerSettings' but with non
  -- deterministic setting.
  --
  -- @since 0.8.0
  --
  -- @
  -- λ> import qualified Faker.Name as FN
  -- λ> generateNonDeterministic FN.name
  -- "Prof. Antoine O'Conner"
  -- λ> generateNonDeterministic FN.name
  -- "Savannah Buckridge"
  -- @
  generateNonDeterministic :: MonadIO m => FakeT m a -> m a
  generateNonDeterministic = generateWithSettings $ setNonDeterministic defaultFakerSettings
  
  -- | Generate fake value with supplied 'FakerSettings'
  --
  -- @
  -- λ> generateWithSettings defaultFakerSettings FN.name
  -- "Antony Langosh"
  -- @
  generateWithSettings :: MonadIO m => FakerSettings -> FakeT m a -> m a
  generateWithSettings settings (FakeT f) = do
    let deterministic = getDeterministic settings
    stdGen <-
      if deterministic
        then pure $ getRandomGen settings
        else case fsNonDeterministicBehavior settings of
          FixedSeed -> pure $ fst $ split (getRandomGen settings)
          NewSeed -> liftIO newStdGen
    let newSettings = setRandomGen stdGen settings
    cacheField <- liftIO $ newIORef HM.empty
    cacheFile <- liftIO $ newIORef HM.empty
    f $ newSettings {fsCacheField = cacheField, fsCacheFile = cacheFile}
  
  -- | Generate fake value with 'NonDeterministicSeed' as
  -- 'FixedSeed'. The difference between 'generateNonDeterministic' and
  -- this function is that this uses a fixed seed set via `setRandomGen`
  -- as it's initial seed value.
  --
  -- Executing this function multiple times will result in generation of
  -- same values.
  --
  -- @since 1.0.3
  -- @
  -- λ> generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,100)
  -- [98,87,77,33,98]
  -- λ> generateNonDeterministicWithFixedSeed $ listOf 5 $ fromRange (1,100)
  -- [98,87,77,33,98]
  -- @
  generateNonDeterministicWithFixedSeed :: MonadIO m => FakeT m a -> m a
  generateNonDeterministicWithFixedSeed =
    generateWithSettings $
      setNonDeterministic
        ( defaultFakerSettings
            { fsNonDeterministicBehavior = FixedSeed
            }
        )
  
  -- | NonDeterministicSeed type which controls if a fixed seed is going
  -- to be used or if a new seed will be generated each time.
  --
  -- @since 1.0.3
  data NonDeterministicSeed
    = -- | Always use a fixed seed.
      FixedSeed
    | -- | Use a new seed every time.
      NewSeed
  