-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT

module Random (randomString) where

import Control.Monad (replicateM)
import Data.Char (intToDigit)
import Data.IORef (IORef, modifyIORef', newIORef, readIORef)
import Data.Set (Set)
import qualified Data.Set as Set
import GHC.IO (unsafePerformIO)
import System.Random (randomRIO)

strings :: IORef (Set String)
{-# NOINLINE strings #-}
strings :: IORef (Set String)
strings = IO (IORef (Set String)) -> IORef (Set String)
forall a. IO a -> a
unsafePerformIO (Set String -> IO (IORef (Set String))
forall a. a -> IO (IORef a)
newIORef Set String
forall a. Set a
Set.empty)

generate :: String -> IO String
generate :: String -> IO String
generate [] = String -> IO String
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure []
generate (Char
'%' : Char
ch : String
rest) = do
  String
rep <- case Char
ch of
    Char
'x' -> Int -> IO Char -> IO String
forall (m :: * -> *) a. Applicative m => Int -> m a -> m [a]
replicateM Int
8 (IO Char -> IO String) -> IO Char -> IO String
forall a b. (a -> b) -> a -> b
$ do
      Int
v <- (Int, Int) -> IO Int
forall a (m :: * -> *). (Random a, MonadIO m) => (a, a) -> m a
randomRIO (Int
0, Int
15)
      Char -> IO Char
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Int -> Char
intToDigit Int
v)
    Char
'd' -> Int -> String
forall a. Show a => a -> String
show (Int -> String) -> IO Int -> IO String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Int, Int) -> IO Int
forall a (m :: * -> *). (Random a, MonadIO m) => (a, a) -> m a
randomRIO (Int
0 :: Int, Int
9999)
    Char
_ -> String -> IO String
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure [Char
'%', Char
ch]
  String
next <- String -> IO String
generate String
rest
  String -> IO String
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (String
rep String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
next)
generate (Char
ch : String
rest) = do
  String
rest' <- String -> IO String
generate String
rest
  String -> IO String
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Char
ch Char -> String -> String
forall a. a -> [a] -> [a]
: String
rest')

regenerate :: String -> Set String -> IO String
regenerate :: String -> Set String -> IO String
regenerate String
pat Set String
set = do
  String
next <- String -> IO String
generate String
pat
  if String
next String -> Set String -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set String
set
    then String -> Set String -> IO String
regenerate String
pat Set String
set
    else do
      IORef (Set String) -> (Set String -> Set String) -> IO ()
forall a. IORef a -> (a -> a) -> IO ()
modifyIORef' IORef (Set String)
strings (String -> Set String -> Set String
forall a. Ord a => a -> Set a -> Set a
Set.insert String
next)
      String -> IO String
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure String
next

randomString :: String -> IO String
randomString :: String -> IO String
randomString String
pat = do
  Set String
set <- IORef (Set String) -> IO (Set String)
forall a. IORef a -> IO a
readIORef IORef (Set String)
strings
  String -> Set String -> IO String
regenerate String
pat Set String
set