{- |
  Pure functions for random number generation.
-}

module Random.MWC.Pure
    (
      -- * Random seed
      Seed (), seed,

      -- * Random number generation
      BoundedRandom (..), UnitRandom (..), RangeRandom (..),
      random_list,
    )
  where

import Data.Bits
import Data.Word
import Data.Int

import Random.MWC.Primitive

---------------------------------------------------------------------

{- |
  Class of things that can be chosen at random over their entire
  value range. This requires that the range of possible values is
  actually limited.
-}
class Bounded x => BoundedRandom x where
  {- |
    Given a 'Seed', return a randomly-chosen value and a new 'Seed'
    value.

    The value is chosen psuedo-randomly (the same 'Seed' will always
    yield the same choice), with uniform distribution (all values
    equally likely). The range of possible values is from 'minBound'
    to 'maxBound' inclusive.
  -}
  bounded_random :: Seed -> (x, Seed)

{- |
  Class of things that can be chosen at random over the interval from
  zero to one. This requires that \"zero\" and \"one\" are meaningful
  concepts for this type, and also that the type is ordered. (Also,
  there must be values /between/ zero and one, which rules out
  integral types.)
-}
class Ord x => UnitRandom x where
  {- |
    Given a 'Seed', return a randomly-chosen value and a new 'Seed'
    value.

    The value is chosen psuedo-randomly (the same 'Seed' will always
    yield the same choice), with uniform distribution (all values
    equally likely). The range of possible values is from \"zero\" to
    \"one\" inclusive.
  -}
  unit_random :: Seed -> (x, Seed)

{- |
  Class of things that can be chosen at random over a specified
  interval. This requires that the type is ordered.
-}
class Ord x => RangeRandom x where
  {- |
    Given a 'Seed', return a randomly-chosen value and a new 'Seed'
    value.

    The value is chosen psuedo-randomly (the same 'Seed' will always
    yield the same choice), with uniform distribution (all values
    equally likely). The range is given by the first argument, which
    specifies the lower and upper bounds (inclusive).
  -}
  range_random :: (x, x) -> Seed -> (x, Seed)

{- |
  Given a function to generate one random item, generate a list of
  random items (of the specified length).
-}
random_list :: (Seed -> (x, Seed)) -> Int -> Seed -> ([x], Seed)
random_list f n s
  | n <  0    = error "Random.MWC.random_list: negative length"
  | n == 0    = ([], s)
  | otherwise =
    let
      (x , s' ) = f s
      (xs, s'') = random_list f (n-1) s'
    in (x:xs, s'')

---------------------------------------------------------------------

instance BoundedRandom Bool where
  bounded_random s =
    let (x, s') = next_word s
    in  (odd x, s')

instance BoundedRandom Word8 where
  bounded_random s =
    let (x, s') = next_word s
    in  (fromIntegral x, s')

instance BoundedRandom Word16 where
  bounded_random s =
    let (x, s') = next_word s
    in  (fromIntegral x, s')

instance BoundedRandom Word32 where
  bounded_random = next_word

instance BoundedRandom Word64 where
  bounded_random s0 =
    let
      (x1, s1) = next_word s0
      (x2, s2) = next_word s1
      w1 = fromIntegral x1
      w2 = fromIntegral x2
    in  (w1 `shift` 32 .|. w2, s2)

instance BoundedRandom Int8 where
  bounded_random s =
    let (x, s') = next_word s
    in  (fromIntegral x, s')

instance BoundedRandom Int16 where
  bounded_random s =
    let (x, s') = next_word s
    in  (fromIntegral x, s')

instance BoundedRandom Int32 where
  bounded_random s =
    let (x, s') = next_word s
    in  (fromIntegral x, s')

instance BoundedRandom Int64 where
  bounded_random s =
    let (x, s') = bounded_random s :: (Word64, Seed)
    in  (fromIntegral x, s')

-- This will go wrong if Int is wider than 32 bits.
instance BoundedRandom Int where
  bounded_random s =
    let (x, s') = next_word s
    in  (fromIntegral x, s')

-- This will go wrong if Word is wider than 32 bits.
instance BoundedRandom Word where
  bounded_random s =
    let (x, s') = next_word s
    in  (fromIntegral x, s')

---------------------------------------------------------------------

instance UnitRandom Float where
  unit_random s =
    let
      (x, s') = next_word s
      magic = 2**(-32) :: Float
    in (magic * fromIntegral x, s')

instance UnitRandom Double where
  unit_random s =
    let
      (x, s') = bounded_random s :: (Word64, Seed)
      magic = 2**(-64) :: Double
    in (magic * fromIntegral x, s')

---------------------------------------------------------------------

instance RangeRandom Float where
  range_random (x0, x1) s =
    let (x, s') = unit_random s
    in  ((x1-x0)*x + x0, s')

instance RangeRandom Double where
  range_random (x0, x1) s =
    let (x, s') = unit_random s
    in  ((x1-x0)*x + x0, s')

instance RangeRandom Word8 where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Word16 where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Word32 where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Word64 where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Int8 where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Int16 where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Int32 where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Int64 where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Int where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')

instance RangeRandom Word where
  range_random (x0, x1) s =
    let
      dx = x1 - x0
      (x, s') = bounded_random s
      (xa, xb) = x `divMod` dx
    in
      if (xa+1)*dx < xa*dx
        then range_random (x0, x1) s'
        else (xb + x0, s')