module Graphics.Proc.Lib.Math.Random(	
	randomSeed, random, random2, randomP2, randomCol, randomCola,
  -- * Perlin noise
  -- | Returns the Perlin noise value at specified coordinates. Perlin noise is a random sequence generator producing a more natural, harmonic succession of numbers than that of the standard random() function. It was developed by Ken Perlin in the 1980s and has been used in graphical applications to generate procedural textures, shapes, terrains, and other seemingly organic forms.
  --
  -- processing docs: <https://processing.org/reference/noise_.html>
	NoiseDetail(..), noiseDetail, noiseOctaves, noiseSeed, 
  noise1, noise2, noise3,
	randomGaussian
) where

import qualified Control.Arrow as Arr(first, second)
import qualified Control.Monad.Trans.State.Strict as S

import GHC.Float

import qualified System.Random as S
import System.Random hiding (random)
import qualified Numeric.Noise.Perlin as Perlin
import qualified Numeric.Noise as Perlin

import Graphics.Proc.Core
import Graphics.Proc.Lib.Environment

onRandom :: (Maybe StdGen -> IO (a, Maybe StdGen)) -> Pio a
onRandom f = do
  gen <- getRandomGen
  (res, gen1) <- liftIO $ f gen
  putRandomGen gen1
  return res

onNoise :: (NoiseDetail -> Maybe Perlin.Seed -> IO (a, Maybe Perlin.Seed)) -> Pio a
onNoise f = do
  noiseDetail <- getNoiseDetail
  gen <- getNoiseGen  
  (res, gen1) <- liftIO $ f noiseDetail gen
  putNoiseGen gen1  
  return res

-- | Sets the seed value for random(). By default, random() produces different results each time the program is run. Set the seed parameter to a constant to return the same pseudo-random numbers each time the software is run. 
--
-- processing docs: <https://processing.org/reference/randomSeed_.html>
randomSeed :: Int -> Pio ()
randomSeed n = onRandom $ const $ return ((), Just (mkStdGen n))

-- | Generates random numbers. Each time the random() function is called, it returns an unexpected value within the specified range. If only one parameter is passed to the function, it will return a float between zero and the value of the high parameter. For example, random(5) returns values between 0 and 5 (starting at zero, and up to, but not including, 5).
--
-- processing docs: <https://processing.org/reference/random_.html>
random :: Float -> Pio Float
random maxVal= random2 (0, maxVal)

-- | Genrates random numbers within the given range.
random2 :: (Float, Float) -> Pio Float
random2 (minVal, maxVal) = onRandom $ \mg -> 
  case mg of 
    Just g  -> return $ Arr.second Just $ randomR (minVal, maxVal) g
    Nothing -> do
      res <- randomRIO (minVal, maxVal)
      return (res, Nothing)

-- | Creates random point within the ranges of the size of the screen.
randomP2 :: Pio P2
randomP2 = do
  x <- random =<< winWidth
  y <- random =<< winHeight
  return (x, y)

-- | Creates random color.
randomCol :: Pio Col
randomCol = liftA3 (\r g b -> Col r g b 1) (random 1) (random 1) (random 1)

-- | Creates random color with transparency.
randomCola :: Pio Col
randomCola = liftA4 Col (random 1) (random 1) (random 1) (random 1)
  where liftA4 f a b c d = f <$> a <*> b <*> c <*> d

-- | Returns a float from a random series of numbers having a mean of 0 and standard deviation of 1. Each time the randomGaussian() function is called, it returns a number fitting a Gaussian, or normal, distribution. There is theoretically no minimum or maximum value that randomGaussian() might return. Rather, there is just a very low probability that values far from the mean will be returned; and a higher probability that numbers near the mean will be returned.
--
-- processing docs: <https://processing.org/reference/randomGaussian_.html>
randomGaussian :: Pio Float
randomGaussian = do
  r1 <- random 1
  r2 <- random 1
  return $ boxMuller 0 1 (r1, r2)

-- | BoxMuller algorythm for creation of Gaussian random values out of two uniform number r1, r2.
-- r1, r2 should belong to the interval [0, 1]
boxMuller :: Floating a => a -> a -> (a, a) -> a
boxMuller mu sigma (r1,r2) =  mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)

-- | Sets the seed value for noise(). By default, noise() produces different results each time the program is run. Set the seed parameter to a constant to return the same pseudo-random numbers each time the software is run. 
--
-- processing docs: <https://processing.org/reference/noiseSeed_.html>
noiseSeed :: Int -> Pio ()
noiseSeed n = onNoise $ const $ const $ return ((), Just n)

-- | Sets the number of octaves for perlin noise.
noiseOctaves :: Int -> Pio ()
noiseOctaves octaves = putOctaves octaves

-- | Adjusts the character and level of detail produced by the Perlin noise function. Similar to harmonics in physics, noise is computed over several octaves. Lower octaves contribute more to the output signal and as such define the overal intensity of the noise, whereas higher octaves create finer-grained details in the noise sequence.
-- 
-- By default, noise is computed over 4 octaves with each octave contributing exactly half than its predecessor, starting at 50% strength for the first octave. This falloff amount can be changed by adding an additional function parameter. For example, a falloff factor of 0.75 means each octave will now have 75% impact (25% less) of the previous lower octave. While any number between 0.0 and 1.0 is valid, note that values greater than 0.5 may result in noise() returning values greater than 1.0.
-- 
-- By changing these parameters, the signal created by the noise() function can be adapted to fit very specific needs and characteristics. 
-- 
-- processing docs: <https://processing.org/reference/noiseDetail_.html>
noiseDetail :: Int -> Float -> Pio ()
noiseDetail octaves fallOff = putNoiseDetail $ NoiseDetail octaves fallOff

-- | Returns 1D Perlin noise.
noise1 :: Float -> Pio Float
noise1 x = noise3 (x, 0, 0)

-- | Returns 2D Perlin noise.
noise2 :: P2 -> Pio Float
noise2 (x, y) = noise3 (x, y, 0) 

-- | Returns 3D Perlin noise.
noise3 :: P3 -> Pio Float
noise3 p = onNoise $ \details mseed -> case mseed of
  Just g  -> return $ (noiseValue g details p, mseed)
  Nothing -> do
    seed <- randomIO 
    return $ (noiseValue seed details p, mseed)
  where
    noiseValue seed (NoiseDetail octaves fallOff) p = 
      double2Float $ rescale $ Perlin.noiseValue (Perlin.perlin seed octaves scale persistance) (toDoublePoint p)
      where
        scale = 1
        persistance = f2d fallOff    
        toDoublePoint (x, y, z) = (f2d x, f2d y, f2d z)

        -- from [-1,1] to [0,1]
        rescale x = 0.5 * (x + 1)