{-# LANGUAGE DeriveFoldable      #-}
{-# LANGUAGE DeriveFunctor       #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE DeriveTraversable   #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE Safe                #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.Prd.Nan where

import Control.Applicative
import Data.Prd
import Data.Connection
import Data.Semiring
import Data.Semifield
import GHC.Generics (Generic, Generic1)

import Prelude hiding (Ord(..), Num(..), Fractional(..))

-- | A type with an additional incomparable element allowing for the possibility of undefined values.
-- Isomorphic to /Maybe a/ but with a different 'Prd' instance.
data Nan a = Nan | Def a deriving ( Show, Generic, Generic1, Functor, Foldable, Traversable)

{-

instance Field a => Field (Nan a) where

u + Nan = Nan + u = Nan − Nan = Nan
u · Nan = Nan · u = Nan Nan−1 = Nan
Nan  u ⇔ u = Nan u  Nan ⇔ u = Nan
-}

nan :: b -> (a -> b) -> Nan a -> b
nan _ f (Def y) = f y
nan x _  Nan    = x

nan' :: Semifield b => (a -> b) -> Nan a -> b
nan' f = nan anan f

isDef :: Nan a -> Bool
isDef Nan = False
isDef _   = True

mapNan :: (a -> b) -> Nan a -> Nan b
mapNan f = nan Nan $ Def . f

joinNan :: Nan (Nan a) -> Nan a
joinNan Nan = Nan
joinNan (Def Nan) = Nan
joinNan (Def (Def a)) = Def a
-- collectNan = joinNan . liftNan id

liftNan :: Prd a => Semifield a => (a -> b) -> a -> Nan b
liftNan f x | x =~ anan = Nan
            | otherwise = Def (f x)

-- Lift all exceptional values
liftAll :: (RealFloat a, Prd a, Bound b) => (a -> b) -> a -> Nan b
liftAll f x | isNaN x = Nan
            | isInf x = Def maximal
            | isInf (-x) = Def minimal
            | otherwise = Def (f x)

isInf :: (RealFloat a, Prd a) => a -> Bool
isInf x = isInfinite x && x > 0

defnan :: Prd a => Prd b => Conn a b -> Conn (Nan a) (Nan b)
defnan (Conn f g) = Conn (fmap f) (fmap g)

defnan' :: Prd a => Prd b => Trip a b -> Trip (Nan a) (Nan b)
defnan' (Trip f g h) = Trip (fmap f) (fmap g) (fmap h)

--nanfld :: Prd a => Field a => Trip (Nan a) a
-- Field a => Field (Nan a)
-- /Caution/ this is only legal if (Nan a) has no nans.
{-
fldnan :: Prd a => Field a => Trip a (Nan a)
fldnan = Trip f g f where
  f a = if a =~ zero / zero then Nan else Def a 
  g = nan (zero / zero) id
-}

fldord :: Prd a => Field a => Trip a (Nan Ordering)
fldord = Trip f g h where
  g (Def GT) = pinf
  g (Def LT) = ninf
  g (Def EQ) = zero
  g Nan = anan

  f x | x =~ anan  = Nan
      | x =~ ninf  = Def LT
      | x <= zero  = Def EQ
      | otherwise  = Def GT

  h x | x =~ anan  = Nan
      | x =~ pinf  = Def GT
      | x >= zero  = Def EQ
      | otherwise  = Def LT

instance Prd a => Prd (Nan a) where
    Nan <= Nan = True
    _   <= Nan = False
    Nan <= _   = False
    Def a <= Def b = a <= b

instance Applicative Nan where
    pure = Def
    Nan <*> _ = Nan
    Def f <*> x = f <$> x

instance (Additive-Semigroup) a => Semigroup (Additive (Nan a)) where
  Additive a <> Additive b = Additive $ liftA2 (+) a b

-- MinPlus Dioid
instance (Additive-Monoid) a => Monoid (Additive (Nan a)) where
  mempty = Additive $ pure zero

instance (Multiplicative-Semigroup) a => Semigroup (Multiplicative (Nan a)) where
  Multiplicative a <> Multiplicative b = Multiplicative $ liftA2 (*) a b

-- MinPlus Dioid
instance (Multiplicative-Monoid) a => Monoid (Multiplicative (Nan a)) where
  mempty = Multiplicative $ pure one

-- Presemiring with a absorbing element.
instance Presemiring a => Presemiring (Nan a)