{-# LANGUAGE FlexibleContexts, TypeOperators #-}
-- |
-- Module      : Test.LeanCheck.Generic
-- Copyright   : (c) 2018 Rudy Matela
-- License     : 3-Clause BSD  (see the file LICENSE)
-- Maintainer  : Rudy Matela <rudy@matela.com.br>
--
-- This module is part of LeanCheck,
-- a simple enumerative property-based testing library.
--
-- This is an experimental module for deriving 'Listable' instances through
-- GHC's generic.
--
-- If you rather do this through Template Haskell please see:
-- "Test.LeanCheck.Derive".
module Test.LeanCheck.Generic
  ( genericList
  , genericTiers
  )
where

import GHC.Generics
import Test.LeanCheck.Core

-- | A generic implementation of 'list' for instances of 'Generic'.
--
-- Use it to define your 'Listable' instances like so:
--
-- > instance Listable MyType where
-- >   list = genericList
--
-- Consider using 'genericTiers' instead of this
-- (unless you know what you're doing).
genericList :: (Generic a, Listable' (Rep a)) => [a]
genericList = concat genericTiers

-- | A generic implementation of 'tiers' for instances of 'Generic'.
--
-- Use it to define your 'Listable' instances like so:
--
-- > instance Listable MyType where
-- >   tiers = genericTiers
genericTiers :: (Generic a, Listable' (Rep a)) => [[a]]
genericTiers = mapT to tiers'

class Listable' f where
  tiers' :: [[f p]]

instance Listable' V1 where
  tiers' = undefined

instance Listable' U1 where
  tiers' = [[U1]]

instance Listable c => Listable' (K1 i c) where
  tiers' = mapT K1 tiers

instance (Listable' a, Listable' b) => Listable' (a :+: b) where
  tiers' = mapT L1 tiers' \/ mapT R1 tiers'

instance (Listable' a, Listable' b) => Listable' (a :*: b) where
  tiers' = productWith (:*:) tiers' tiers'

instance Listable' f => Listable' (M1 i c f) where
  tiers' = mapT M1 tiers'