#ifdef GENERICS
#endif
module Test.SmallCheck.Series (
  
  Depth, Series, Serial(..),
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  cons0, cons1, cons2, cons3, cons4,
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  alts0, alts1, alts2, alts3, alts4,
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  (\/), (><),
  N(..), Nat, Natural,
  depth
  ) where
import Data.List (intersperse)
#ifdef GENERICS
import GHC.Generics
import Data.DList (DList, toList, fromList)
import Data.Monoid (mempty, mappend)
#endif
type Depth = Int
type Series a = Depth -> [a]
infixr 7 \/
(\/) :: Series a -> Series a -> Series a
s1 \/ s2 = \d -> s1 d ++ s2 d
infixr 8 ><
(><) :: Series a -> Series b -> Series (a,b)
s1 >< s2 = \d -> [(x,y) | x <- s1 d, y <- s2 d]
class Serial a where
  series   :: Series a
  coseries :: Series b -> Series (a->b)
#ifdef GENERICS
  default series :: (Generic a, GSerial (Rep a)) => Series a
  series = map to . gSeries
  default coseries :: (Generic a, GSerial (Rep a)) => Series b -> Series (a->b)
  coseries rs = map (. from) . gCoseries rs
class GSerial f where
  gSeries   :: Series (f a)
  gCoseries :: Series b -> Series (f a -> b)
instance GSerial f => GSerial (M1 i c f) where
  gSeries      = map M1 . gSeries
  gCoseries rs = map (. unM1) . gCoseries rs
  
  
instance Serial c => GSerial (K1 i c) where
  gSeries      = map K1 . series
  gCoseries rs = map (. unK1) . coseries rs
  
  
instance GSerial U1 where
  gSeries        = cons0 U1
  gCoseries rs d = [\U1 -> b | b <- rs d]
  
  
instance (GSerial a, GSerial b) => GSerial (a :*: b) where
  gSeries    d = [x :*: y | x <- gSeries d, y <- gSeries d]
  gCoseries rs = map uncur . gCoseries (gCoseries rs)
      where
        uncur f (x :*: y) = f x y
  
  
instance (GSerialSum a, GSerialSum b) => GSerial (a :+: b) where
  gSeries   = toList . gSeriesSum
  gCoseries = gCoseriesSum
  
  
class GSerialSum f where
  gSeriesSum   :: DSeries (f a)
  gCoseriesSum :: Series b -> Series (f a -> b)
type DSeries a = Depth -> DList a
instance (GSerialSum a, GSerialSum b) => GSerialSum (a :+: b) where
  gSeriesSum      d = fmap L1 (gSeriesSum d) `mappend` fmap R1 (gSeriesSum d)
  gCoseriesSum rs d = [ \e -> case e of
                                L1 x -> f x
                                R1 y -> g y
                      | f <- gCoseriesSum rs d
                      , g <- gCoseriesSum rs d
                      ]
  
  
instance GSerial f => GSerialSum (C1 c f) where
  gSeriesSum      d | d > 0     = fromList $ gSeries (d1)
                    | otherwise = mempty
  gCoseriesSum rs d | d > 0     = gCoseries rs (d1)
                    | otherwise = [\_ -> x | x <- rs d]
  
  
#endif
instance Serial () where
  series      _ = [()]
  coseries rs d = [ \() -> b
                  | b <- rs d ]
instance Serial Int where
  series      d = [(d)..d]
  coseries rs d = [ \i -> if i > 0 then f (N (i  1))
                          else if i < 0 then g (N (abs i  1))
                          else z
                  | z <- alts0 rs d, f <- alts1 rs d, g <- alts1 rs d ]
instance Serial Integer where
  series      d = [ toInteger (i :: Int)
                  | i <- series d ]
  coseries rs d = [ f . (fromInteger :: Integer->Int)
                  | f <- coseries rs d ]
newtype N a = N a
              deriving (Eq, Ord)
instance Show a => Show (N a) where
  show (N i) = show i
instance (Integral a, Serial a) => Serial (N a) where
  series      d = map N [0..d']
                  where
                  d' = fromInteger (toInteger d)
  coseries rs d = [ \(N i) -> if i > 0 then f (N (i  1))
                              else z
                  | z <- alts0 rs d, f <- alts1 rs d ]
type Nat = N Int
type Natural = N Integer
instance Serial Float where
  series     d = [ encodeFloat sig exp
                 | (sig,exp) <- series d,
                   odd sig || sig==0 && exp==0 ]
  coseries rs d = [ f . decodeFloat
                  | f <- coseries rs d ]
instance Serial Double where
  series      d = [ frac (x :: Float)
                  | x <- series d ]
  coseries rs d = [ f . (frac :: Double->Float)
                  | f <- coseries rs d ]
frac :: (Real a, Fractional a, Real b, Fractional b) => a -> b
frac = fromRational . toRational
instance Serial Char where
  series      d = take (d+1) ['a'..'z']
  coseries rs d = [ \c -> f (N (fromEnum c  fromEnum 'a'))
                  | f <- coseries rs d ]
instance (Serial a, Serial b) =>
         Serial (a,b) where
  series      = series >< series
  coseries rs = map uncurry . (coseries $ coseries rs)
instance (Serial a, Serial b, Serial c) =>
         Serial (a,b,c) where
  series      = \d -> [(a,b,c) | (a,(b,c)) <- series d]
  coseries rs = map uncurry3 . (coseries $ coseries $ coseries rs)
instance (Serial a, Serial b, Serial c, Serial d) =>
         Serial (a,b,c,d) where
  series      = \d -> [(a,b,c,d) | (a,(b,(c,d))) <- series d]
  coseries rs = map uncurry4 . (coseries $ coseries $ coseries $ coseries rs)
uncurry3 :: (a->b->c->d) -> ((a,b,c)->d)
uncurry3 f (x,y,z) = f x y z
uncurry4 :: (a->b->c->d->e) -> ((a,b,c,d)->e)
uncurry4 f (w,x,y,z) = f w x y z
cons0 ::
         a -> Series a
cons0 c _ = [c]
cons1 :: Serial a =>
         (a->b) -> Series b
cons1 c d = [c z | d > 0, z <- series (d1)]
cons2 :: (Serial a, Serial b) =>
         (a->b->c) -> Series c
cons2 c d = [c y z | d > 0, (y,z) <- series (d1)]
cons3 :: (Serial a, Serial b, Serial c) =>
         (a->b->c->d) -> Series d
cons3 c d = [c x y z | d > 0, (x,y,z) <- series (d1)]
cons4 :: (Serial a, Serial b, Serial c, Serial d) =>
         (a->b->c->d->e) -> Series e
cons4 c d = [c w x y z | d > 0, (w,x,y,z) <- series (d1)]
alts0 ::  Series a ->
            Series a
alts0 as d = as d
alts1 ::  Serial a =>
            Series b -> Series (a->b)
alts1 bs d = if d > 0 then coseries bs (dec d)
             else [\_ -> x | x <- bs d]
alts2 ::  (Serial a, Serial b) =>
            Series c -> Series (a->b->c)
alts2 cs d = if d > 0 then coseries (coseries cs) (dec d)
             else [\_ _ -> x | x <- cs d]
alts3 ::  (Serial a, Serial b, Serial c) =>
            Series d -> Series (a->b->c->d)
alts3 ds d = if d > 0 then coseries (coseries (coseries ds)) (dec d)
             else [\_ _ _ -> x | x <- ds d]
alts4 ::  (Serial a, Serial b, Serial c, Serial d) =>
            Series e -> Series (a->b->c->d->e)
alts4 es d = if d > 0 then coseries (coseries (coseries (coseries es))) (dec d)
             else [\_ _ _ _ -> x | x <- es d]
instance Serial Bool where
  series        = cons0 True \/ cons0 False
  coseries rs d = [ \x -> if x then r1 else r2
                  | r1 <- rs d, r2 <- rs d ]
instance Serial a => Serial (Maybe a) where
  series        = cons0 Nothing \/ cons1 Just
  coseries rs d = [ \m -> case m of
                       Nothing -> z
                       Just x  -> f x
                  |  z <- alts0 rs d ,
                     f <- alts1 rs d ]
instance (Serial a, Serial b) => Serial (Either a b) where
  series        = cons1 Left \/ cons1 Right
  coseries rs d = [ \e -> case e of
                          Left x  -> f x
                          Right y -> g y
                  |  f <- alts1 rs d ,
                     g <- alts1 rs d ]
instance Serial a => Serial [a] where
  series        = cons0 [] \/ cons2 (:)
  coseries rs d = [ \xs -> case xs of
                           []      -> y
                           (x:xs') -> f x xs'
                  |   y <- alts0 rs d ,
                      f <- alts2 rs d ]
instance (Serial a, Serial b) => Serial (a->b) where
  series = coseries series
  coseries rs d =
    [ \ f -> g [ f a | a <- args ]
    | g <- nest args d ]
    where
    args = series d
    nest []     _ = [ \[] -> c
                    | c <- rs d ]
    nest (a:as) _ = [ \(b:bs) -> f b bs
                    | f <- coseries (nest as) d ]
depth :: Depth -> Depth -> Depth
depth d d' | d >= 0    = d'+1d
           | otherwise = error "SmallCheck.depth: argument < 0"
dec :: Depth -> Depth
dec d | d > 0     = d1
      | otherwise = error "SmallCheck.dec: argument <= 0"
inc :: Depth -> Depth
inc d = d+1
instance (Serial a, Show a, Show b) => Show (a->b) where
  show f =
    if maxarheight == 1
    && sumarwidth + length ars * length "->;" < widthLimit then
      "{"++(
      concat $ intersperse ";" $ [a++"->"++r | (a,r) <- ars]
      )++"}"
    else
      concat $ [a++"->\n"++indent r | (a,r) <- ars]
    where
    ars = take lengthLimit [ (show x, show (f x))
                           | x <- series depthLimit ]
    maxarheight = maximum  [ max (height a) (height r)
                           | (a,r) <- ars ]
    sumarwidth = sum       [ length a + length r
                           | (a,r) <- ars]
    indent = unlines . map ("  "++) . lines
    height = length . lines
    (widthLimit,lengthLimit,depthLimit) = (80,20,3)::(Int,Int,Depth)