{-# LANGUAGE
    FlexibleContexts
  , FlexibleInstances
  , ScopedTypeVariables
  , TypeOperators
  #-}
-- | Test if a data type is an enumeration (only zero-argument
-- constructors) generically using "GHC.Generics".
module Generics.Generic.IsEnum
  ( isEnum
  , GIsEnum (..)
  ) where

import Data.Proxy

import GHC.Generics

-- | Class for testing if the functors from "GHC.Generics" are
-- enumerations. You generally don't need to give any custom
-- instances. Just call 'isEnum'.
class GIsEnum f where
  gIsEnum :: Proxy (f a) -> Bool


instance GIsEnum V1 where
  gIsEnum _ = False

instance GIsEnum (K1 i a) where
  gIsEnum _ = False

instance GIsEnum U1 where
  gIsEnum _ = True

instance GIsEnum Par1 where
  gIsEnum _ = False

-- should be K1 R
instance GIsEnum (Rec1 f) where
  gIsEnum _ = False


instance (GIsEnum f, GIsEnum g) => GIsEnum (f :+: g) where
  gIsEnum _ = gIsEnum (Proxy :: Proxy (f a)) && gIsEnum (Proxy :: Proxy (g a))

instance (GIsEnum f, GIsEnum g) => GIsEnum (f :*: g) where
  gIsEnum _ = False

instance GIsEnum f => GIsEnum (M1 C c f) where
  gIsEnum _ = gIsEnum (Proxy :: Proxy (f a))

instance GIsEnum (M1 S c a) where
  gIsEnum _ = False

instance GIsEnum f => GIsEnum (M1 D c f) where
  gIsEnum _ = gIsEnum (Proxy :: Proxy (f a))

-- instance GIsEnum (f :.: g) where

-- | Generically test if a data type is an enumeration.
isEnum :: forall a. (Generic a, GIsEnum (Rep a)) => Proxy a -> Bool
isEnum _ = gIsEnum (Proxy :: Proxy ((Rep a) a))