{-# LANGUAGE TypeFamilies, ExplicitNamespaces, TypeOperators, FlexibleInstances #-}
{-# LANGUAGE DataKinds, UndecidableInstances, ConstraintKinds, KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables, FlexibleContexts #-}

{-| the cardinality of a finite type, at the type-level.

-}
module Enumerate.Cardinality where

import           GHC.Generics
import Data.Vinyl (Rec)
import           Data.Proxy (Proxy)
import           Data.Void (Void)
import           Data.Word (Word8, Word16)
import           Data.Int (Int8, Int16)
import Data.Set (Set)
import Numeric.Natural (Natural)
import GHC.TypeLits (Nat, KnownNat, natVal, type (+), type (*), type (^), type (<=?))
import           Data.Proxy (Proxy(..))
import Spiros.Prelude

-- alternatives:
-- class Finite a where
-- type GenericCardinality a = GCardinality (Rep a)
-- class Cardinality a n
-- class Finite a where  type Cardinality a :: Nat
  {- needs DefaultTypeInstances,
  or we have to pick between deriving instances (the user should)
  and manually providing them (the author should, for base types like Char,
  because their Generic rep is huge and slows down the compiler to a stop)
 -}
 -- class GFinite a where
-- default type (Generic a) => Cardinality a = GCardinality (Rep a)
-- type instance {-# OVERLAPS #-} (Generic a) => Cardinality a = GCardinality (Rep a)

{-| a type is finite, i.e. has a bounded size.

laws:

 * consistent with "Enumerate.Enumerable":

     * @'cardinality' = 'reifyCardinality'@

     i.e. the value-level (a 'Natural') matches the type-level (a 'Nat')

e.g.

>>> reifyCardinality ([]::[Bool])
2

-}
class Finite a where
  type Cardinality a :: Nat
  type Cardinality a = GCardinality (Rep a)

-- base types. TODO any more?

-- | @0@
instance Finite Void
-- | @1@
instance Finite ()
-- | @2@
instance Finite Bool
-- | @3@
instance Finite Ordering

instance Finite (Proxy a) where
 type Cardinality (Proxy a) = 1

-- | @2^8@
instance Finite Int8 where
 type Cardinality Int8 = 256

-- | @2^8@
instance Finite Word8 where
 type Cardinality Word8 = 256

-- | @2^16@
instance Finite Int16 where
  type Cardinality Int16 = 65536

-- | @2^16@
instance Finite Word16 where
 type Cardinality Word16 = 65536

-- | @1114112@
instance Finite Char where
 type Cardinality Char = 1114112

-- | @1 + a@
instance (Finite a) => Finite (Maybe a) where
 type Cardinality (Maybe a) = 1 + (Cardinality a)

-- | @a + b@
instance (Finite a, Finite b) => Finite (Either a b) where
 type Cardinality (Either a b) = (Cardinality a) + (Cardinality b)

{-| the cardinality is a product of cardinalities. -}
instance (Finite (f a), Finite (Rec f as)) => Finite (Rec f (a ': as)) where
 type Cardinality (Rec f (a ': as)) = (Cardinality (f a)) * (Cardinality (Rec f as))

 -- | @1@
instance Finite (Rec f '[]) where
 type Cardinality (Rec f '[]) = 1

{-
class Finite (Mod i n) where
 type Cardinality (Mod i n) = n
-}

-- | @a*b@
instance (Finite a, Finite b) => Finite (a, b)

-- | @a*b*c@
instance (Finite a, Finite b, Finite c) => Finite (a, b, c)
-- | @a*b*c*d@
instance (Finite a, Finite b, Finite c, Finite d) => Finite (a, b, c, d)
-- | @a*b*c*d*e@
instance (Finite a, Finite b, Finite c, Finite d, Finite e) => Finite (a, b, c, d, e)
-- | @a*b*c*d*e*f@
instance (Finite a, Finite b, Finite c, Finite d, Finite e, Finite f) => Finite (a, b, c, d, e, f)
-- | @a*b*c*d*e*f*g@
instance (Finite a, Finite b, Finite c, Finite d, Finite e, Finite f, Finite g) => Finite (a, b, c, d, e, f, g)

-- | @2^a@
instance (Finite a) => Finite (Set a) where
 type Cardinality (Set a) = 2 ^ (Cardinality a)

-- | @b^a@
instance (Finite a, Finite b) => Finite (a -> b) where
 type Cardinality (a -> b) = (Cardinality b) ^ (Cardinality a)

--------------------------------------------------------------------------------

type family GCardinality (f :: * -> *) :: Nat

type instance GCardinality (V1) = 0

type instance GCardinality (U1) = 1

type instance GCardinality (K1 i a) = Cardinality a

type instance GCardinality (f :+: g) = (GCardinality f) + (GCardinality g)

type instance GCardinality (f :*: g) = (GCardinality f) * (GCardinality g)

type instance GCardinality (M1 i t f) = GCardinality f

--------------------------------------------------------------------------------

{-|

>>> reifyCardinality ([]::[Bool])
2

-}
reifyCardinality
 :: forall a proxy. (KnownNat (Cardinality a))
 => proxy a
 -> Natural
reifyCardinality _ = fromInteger (natVal (Proxy::Proxy (Cardinality a)))


{-| typechecks only when the constraint is satisifed.

a constaint.

-}
type CardinalityWithin n a = IsCardinalityWithin n a ~ True

{-|

a predicate, inclusive.

@
> type CardinalityWithinAMillion a = CardinalityWithin 1000000 a
> :kind! CardinalityWithinAMillion Bool
True
> :kind! CardinalityWithinAMillion Char
False
@

-}
type IsCardinalityWithin n a = Cardinality a <=? n

{-
>>> :set -XDataKinds
>>> :set -XConstraintKinds
>>> :set -XTypeFamilies
>>> type CardinalityWithinAMillion a = CardinalityWithin 1000000 a
>>> :kind! CardinalityWithinAMillion Bool
True
>>> :kind! CardinalityWithinAMillion Char
False
-}

-- {-| enumerate only when the cardinality is small enough.
--
-- >>> enumerateWithin 2 :: Either Natural [Bool]
-- Left 2
--
-- >>> enumerateWithin 100 :: Either Natural [Bool]
-- Right [False,True]
--
-- useful when you've established that traversing a list below some length
-- and consuming its values is reasonable for your application.
-- e.g. after benchmarking, you think you can process a billion entries within a minute.
--
-- -}
-- enumerateWithin :: forall a. (Enumerable a) => Natural -> Either Natural [a] --TODO move
-- enumerateWithin maxSize = if theSize < maxSize
--   then Right enumerated
--   else Left theSize
--  where
--  theSize = cardinality (Proxy :: Proxy a)