{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

-- | In this module we provide a way to canonically define a totally
-- ordered set with a given number of elements.  These types have a
-- custom @'Show'@ instances so that their elements are displayed with
-- usual decimal number.
--
--
-- @'One'@ = {'One'} = {1}
--
-- @'Succ' 'One'@ = {@'First'@, @'Succ' 'One'@} = {1,2}
--
-- @'Succ' 'Succ' 'One'@ = {@'First'@, @'Succ' 'First'@, @'Succ'
-- 'Succ' 'One'@} = {1,2,3}
--
-- ...
module Data.Ordinal
    ( One(..)
    , Succ(..)

    , Two
    , Three
    , Four
    , Five
    , Six
    , Seven
    , Eight
    , Nine
    , Ten

    , Ordinal(..)

    , module Data.TypeAlgebra

    ) where

import           Data.Cardinal hiding (Succ)
import qualified Data.Cardinal as C
import           Data.Ord()
import           Data.TypeAlgebra
import qualified GHC.Generics as G

-- | A set with one element.
data One = One
           deriving (Eq, G.Generic)

instance Bounded One where
    minBound = One
    maxBound = One


instance Ord One where
    compare One One = EQ


instance Ordinal One where
    fromOrdinal One = 1
    toOrdinal 1 = One
    toOrdinal _ = error "(toOrdinal n): n is out of bounds"

instance Enum One where
    succ _ = error "Prelude.Enum.One.succ: bad argument"
    pred _ = error "Prelude.Enum.One.succ: bad argument"
    toEnum = toOrdinal
    fromEnum = fromOrdinal
    enumFrom _ = [One]
    enumFromThen _ _ = [One]
    enumFromTo _ _ = [One]
    enumFromThenTo _ _ _ = [One]

instance Show One where
    show One = "1"

-- | If @n@ is a set with n elements, @'Succ' n@ is a set with n+1 elements.
data Succ n = First -- ^ The first element of the type.
            | Succ n -- ^ The last @n@ elements.
              deriving (Eq, G.Generic)

type Two = Succ One
type Three = Succ Two
type Four = Succ Three
type Five = Succ Four
type Six = Succ Five
type Seven = Succ Six
type Eight = Succ Seven
type Nine = Succ Eight
type Ten = Succ Nine

instance (Bounded n) => Bounded (Succ n) where
    minBound = First
    maxBound = Succ (maxBound)


instance (Ord n) => Ord (Succ n) where
    compare (Succ x) (Succ y) = compare x y
    compare (Succ _) First = GT
    compare First (Succ _) = LT
    compare First First = EQ


instance (Ordinal n) => Ordinal (Succ n) where
    fromOrdinal First = 1
    fromOrdinal (Succ x) = 1 + fromOrdinal x
    toOrdinal x | x == 1 = First
                | otherwise = Succ (toOrdinal (x - 1))

instance (Bounded n, Enum n, Ordinal n) => Enum (Succ n) where
    succ First = Succ minBound
    succ (Succ n) = Succ (succ n)
    toEnum = toOrdinal
    fromEnum = fromOrdinal
    enumFrom     x   = enumFromTo     x maxBound
    enumFromThen x y = enumFromThenTo x y bound
        where
          bound | fromEnum y >= fromEnum x = maxBound
                | otherwise                = minBound


instance (Ordinal n) => Show (Succ n) where
    show x = show (fromOrdinal x :: Integer)

instance Functor Succ where
    fmap _ First = First
    fmap f (Succ x) = Succ (f x)

instance Monad Succ where
    First >>= _ = First
    (Succ x) >>= f = f x
    return x = Succ x


instance Cardinality One where
    type Card One = C.Succ Zero

instance Cardinality n => Cardinality (Succ n) where
    type Card (Succ n) = C.Succ (Card n)


-- | Class of ordered sets with n elements. The methods in this class
-- provide a convenient way to convert to and from a numeric type.
class (Cardinality n, Ord n) => Ordinal n where
    fromOrdinal :: (Num i) => n -> i
    toOrdinal :: (Eq i, Num i) => i -> n

instance (Ordinal m) => Sum m One where
    type m :+: One = Succ m
    x <+> One = Succ x

instance (Ordinal m, Ordinal n, Ordinal (m :+: n), Sum m n) =>
    Sum m (Succ n) where
        type m :+: (Succ n) = Succ (m :+: n)
        x <+> First = toOrdinal (1 + fromOrdinal x :: Integer)
        x <+> (Succ y) = Succ (x <+> y)


instance (Ordinal m) => Prod m One where
    type m :*: One = m
    x <*> One = toOrdinal (fromOrdinal x :: Integer)

instance (Ordinal m, Ordinal n, Prod m n, Sum m (m :*: n), Ordinal (m :+: (m :*: n)))
    => Prod m (Succ n) where
        type m :*: (Succ n) = m :+: (m :*: n)
        x <*> First = toOrdinal (fromOrdinal x :: Integer)
        x <*> (Succ y) = x <+> (x <*> y)