```{-# OPTIONS_HADDOCK not-home #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Hedgehog.Internal.Range (
-- * Size
Size(..)

-- * Range
, Range(..)
, origin
, bounds
, lowerBound
, upperBound

-- * Constant
, singleton
, constant
, constantFrom
, constantBounded

-- * Linear
, linear
, linearFrom
, linearFrac
, linearFracFrom
, linearBounded

-- * Exponential
, exponential
, exponentialFrom
, exponentialBounded
, exponentialFloat
, exponentialFloatFrom

-- * Internal
-- \$internal
, clamp
, scaleLinear
, scaleLinearFrac
, scaleExponential
, scaleExponentialFloat
) where

import           Data.Bifunctor (bimap)

import           Prelude hiding (minimum, maximum)

-- \$setup
-- >>> import Data.Int (Int8)
-- >>> let x = 3

-- | Tests are parameterized by the size of the randomly-generated data, the
--   meaning of which depends on the particular generator used.
--
newtype Size =
Size {
unSize :: Int
} deriving (Eq, Ord, Num, Real, Enum, Integral)

instance Show Size where
showsPrec p (Size x) =
showParen (p > 10) \$
showString "Size " .
showsPrec 11 x

readParen (p > 10) \$ \r0 -> do
("Size", r1) <- lex r0
(s, r2) <- readsPrec 11 r1
pure (Size s, r2)

-- | A range describes the bounds of a number to generate, which may or may not
--   be dependent on a 'Size'.
--
--   The constructor takes an origin between the lower and upper bound, and a
--   function from 'Size' to bounds.  As the size goes towards @0@, the values
--   go towards the origin.
--
data Range a =
Range !a (Size -> (a, a))

instance Functor Range where
fmap f (Range z g) =
Range (f z) \$ \sz ->
bimap f f (g sz)

-- | Get the origin of a range. This might be the mid-point or the lower bound,
--   depending on what the range represents.
--
--   The 'bounds' of a range are scaled around this value when using the
--   'linear' family of combinators.
--
--   When using a 'Range' to generate numbers, the shrinking function will
--   shrink towards the origin.
--
origin :: Range a -> a
origin (Range z _) =
z

-- | Get the extents of a range, for a given size.
--
bounds :: Size -> Range a -> (a, a)
bounds sz (Range _ f) =
f sz

-- | Get the lower bound of a range for the given size.
--
lowerBound :: Ord a => Size -> Range a -> a
lowerBound sz range =
let
(x, y) =
bounds sz range
in
min x y

-- | Get the upper bound of a range for the given size.
--
upperBound :: Ord a => Size -> Range a -> a
upperBound sz range =
let
(x, y) =
bounds sz range
in
max x y

-- | Construct a range which represents a constant single value.
--
--   >>> bounds x \$ singleton 5
--   (5,5)
--
--   >>> origin \$ singleton 5
--   5
--
singleton :: a -> Range a
singleton x =
Range x \$ \_ -> (x, x)

-- | Construct a range which is unaffected by the size parameter.
--
--   A range from @0@ to @10@, with the origin at @0@:
--
--   >>> bounds x \$ constant 0 10
--   (0,10)
--
--   >>> origin \$ constant 0 10
--   0
--
constant :: a -> a -> Range a
constant x y =
constantFrom x x y

-- | Construct a range which is unaffected by the size parameter with a origin
--   point which may differ from the bounds.
--
--   A range from @-10@ to @10@, with the origin at @0@:
--
--   >>> bounds x \$ constantFrom 0 (-10) 10
--   (-10,10)
--
--   >>> origin \$ constantFrom 0 (-10) 10
--   0
--
--   A range from @1970@ to @2100@, with the origin at @2000@:
--
--   >>> bounds x \$ constantFrom 2000 1970 2100
--   (1970,2100)
--
--   >>> origin \$ constantFrom 2000 1970 2100
--   2000
--
constantFrom ::
a -- ^ Origin (the value produced when the size parameter is 0).
-> a -- ^ Lower bound (the bottom of the range when the size parameter is 99).
-> a -- ^ Upper bound (the top of the range when the size parameter is 99).
-> Range a
constantFrom z x y =
Range z \$ \_ -> (x, y)

-- | Construct a range which is unaffected by the size parameter using the full
--   range of a data type.
--
--   A range from @-128@ to @127@, with the origin at @0@:
--
--   >>> bounds x (constantBounded :: Range Int8)
--   (-128,127)
--
--   >>> origin (constantBounded :: Range Int8)
--   0
--
constantBounded :: (Bounded a, Num a) => Range a
constantBounded =
constantFrom 0 minBound maxBound

-- | Construct a range which scales the second bound relative to the size
--   parameter.
--
--   >>> bounds 0 \$ linear 0 10
--   (0,0)
--
--   >>> bounds 50 \$ linear 0 10
--   (0,5)
--
--   >>> bounds 99 \$ linear 0 10
--   (0,10)
--
linear :: Integral a => a -> a -> Range a
linear x y =
linearFrom x x y

-- | Construct a range which scales the bounds relative to the size parameter.
--
--   >>> bounds 0 \$ linearFrom 0 (-10) 10
--   (0,0)
--
--   >>> bounds 50 \$ linearFrom 0 (-10) 20
--   (-5,10)
--
--   >>> bounds 99 \$ linearFrom 0 (-10) 20
--   (-10,20)
--
linearFrom :: Integral a
=> a -- ^ Origin (the value produced when the size parameter is 0).
-> a -- ^ Lower bound (the bottom of the range when the size parameter is 99).
-> a -- ^ Upper bound (the top of the range when the size parameter is 99).
-> Range a
linearFrom z x y =
Range z \$ \sz ->
let
x_sized =
clamp x y \$ scaleLinear sz z x

y_sized =
clamp x y \$ scaleLinear sz z y
in
(x_sized, y_sized)

-- | Construct a range which is scaled relative to the size parameter and uses
--   the full range of a data type.
--
--   >>> bounds 0 (linearBounded :: Range Int8)
--   (0,0)
--
--   >>> bounds 50 (linearBounded :: Range Int8)
--   (-64,64)
--
--   >>> bounds 99 (linearBounded :: Range Int8)
--   (-128,127)
--
linearBounded :: (Bounded a, Integral a) => Range a
linearBounded =
linearFrom 0 minBound maxBound

-- | Construct a range which scales the second bound relative to the size
--   parameter.
--
--   This works the same as 'linear', but for fractional values.
--
linearFrac :: (Fractional a, Ord a) => a -> a -> Range a
linearFrac x y =
linearFracFrom x x y

-- | Construct a range which scales the bounds relative to the size parameter.
--
--   This works the same as 'linearFrom', but for fractional values.
--
linearFracFrom :: (Fractional a, Ord a) => a -> a -> a -> Range a
linearFracFrom z x y =
Range z \$ \sz ->
let
x_sized =
clamp x y \$ scaleLinearFrac sz z x

y_sized =
clamp x y \$ scaleLinearFrac sz z y
in
(x_sized, y_sized)

-- | Truncate a value so it stays within some range.
--
--   >>> clamp 5 10 15
--   10
--
--   >>> clamp 5 10 0
--   5
--
clamp :: Ord a => a -> a -> a -> a
clamp x y n =
if x > y then
min x (max y n)
else
min y (max x n)

-- | Scale an integral linearly with the size parameter.
--
scaleLinear :: Integral a => Size -> a -> a -> a
scaleLinear sz0 z0 n0 =
let
sz =
max 0 (min 99 sz0)

z =
toInteger z0

n =
toInteger n0

diff =
((n - z) * fromIntegral sz) `quot` 99
in
fromInteger \$ z + diff

-- | Scale a fractional number linearly with the size parameter.
--
scaleLinearFrac :: Fractional a => Size -> a -> a -> a
scaleLinearFrac sz0 z n =
let
sz =
max 0 (min 99 sz0)

diff =
(n - z) * (fromIntegral sz / 99)
in
z + diff

-- | Construct a range which scales the second bound exponentially relative to
--   the size parameter.
--
--   >>> bounds 0 \$ exponential 1 512
--   (1,1)
--
--   >>> bounds 11 \$ exponential 1 512
--   (1,2)
--
--   >>> bounds 22 \$ exponential 1 512
--   (1,4)
--
--   >>> bounds 77 \$ exponential 1 512
--   (1,128)
--
--   >>> bounds 88 \$ exponential 1 512
--   (1,256)
--
--   >>> bounds 99 \$ exponential 1 512
--   (1,512)
--
exponential :: Integral a => a -> a -> Range a
exponential x y =
exponentialFrom x x y

-- | Construct a range which scales the bounds exponentially relative to the
-- size parameter.
--
--   >>> bounds 0 \$ exponentialFrom 0 (-128) 512
--   (0,0)
--
--   >>> bounds 25 \$ exponentialFrom 0 (-128) 512
--   (-2,4)
--
--   >>> bounds 50 \$ exponentialFrom 0 (-128) 512
--   (-11,22)
--
--   >>> bounds 75 \$ exponentialFrom 0 (-128) 512
--   (-39,112)
--
--   >>> bounds 99 \$ exponentialFrom x (-128) 512
--   (-128,512)
--
exponentialFrom :: Integral a
=> a -- ^ Origin (the value produced when the size parameter is 0).
-> a -- ^ Lower bound (the bottom of the range when the size parameter is 99).
-> a -- ^ Upper bound (the top of the range when the size parameter is 99).
-> Range a
exponentialFrom z x y =
Range z \$ \sz ->
let
sized_x =
clamp x y \$ scaleExponential sz z x

sized_y =
clamp x y \$ scaleExponential sz z y
in
(sized_x, sized_y)

-- | Construct a range which is scaled exponentially relative to the size
--   parameter and uses the full range of a data type.
--
--   >>> bounds 0 (exponentialBounded :: Range Int8)
--   (0,0)
--
--   >>> bounds 50 (exponentialBounded :: Range Int8)
--   (-11,11)
--
--   >>> bounds 99 (exponentialBounded :: Range Int8)
--   (-128,127)
--
exponentialBounded :: (Bounded a, Integral a) => Range a
exponentialBounded =
exponentialFrom 0 minBound maxBound

-- | Construct a range which scales the second bound exponentially relative to
--   the size parameter.
--
--   This works the same as 'exponential', but for floating-point values.
--
--   >>> bounds 0 \$ exponentialFloat 0 10
--   (0.0,0.0)
--
--   >>> bounds 50 \$ exponentialFloat 0 10
--   (0.0,2.357035250656098)
--
--   >>> bounds 99 \$ exponentialFloat 0 10
--   (0.0,10.0)
--
exponentialFloat :: (Floating a, Ord a) => a -> a -> Range a
exponentialFloat x y =
exponentialFloatFrom x x y

-- | Construct a range which scales the bounds exponentially relative to the
--   size parameter.
--
--   This works the same as 'exponentialFrom', but for floating-point values.
--
--   >>> bounds 0 \$ exponentialFloatFrom 0 (-10) 20
--   (0.0,0.0)
--
--   >>> bounds 50 \$ exponentialFloatFrom 0 (-10) 20
--   (-2.357035250656098,3.6535836249197002)
--
--   >>> bounds 99 \$ exponentialFloatFrom x (-10) 20
--   (-10.0,20.0)
--
exponentialFloatFrom :: (Floating a, Ord a) => a -> a -> a -> Range a
exponentialFloatFrom z x y =
Range z \$ \sz ->
let
sized_x =
clamp x y \$ scaleExponentialFloat sz z x

sized_y =
clamp x y \$ scaleExponentialFloat sz z y
in
(sized_x, sized_y)

-- | Scale an integral exponentially with the size parameter.
--
scaleExponential :: Integral a => Size -> a -> a -> a
scaleExponential sz z0 n0 =
let
z =
fromIntegral z0

n =
fromIntegral n0
in
round (scaleExponentialFloat sz z n :: Double)

-- | Scale a floating-point number exponentially with the size parameter.
--
scaleExponentialFloat :: Floating a => Size -> a -> a -> a
scaleExponentialFloat sz0 z n =
let
sz =
clamp 0 99 sz0

diff =
(((abs (n - z) + 1) ** (realToFrac sz / 99)) - 1) * signum (n - z)
in
z + diff

------------------------------------------------------------------------
-- Internal

-- \$internal
--
-- These functions are exported in case you need them in a pinch, but are not
-- part of the public API and may change at any time, even as part of a minor
-- update.
```