{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}

-- |
-- Copyright   : Maciej Goszczycki 2015
-- License     : GPL-3
-- Maintainer  : mgoszcz2@gmail.com
-- Stability   : experimental
-- Portability : portable
-- 
-- `Enumerable` provides a generalized equivalent to doing `[minBound..maxBound]` but on complex types.
-- 
-- > ([minBound..maxBound] :: [Word8]) == allEnums


module Data.Enumerable.Generic (
    -- * Example usage
    -- $usage

    -- ** Multi-consturctor types
    -- $constructors

    -- ** Without `Default` instances
    -- $nodefault
    
    -- * Caveats & Extending
    -- $info

    -- * Convenience functions
      allEnum
    , allConstEnum
    , allDefsEnum
    -- * Type-classes
    , Enumerable(..)
    , Defaults(..)
    -- * Extra `Default` instances
    , Default(..)
    ) where


import GHC.Generics
import Data.Default (Default, def)
import Control.Arrow (first, second)
import Data.Word (Word, Word8, Word16, Word32, Word64)
import Data.Int (Int8, Int16, Int32, Int64)


-- | Generate all possible variations of a type
allEnum :: (Default x, Enumerable x) => [x]
allEnum = allNext def

-- | Generate all possible variations of a type given a list of default values of constructors
allConstEnum :: (Enumerable x) => [x] -> [x]
allConstEnum = concatMap allNext

-- | Same as `allConstEnum` but automatically get constructors from `Defaults` class
allDefsEnum :: (Defaults x, Enumerable x) => [x]
allDefsEnum = allConstEnum defs


-- | Class used for deriving generic `Enumerable` instances
class GEnumerable x where
    gper :: x a -> (x a, Bool)


-- | Class for easier enumeration of multi-constructor types
class Defaults x where
    defs :: [x]


-- | Class for generating enumerations of arbitrary data types
class Enumerable x where
    -- | Minimum definition of `Enumerable`. Due to haskell's inability to
    -- deal with overlapping instances, only defined by `GEnumerable` derived types
    
    -- | Given a value returns next enumeration and a carry boolean, set to `True`
    -- if returning final enumeration
    per :: x -> (x, Bool)
    default per :: (Generic x, GEnumerable (Rep x)) => x -> (x, Bool)
    per = first to . gper . from

    -- | Symmetric `per`. Only enumerate if the carry boolean is `True`
    sper :: (x, Bool) -> (x, Bool)
    sper (x, False) = (x, False)
    sper (x, True) = per x

    -- | Next enumeration. Call `per` discarding the carry boolean
    next :: x -> x
    next = fst . per

    -- | Return all enumerations of a starting value
    allNext :: x -> [x]
    allNext x = map fst . takeWhile (not . snd) $ iterate (sper . second (const True)) (x, False)


-- | Default implementation of `per` used by all simple `Enumerable` instances
-- that are also instances of `Eq`, `Enum` and `Bounded`
defEnumerable :: (Eq x, Enum x, Bounded x) => x -> (x, Bool)
defEnumerable x = if maxBound == x then (minBound, True) else (succ x, False)


-- | Missing `Bool` `Default` instance
instance Default Bool where def = False
-- | Default `Char` is NULL
instance Default Char where def = '\0'
-- | Default `Either` is Left (a false value just like Default Bool)
instance (Default a, Default b) => Default (Either a b) where
    def = Left def

instance Enumerable Bool where per = defEnumerable
instance Enumerable Char where per = defEnumerable
instance Enumerable Int where per = defEnumerable
instance Enumerable Int8 where per = defEnumerable
instance Enumerable Int16 where per = defEnumerable
instance Enumerable Int32 where per = defEnumerable
instance Enumerable Int64 where per = defEnumerable
instance Enumerable Ordering where per = defEnumerable
instance Enumerable Word where per = defEnumerable
instance Enumerable Word8 where per = defEnumerable
instance Enumerable Word16 where per = defEnumerable
instance Enumerable Word32 where per = defEnumerable
instance Enumerable Word64 where per = defEnumerable
instance Enumerable () where per = defEnumerable

instance (Default a, Default b, Enumerable a, Enumerable b) => Enumerable (Either a b) where
    per (Left a) = (if carry then Right def else Left nexta, False)
        where (nexta, carry) = per a
    per (Right b) = (if carry then Left def else Right nextb, carry)
        where (nextb, carry) = per b

instance (Default x, Enumerable x) => Enumerable (Maybe x) where
    per Nothing = (Just def, False)
    per (Just a) = (if carry then Nothing else Just nexta, carry)
        where (nexta, carry) = per a

-- | Enumerates left followed by right
instance (Enumerable a, Enumerable b) => Enumerable (a, b) where
    per (a, b) = if carrya then ((a', b'), carryb) else ((a', b), False)
        where (a', carrya) = per a
              (b', carryb) = per b


instance (GEnumerable x, GEnumerable y) => GEnumerable (x :*: y) where
    gper (x :*: y) = if carry then first (ntype :*:) $ gper y
                              else (ntype :*: y, False)
        where (ntype, carry) = gper x

instance (GEnumerable x, GEnumerable y) => GEnumerable (x :+: y) where
    gper (L1 x) = first L1 $ gper x
    gper (R1 x) = first R1 $ gper x

instance (GEnumerable x) => GEnumerable (M1 i c x) where
    gper (M1 x) = first M1 $ gper x

instance (Enumerable x) => GEnumerable (K1 i x) where
    gper (K1 x) = first K1 $ per x

instance GEnumerable (U1) where
    gper U1 = (U1, True)


-- $info
-- `Defaults` class is just a way of providing default arguments to `allConstEnum`
-- 
-- Elegantly handling multi-constructor types with type-variables and implementing
-- the corresponding `Enumerable` instance is left to the user. Implementations for `Maybe` and `Either`
-- and provided out of the box


-- $usage
-- > λ :set -XDeriveGeneric
-- > λ data Flag = Flag Bool Word8 deriving (Show, Generic)
-- > λ instance Enumerable Flag
-- > λ instance Default Flag where def = Flag False 0
-- > λ allEnum :: [Flag]
-- > [Flag False 0,Flag True 0,Flag False 1,Flag True 1, (..snip..) Flag False 255,Flag True 255]


-- $constructors
-- > λ data Value = A Bool | B | C deriving (Show, Generic)
-- > λ instance Enumerable Value
-- > λ allConstEnum [A False, B, C]
-- > [A False,A True,B,C]
-- > λ instance Defaults Value where defs = [A False, B, C]
-- > λ allDefsEnum :: [Value]
-- > [A False,A True,B,C]


-- $nodefault
-- > λ next False
-- > True
-- > λ next $ 8086 :: Int
-- > 8087
-- > λ next $ Flag True 42
-- > Flag False 43