```-- | Functions for accessing the values of enumerations including
-- compatibility with the property based testing frameworks QuickCheck and
-- SmallCheck.
module Test.Feat.Access(
-- ** Accessing functions
index,
values,
striped,
bounded,

-- ** A simple property tester
ioFeat,
ioAll,
ioBounded,

-- ** Compatibility
-- *** QuickCheck
uniform,
-- *** SmallCheck
toSeries,

-- ** Non-class versions of the access functions
valuesWith,
stripedWith,
boundedWith,
uniformWith,
toSeriesWith
)where

-- testing-feat
import Test.Feat.Enumerate
import Test.Feat.Class
-- base
import Data.List
-- quickcheck
import Test.QuickCheck
-- smallcheck
-- import Test.SmallCheck.Series -- Not needed

group :: Enumerate a -> Part -> Index -> Integer
group e p i = sum (map (card e) [0..p-1]) + i

split :: Enumerate a -> Integer -> (Part, Index)
split e i0 = go i0 0 where
go i p = let crd = card e p in
if i < crd then (p,i)
else go (i-crd) (p+1)

-- | Mainly as a proof of concept we can use the isomorphism between
-- natural numbers and @(Part,Index)@ pairs to index into a type.
-- May not terminate for finite types.
-- Might be slow the first time it is used with a specific enumeration
-- because cardinalities need to be calculated.
-- The computational complexity (after cardinalities are computed) is a polynomial
-- in the size of the resulting value.
index :: Enumerate a -> Integer -> a
index e = uncurry (select e) . split e

-- | All values of the enumeration by increasing cost (which is the number
-- of constructors for most types). Also contains the cardinality of each list.
values :: Enumerable a => [(Integer,[a])]
values = valuesWith optimised

-- | A generalisation of @values@ that enumerates every nth value of the
-- enumeration from a given starting point.
-- As a special case @values = striped 0 0 1@.
striped ::  Enumerable a => Part -> Index -> Integer -> [(Integer,[a])]
striped = stripedWith optimised

-- | A version of vales that has a limited number of values in each inner list.
-- If the list corresponds to a Part which is larger than the bound it evenly
-- intersperses the values across the enumeration of the Part.
bounded :: Enumerable a => Integer -> [(Integer,[a])]
bounded = boundedWith optimised

-- | A rather simple but general property testing driver.
-- The property is an (funcurried) IO function that both tests and reports the
-- error. The driver goes on forever or until the list is exhausted,
-- reporting the coverage and the number of
-- tests before each new part.
ioFeat :: [(Integer,[a])] -> (a -> IO ()) -> IO ()
ioFeat vs f = go vs 0 0 where
go ((c,xs):xss) s tot = do
putStrLn \$ "--- Testing "++show c++" values at size " ++ show s
mapM f xs
go xss (s+1) (tot + c)
go []           s tot = putStrLn \$ "--- Done. Tested "++ show tot++" values"

-- | Defined as @ioAll = 'ioFeat' 'values' @
ioAll :: Enumerable a => (a -> IO ()) -> IO ()
ioAll = ioFeat values

-- | Defined as @ioBounded n = 'ioFeat' ('bounded' n)@
ioBounded :: Enumerable a => Integer -> (a -> IO ()) -> IO ()
ioBounded n = ioFeat (bounded n)

-- | Compatibility with QuickCheck. Distribution is uniform generator over
-- values bounded by the given size. Typical use: @sized uniform@.
uniform :: Enumerable a => Int -> Gen a
uniform = uniformWith optimised

-- | Compatibility with SmallCheck.
toSeries :: Enumerable a => Int -> [a]
toSeries = toSeriesWith optimised

-- | Non class version of 'values'.
valuesWith :: Enumerate a -> [(Integer,[a])]
valuesWith e =
[(crd,[select e p i|i <- [0..crd - 1]])
|p <- [0..], let crd = card e p]

-- | Non class version of 'striped'.
stripedWith :: Enumerate a -> Part -> Index -> Integer -> [(Integer,[a])]
stripedWith e p o step = if space <= 0
then (0,[]) : stripedWith e (p+1) (o - crd) step
else (d,thisP) : stripedWith e (p+1) (step-m-1) step
where
thisP =
[select e p x|x <- genericTake d \$ iterate (+step) o]
space = crd - o
(d,m) = divMod space step
crd = card e p

-- | Non class version of 'bounded'.
boundedWith :: Enumerate a -> Integer -> [(Integer,[a])]
boundedWith e n = map (samplePart e n) [0..]

samplePart :: Enumerate a -> Index -> Part -> (Integer,[a])
samplePart e m p =
let
top   =  toRational \$ (card e p) - 1
step  =  top / toRational (m-1)
crd = card e p
in if toRational m >= top
then (crd, map (select e p) [0..crd - 1])
else let d = floor ((toRational crd)/ step) in
(d+1,[select e p (round (k * step))|k <- map toRational [0..d]])

-- | Non class version of 'uniform'.
uniformWith :: Enumerate a -> Part -> Gen a
uniformWith e maxp = let
cards  = [(card e x, x) | x <- [maxp, maxp-1 .. 0]]
tot    = sum \$ fst \$ unzip cards
in if tot == 0 then uniformWith e (maxp+1) else do
i <- choose (0,tot-1)
return \$ uncurry (select e) (lu i cards)
where
lu i ((crd,p):xs)  = if i<crd
then (p,i)
else lu (i-crd) xs

-- | Non class version of 'toSeries'.
toSeriesWith :: Enumerate a -> Int -> [a]
toSeriesWith e d = concat (take (d+1) \$ map snd \$ valuesWith e)
```