{-|
Module      : Math.ExpPairs.RatioInf
Copyright   : (c) Andrew Lelechenko, 2014-2020
Maintainer  : andrew.lelechenko@gmail.com

Rational numbers extended with infinities.
-}

{-# LANGUAGE Safe #-}

module Math.ExpPairs.RatioInf
( RatioInf (..)
, RationalInf
) where

import Data.Ratio (Ratio, numerator, denominator)
import Data.Text.Prettyprint.Doc

-- | Extend 'Ratio' @t@ with $$\pm \infty$$ positive and negative
-- infinities.
data RatioInf t
= InfMinus          -- ^ $$- \infty$$
| Finite !(Ratio t) -- ^ Finite value
| InfPlus           -- ^ $$+ \infty$$
deriving (Eq, Ord, Show)

-- |Arbitrary-precision rational numbers with positive and negative
-- infinities.
type RationalInf = RatioInf Integer

instance (Integral t, Pretty t) => Pretty (RatioInf t) where
pretty InfMinus   = pretty "-Inf"
pretty (Finite x)
| denominator x == 1 = pretty (numerator x)
| otherwise          = pretty (numerator x) <+> pretty "/" <+> pretty (denominator x)
pretty InfPlus    = pretty "+Inf"

instance Integral t => Num (RatioInf t) where
InfMinus + InfPlus = error "Cannot add up negative and positive infinities"
InfPlus + InfMinus = error "Cannot add up negative and positive infinities"
InfMinus + _ = InfMinus
InfPlus + _  = InfPlus
_ + InfMinus = InfMinus
_ + InfPlus  = InfPlus
(Finite a) + (Finite b) = Finite (a+b)
{-# SPECIALIZE (+) :: RationalInf -> RationalInf -> RationalInf #-}

fromInteger = Finite . fromInteger
{-# SPECIALIZE fromInteger :: Integer -> RationalInf #-}

signum InfMinus   = Finite (-1)
signum InfPlus    = Finite 1
signum (Finite r) = Finite (signum r)
{-# SPECIALIZE signum :: RationalInf -> RationalInf #-}

abs InfMinus   = InfPlus
abs InfPlus    = InfPlus
abs (Finite r) = Finite (abs r)
{-# SPECIALIZE abs :: RationalInf -> RationalInf #-}

negate InfMinus   = InfPlus
negate InfPlus    = InfMinus
negate (Finite r) = Finite (negate r)
{-# SPECIALIZE negate :: RationalInf -> RationalInf #-}

InfMinus * InfMinus = InfMinus
InfMinus * InfPlus  = InfMinus
InfMinus * Finite a = case signum a of
1  -> InfMinus
-1 -> InfPlus
_  -> error "Cannot multiply infinity by zero"

InfPlus * InfMinus = InfMinus
InfPlus * InfPlus  = InfPlus
InfPlus * Finite a = case signum a of
1  -> InfPlus
-1 -> InfMinus
_  -> error "Cannot multiply infinity by zero"

Finite a * InfMinus = case signum a of
1  -> InfMinus
-1 -> InfPlus
_  -> error "Cannot multiply infinity by zero"

Finite a * InfPlus = case signum a of
1  -> InfPlus
-1 -> InfMinus
_  -> error "Cannot multiply infinity by zero"

Finite a * Finite b = Finite (a * b)

{-# SPECIALIZE (*) :: RationalInf -> RationalInf -> RationalInf #-}

instance Integral t => Fractional (RatioInf t) where
fromRational = Finite . fromRational
{-# SPECIALIZE fromRational :: Rational -> RationalInf #-}

InfMinus / InfMinus = error "Cannot divide infinity by infinity"
InfMinus / InfPlus  = error "Cannot divide infinity by infinity"
InfMinus / Finite a = case signum a of
1  -> InfMinus
-1 -> InfPlus
_  -> error "Cannot divide infinity by zero"

InfPlus  / InfMinus = error "Cannot divide infinity by infinity"
InfPlus  / InfPlus  = error "Cannot divide infinity by infinity"
InfPlus / Finite a  = case signum a of
1  -> InfPlus
-1 -> InfMinus
_  -> error "Cannot divide infinity by zero"

Finite _ / InfPlus  = Finite 0
Finite _ / InfMinus = Finite 0

Finite _ / Finite 0 = error "Cannot divide finite value by zero"
Finite a / Finite b = Finite (a/b)

{-# SPECIALIZE (/) :: RationalInf -> RationalInf -> RationalInf #-}

instance Integral t => Real (RatioInf t) where
toRational (Finite r) = toRational r
toRational InfPlus    = error "Cannot convert positive infinity into Rational"
toRational InfMinus   = error "Cannot convert negative infinity into Rational"
{-# SPECIALIZE toRational :: RationalInf -> Rational #-}