{-# LANGUAGE CPP #-}

#if __GLASGOW_HASKELL__ >= 908
{-# OPTIONS_GHC -Wno-x-partial #-}
#endif

module Test.Util.Range (
    Range (..)
  , RangeK (..)
  , range
  , rangeK
  ) where

import qualified Data.List as L
import           Data.Word
import           Ouroboros.Consensus.Config.SecurityParam

{-------------------------------------------------------------------------------
  Range of values related to K
-------------------------------------------------------------------------------}

-- | Rough indication of the size of a value in relation to @k@
--
-- Useful for labelling tests.
data RangeK =
    -- | The value was equal to K
    Range_Eq_K SecurityParam

    -- | The value was just below to K
    --
    -- We indicate how far off from K it was
  | Range_Just_Below_K SecurityParam Word64

    -- | The value was just above K
    --
    -- We indicate how far off from K it was
  | Range_Just_Above_K SecurityParam Word64

    -- | The value was almost zero
    --
    -- If there is a choice between 'Range_Just_Below_K' and 'Range_Near_Zero',
    -- the constructor with the smaller argument should be used.
  | Range_Near_Zero SecurityParam Word64

    -- | Less than k (but not near k and not near 0)
    --
    -- We round to the neareast multiple of (k / 10)
  | Range_Less_Than_K SecurityParam Word64

    -- | More than k (but not near k)
    --
    -- We round to the first power of two above k that is equal to above the value.
  | Range_More_Than_K SecurityParam Word64
  deriving (RangeK -> RangeK -> Bool
(RangeK -> RangeK -> Bool)
-> (RangeK -> RangeK -> Bool) -> Eq RangeK
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RangeK -> RangeK -> Bool
== :: RangeK -> RangeK -> Bool
$c/= :: RangeK -> RangeK -> Bool
/= :: RangeK -> RangeK -> Bool
Eq)

instance Show RangeK where
  show :: RangeK -> String
show RangeK
r =
      case RangeK
r of
        Range_Eq_K         (SecurityParam Word64
k)   -> String
"= (k = " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
k String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
        Range_Just_Below_K (SecurityParam Word64
k) Word64
n -> String
"= (k = " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
k String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" >  " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show (Word64
k Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
- Word64
n)
        Range_Just_Above_K (SecurityParam Word64
k) Word64
n -> String
"= (k = " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
k String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" <  " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show (Word64
k Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
n)
        Range_Near_Zero    (SecurityParam Word64
k) Word64
n -> String
"= (k = " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
k String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" >> " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
n
        Range_Less_Than_K  (SecurityParam Word64
k) Word64
n -> String
"≈ (k = " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
k String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" >> " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
n
        Range_More_Than_K  (SecurityParam Word64
k) Word64
n -> String
"≈ (k = " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
k String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" << " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Word64 -> String
forall a. Show a => a -> String
show Word64
n

rangeK :: Integral a => SecurityParam -> a -> RangeK
rangeK :: forall a. Integral a => SecurityParam -> a -> RangeK
rangeK (SecurityParam Word64
k) a
a
  | Word64
n Word64 -> Word64 -> Bool
forall a. Eq a => a -> a -> Bool
== Word64
k    = SecurityParam -> RangeK
Range_Eq_K      (Word64 -> SecurityParam
SecurityParam Word64
k)
  | Word64
n Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
nearK = SecurityParam -> Word64 -> RangeK
Range_Near_Zero (Word64 -> SecurityParam
SecurityParam Word64
k) Word64
n
  | Word64
n Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
k     = if Word64
belowK Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
nearK
                  then SecurityParam -> Word64 -> RangeK
Range_Just_Below_K (Word64 -> SecurityParam
SecurityParam Word64
k) Word64
n
                  else SecurityParam -> Word64 -> RangeK
Range_Less_Than_K  (Word64 -> SecurityParam
SecurityParam Word64
k) (Word64
n Word64 -> Word64 -> Word64
forall a. Integral a => a -> a -> a
`div` Word64
bandSize)
  | Bool
otherwise = if Word64
aboveK Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
nearK
                  then SecurityParam -> Word64 -> RangeK
Range_Just_Above_K (Word64 -> SecurityParam
SecurityParam Word64
k) Word64
n
                  else SecurityParam -> Word64 -> RangeK
Range_More_Than_K  (Word64 -> SecurityParam
SecurityParam Word64
k) ([Word64] -> Word64
forall a. HasCallStack => [a] -> a
head ((Word64 -> Bool) -> [Word64] -> [Word64]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
n) [Word64]
powers))
  where
    n :: Word64
n      = a -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral a
a
    belowK :: Word64
belowK = Word64
k Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
- Word64
n
    aboveK :: Word64
aboveK = Word64
n Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
- Word64
k
    powers :: [Word64]
powers = [Word64
k Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
2 Word64 -> Int -> Word64
forall a b. (Num a, Integral b) => a -> b -> a
^ Int
i | Int
i <- [Int
0..] :: [Int]]

    -- threshold for determining if a value is near k
    nearK :: Word64
nearK = Word64
5

    -- bands for summarizing values less than k
    bandSize :: Word64
bandSize = Word64 -> Word64 -> Word64
forall a. Ord a => a -> a -> a
max Word64
1 (Word64
k Word64 -> Word64 -> Word64
forall a. Integral a => a -> a -> a
`div` Word64
10)

{-------------------------------------------------------------------------------
  Summarize values not related to K
-------------------------------------------------------------------------------}

data Range n = R_Eq n | R_Btwn (n, n) | R_Gt n
  deriving (Int -> Range n -> ShowS
[Range n] -> ShowS
Range n -> String
(Int -> Range n -> ShowS)
-> (Range n -> String) -> ([Range n] -> ShowS) -> Show (Range n)
forall n. Show n => Int -> Range n -> ShowS
forall n. Show n => [Range n] -> ShowS
forall n. Show n => Range n -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: forall n. Show n => Int -> Range n -> ShowS
showsPrec :: Int -> Range n -> ShowS
$cshow :: forall n. Show n => Range n -> String
show :: Range n -> String
$cshowList :: forall n. Show n => [Range n] -> ShowS
showList :: [Range n] -> ShowS
Show, Range n -> Range n -> Bool
(Range n -> Range n -> Bool)
-> (Range n -> Range n -> Bool) -> Eq (Range n)
forall n. Eq n => Range n -> Range n -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: forall n. Eq n => Range n -> Range n -> Bool
== :: Range n -> Range n -> Bool
$c/= :: forall n. Eq n => Range n -> Range n -> Bool
/= :: Range n -> Range n -> Bool
Eq)

range :: (Ord n, Show n, Num n) => n -> Range n
range :: forall n. (Ord n, Show n, Num n) => n -> Range n
range n
n
  | n
n n -> n -> Bool
forall a. Ord a => a -> a -> Bool
> n
limit       = n -> Range n
forall n. n -> Range n
R_Gt n
limit
  | n
n n -> [n] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`L.elem` [n]
vals = n -> Range n
forall n. n -> Range n
R_Eq n
n
  | Bool
otherwise       =
      case ((n, n) -> Bool) -> [(n, n)] -> Maybe (n, n)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
L.find (\(n
lo, n
hi) -> n
lo n -> n -> Bool
forall a. Ord a => a -> a -> Bool
<= n
n Bool -> Bool -> Bool
&& n
n n -> n -> Bool
forall a. Ord a => a -> a -> Bool
<= n
hi) [(n, n)]
rngs of
        Maybe (n, n)
Nothing  -> String -> Range n
forall a. HasCallStack => String -> a
error (String -> Range n) -> String -> Range n
forall a b. (a -> b) -> a -> b
$ String
"range: unable to classify " String -> ShowS
forall a. [a] -> [a] -> [a]
++ n -> String
forall a. Show a => a -> String
show n
n
        Just (n, n)
rng -> (n, n) -> Range n
forall n. (n, n) -> Range n
R_Btwn (n, n)
rng
  where
    vals :: [n]
vals  = [n
0, n
1, n
2, n
3, n
4]
    rngs :: [(n, n)]
rngs  = [(n
0, n
1), (n
1, n
2), (n
2, n
3), (n
3, n
4), (n
4, n
5), (n
5, n
10), (n
10, n
20)]
    limit :: n
limit = n
20