{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wall #-}

-- | A bit of extra Ordering taken from gaia.
module NumHask.Algebra.Ordering (
    -- * lattice
    POrd(..)
  , POrdering(..)
  , Topped(..)
  , Bottomed(..)
  , Bounded
  , Negated(..)
  , Semilattice
  , Lattice(..)
  , ord2pord
  ) where

import qualified Protolude as P
import Protolude (Double, Float, Int, Integer, Bool(..), Ord(..), Eq(..), fst)
import Data.Coerce
import NumHask.Algebra.Magma

-- | Equal to, Less than, Greater than, Not comparable to
data POrdering = PEQ | PLT | PGT | PNC

-- | P's just to avoid name clashes
class POrd s where pcompare :: s -> s -> POrdering

-- | POrd
instance (Ord a) => POrd a where
    pcompare n m
        | n > m = PGT
        | n == m = PEQ
        | P.otherwise = PLT

-- | conversion
ord2pord :: P.Ordering -> POrdering
ord2pord P.EQ = PEQ
ord2pord P.LT = PLT
ord2pord P.GT = PGT

-- | Topped
class POrd s => Topped s where top :: s

-- | Bottomed
class POrd s => Bottomed s where bottom :: s

-- | Semilattice
class ( Associative a
      , Commutative a
      , Idempotent a) =>
      Semilattice a

-- | Replaces the Bounded in base.  Is this a good idea?
class ( Topped a
      , Bottomed a) =>
      Bounded a

instance Topped Int where top = P.maxBound
instance Bottomed Int where bottom = P.minBound
instance Bounded Int

instance Topped Bool where top = True
instance Bottomed Bool where bottom = False
instance Bounded Bool

-- | a nice Lattice, but the types explode the instance requirements
class (
    Coercible a (Sup a)
  , Coercible a (Inf a)
  , Semilattice (Sup a)
  , Semilattice (Inf a)
  , POrd a
  ) => Lattice a where
    type Inf a
    type Sup a
    (/\) :: a -> a -> a
    (/\) = coerce (() :: Sup a -> Sup a -> Sup a)
    (\/) :: a -> a -> a
    (\/) = coerce (() :: Inf a -> Inf a -> Inf a)

-- | which creates a nice alternative for negate
class (Lattice a, Isomorphic (Inf a) (Sup a) ) => Negated a where
    negated :: a -> a
    negated a = coerce (fst isomorph (coerce a :: Inf a) :: Sup a) :: a

-- Int
newtype InfInt = InfInt Int
newtype SupInt = SupInt Int

instance Magma InfInt where
    InfInt a  InfInt b = InfInt (if a <= b then a else b)

instance Magma SupInt where
    SupInt a  SupInt b = SupInt (if a >= b then a else b)

instance Associative InfInt
instance Associative SupInt

instance Commutative SupInt
instance Commutative InfInt

instance Idempotent SupInt
instance Idempotent InfInt

instance Homomorphic SupInt InfInt where hom (SupInt a) = InfInt (-a)
instance Homomorphic InfInt SupInt where hom (InfInt a) = SupInt (-a)

instance Isomorphic SupInt InfInt where isomorph = (hom, hom)
instance Isomorphic InfInt SupInt where isomorph = (hom, hom)

instance Semilattice SupInt
instance Semilattice InfInt

instance Lattice Int where
    type Inf Int = InfInt
    type Sup Int = SupInt

-- Integer
newtype InfInteger = InfInteger Integer
newtype SupInteger = SupInteger Integer

instance Magma InfInteger where
    InfInteger a  InfInteger b = InfInteger (if a <= b then a else b)

instance Magma SupInteger where
    SupInteger a  SupInteger b = SupInteger (if a >= b then a else b)

instance Associative InfInteger
instance Associative SupInteger

instance Commutative SupInteger
instance Commutative InfInteger

instance Idempotent SupInteger
instance Idempotent InfInteger

instance Homomorphic SupInteger InfInteger where hom (SupInteger a) = InfInteger (-a)
instance Homomorphic InfInteger SupInteger where hom (InfInteger a) = SupInteger (-a)

instance Isomorphic SupInteger InfInteger where isomorph = (hom, hom)
instance Isomorphic InfInteger SupInteger where isomorph = (hom, hom)

instance Semilattice SupInteger
instance Semilattice InfInteger

instance Lattice Integer where
    type Inf Integer = InfInteger
    type Sup Integer = SupInteger

-- Float
newtype InfFloat = InfFloat Float
newtype SupFloat = SupFloat Float

instance Magma InfFloat where
    InfFloat a  InfFloat b = InfFloat (if a <= b then a else b)

instance Magma SupFloat where
    SupFloat a  SupFloat b = SupFloat (if a >= b then a else b)

instance Associative InfFloat
instance Associative SupFloat

instance Commutative SupFloat
instance Commutative InfFloat

instance Idempotent SupFloat
instance Idempotent InfFloat

instance Homomorphic SupFloat InfFloat where hom (SupFloat a) = InfFloat (-a)
instance Homomorphic InfFloat SupFloat where hom (InfFloat a) = SupFloat (-a)

instance Isomorphic SupFloat InfFloat where isomorph = (hom, hom)
instance Isomorphic InfFloat SupFloat where isomorph = (hom, hom)

instance Semilattice SupFloat
instance Semilattice InfFloat

instance Lattice Float where
    type Inf Float = InfFloat
    type Sup Float = SupFloat

-- Double
newtype InfDouble = InfDouble Double
newtype SupDouble = SupDouble Double

instance Magma InfDouble where
    InfDouble a  InfDouble b = InfDouble (if a <= b then a else b)

instance Magma SupDouble where
    SupDouble a  SupDouble b = SupDouble (if a >= b then a else b)

instance Associative InfDouble
instance Associative SupDouble

instance Commutative SupDouble
instance Commutative InfDouble

instance Idempotent SupDouble
instance Idempotent InfDouble

instance Homomorphic SupDouble InfDouble where hom (SupDouble a) = InfDouble (-a)
instance Homomorphic InfDouble SupDouble where hom (InfDouble a) = SupDouble (-a)

instance Isomorphic SupDouble InfDouble where isomorph = (hom, hom)
instance Isomorphic InfDouble SupDouble where isomorph = (hom, hom)

instance Semilattice SupDouble
instance Semilattice InfDouble

instance Lattice Double where
    type Inf Double = InfDouble
    type Sup Double = SupDouble