{-# LANGUAGE CPP                  #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE DefaultSignatures    #-}
{-# LANGUAGE DeriveFunctor        #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE RankNTypes           #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE UndecidableInstances #-}

-- | Generate fake values with full constructor coverage
--
-- The idea behind Fake.Cover is that although exhaustive testing is highly
-- exponential, you can cover a large portion of the likely problem cases by
-- exercising all the constructors of a data type and associated fields. This
-- approach only requires a sub-exponential number of cases--far fewer than what
-- you need for the exhaustive approach. The number of test cases needed to
-- ensure that you have full coverage of all the constructors is given by the
-- following relations for product and sum types:
--
-- numCases (a, b) = max (numCases a) (numCases b)
--
-- numCases (Either a b) = numCases a + numCases b
--
-- See the test suite for examples of how many values are generated for
-- different data types.
module Fake.Cover
  ( gcover
  , Coverage(..)
  , Cover(..)
  ) where

------------------------------------------------------------------------------
import Control.Applicative
import Control.Monad
import GHC.Generics as G
------------------------------------------------------------------------------
import Fake.Types
------------------------------------------------------------------------------


------------------------------------------------------------------------------
-- | Coverage is a list of values, implemented here with a newtype around a
-- list of fake value generators.  It's @[FGen a]@ instead of @FGen [a]@
-- because we don't want to have to evaluate the 'FGen' monad to work with
-- coverage lists.
newtype Coverage a = Coverage { unCoverage :: [FGen a] }
  deriving (Functor)

instance Applicative Coverage where
  pure = Coverage . pure . pure
  Coverage as <*> Coverage bs = Coverage $ zipWith (<*>)
     (as ++ take (newlen - alen) (cycle as))
     (bs ++ take (newlen - blen) (cycle bs))
   where
    alen = length as
    blen = length bs
    newlen = max alen blen

instance Alternative Coverage where
  empty = Coverage empty
  Coverage as <|> Coverage bs = Coverage (as ++ bs)


------------------------------------------------------------------------------
-- | A type class that generates a list of values giving full construcor
-- coverage for data types.  You can write your own instances by hand or you
-- can use the default instance which calls 'gcover' provided your data type
-- has a Generic instance.
class Cover a where
    cover :: Coverage a
    default cover :: (Generic a, GCover ga, ga ~ G.Rep a) => Coverage a
    cover = gcover

instance Cover () where
    cover = gcover

instance Cover a => Cover (Maybe a) where
    cover = gcover

instance (Cover a, Cover b) => Cover (Either a b) where
    cover = gcover

instance (Cover a, Cover b) => Cover (a,b) where
    cover = gcover

instance (Cover a, Cover b, Cover c) => Cover (a,b,c) where
    cover = gcover

instance (Cover a, Cover b, Cover c, Cover d) => Cover (a,b,c,d) where
    cover = gcover

instance (Cover a, Cover b, Cover c, Cover d, Cover e)
      => Cover (a,b,c,d,e) where
    cover = gcover

instance (Cover a, Cover b, Cover c, Cover d, Cover e, Cover f)
      => Cover (a,b,c,d,e,f) where
    cover = gcover

instance (Cover a, Cover b, Cover c, Cover d, Cover e, Cover f, Cover g)
      => Cover (a,b,c,d,e,f,g) where
    cover = gcover
-- GHC only has Generic instances up to 7-tuples


------------------------------------------------------------------------------
-- | A generic function that gives you full constructor coverage for a data
-- type.  Using this function as the 'Cover' instance for a data type avoids
-- the need to explicitly enumerate values that include coverage of all
-- constructors.
gcover :: (Generic a, GCover ga, ga ~ G.Rep a) => Coverage a
gcover = Coverage $ fmap G.to <$> genericCover


------------------------------------------------------------------------------
-- | Used to implement 'gcover'.
class GCover a where
    genericCover :: [FGen (a x)]

instance GCover G.U1 where
    genericCover = pure $ pure G.U1

instance Cover c => GCover (G.K1 i c) where
    genericCover = fmap G.K1 <$> unCoverage cover

instance GCover f => GCover (G.M1 i c f) where
    genericCover = fmap G.M1 <$> genericCover

instance (GCover a, GCover b) => GCover (a G.:*: b) where
    genericCover = zipWith (liftM2 (G.:*:))
                     (acover ++ take (newlen - alen) (cycle acover))
                     (bcover ++ take (newlen - blen) (cycle bcover))
      where
        acover = genericCover :: [FGen (a x)]
        alen = length acover
        bcover = genericCover :: [FGen (b x)]
        blen = length bcover
        newlen = max alen blen

instance (GCover a, GCover b) => GCover (a G.:+: b) where
    genericCover = fmap (fmap G.L1) genericCover ++
                   fmap (fmap G.R1) genericCover