{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-----------------------------------------------------------------------------
-- Copyright 2019, Ideas project team. This file is distributed under the
-- terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------

module Domain.Algebra.Field
   ( -- * Semi-ring
     SemiRing(..)
     -- * Ring
   , Ring(..)
     -- * Field
   , Field(..)
     -- * Additive monoid
   , Additive(..)
     -- * Multiplicative monoid
   , Multiplicative(..)
     -- * Datatype for safe numeric operators
   , SafeNum, safeNum
     -- * CoSemiRing, CoRing, and CoField (for matching)
   , CoSemiRing(..), CoRing(..), CoField(..)
   ) where

import Control.Monad
import Domain.Algebra.Group
import Ideas.Common.Classes (mapBoth)
import Test.QuickCheck
import qualified Control.Applicative as A
import qualified Control.Applicative as Applicative
import qualified Data.Semigroup as Sem

--------------------------------------------------------
-- Semi-ring

infixl 6 |+|
infixl 7 |*|

class SemiRing a where
   -- additive
   (|+|) :: a -> a -> a
   zero  :: a
   sum   :: [a] -> a
   -- multiplicative
   (|*|)   :: a -> a -> a
   one     :: a
   product :: [a] -> a
   -- default implementation
   sum     [] = zero
   sum     xs = foldl1 (|+|) xs
   product [] = one
   product xs = foldl1 (|*|) xs

--------------------------------------------------------
-- Ring

infixl 6 |-|

-- Minimal complete definition: plusInverse or <->
class SemiRing a => Ring a where
   plusInverse :: a -> a
   (|-|)       :: a -> a -> a
   -- default definitions
   plusInverse = (zero |-|)
   a |-| b     = a |+| plusInverse b

--------------------------------------------------------
-- Field

infixl 7 |/|

-- Minimal complete definition: mulInverse or </>
class Ring a => Field a where
   timesInverse :: a -> a
   (|/|)        :: a -> a -> a
   -- default definitions
   timesInverse = (one |/|)
   a |/| b      = a |*| timesInverse b

--------------------------------------------------------
-- Additive monoid

newtype Additive a = Additive {fromAdditive :: a}
   deriving (Show, Eq, Ord, Arbitrary, CoArbitrary)

instance Functor Additive where -- could be derived
   fmap f = Additive . f . fromAdditive

instance A.Applicative Additive where
   pure = Additive
   Additive f <*> Additive a = Additive (f a)

instance SemiRing a => Sem.Semigroup (Additive a) where
   (<>) = A.liftA2 (|+|)

instance SemiRing a => Monoid (Additive a) where
   mempty  = A.pure zero
   mappend = (Sem.<>)

instance Ring a => Group (Additive a) where
   inverse   = A.liftA plusInverse
   appendInv = A.liftA2 (|-|)

--------------------------------------------------------
-- Multiplicative monoid

newtype Multiplicative a = Multiplicative {fromMultiplicative :: a}
   deriving (Show, Eq, Ord, Arbitrary, CoArbitrary)

instance Functor Multiplicative where -- could be derived
   fmap f = Multiplicative . f . fromMultiplicative

instance A.Applicative Multiplicative where
   pure = Multiplicative
   Multiplicative f <*> Multiplicative a = Multiplicative (f a)

instance SemiRing a => Sem.Semigroup (Multiplicative a) where
   (<>) = A.liftA2 (|*|)

instance SemiRing a => Monoid (Multiplicative a) where
   mempty  = A.pure one
   mappend = (Sem.<>)

instance Field a => Group (Multiplicative a) where
   inverse   = A.liftA timesInverse
   appendInv = A.liftA2 (|/|)

instance SemiRing a => MonoidZero (Multiplicative a) where
   mzero = Multiplicative zero

--------------------------------------------------------
-- Datatype for safe numeric operators

data SafeNum a = Ok a | Exception String

safeNum :: SafeNum a -> Either String a
safeNum (Ok a)        = Right a
safeNum (Exception s) = Left s

instance Arbitrary a => Arbitrary (SafeNum a) where
   arbitrary = liftM return arbitrary

instance Eq a => Eq (SafeNum a) where
   Ok a == Ok b = a == b
   _    == _    = True

instance Ord a => Ord (SafeNum a) where
   Ok a `compare` Ok b = a `compare` b
   _    `compare` _    = EQ

instance Show a => Show (SafeNum a) where
   show = either ("Exception: " ++) show . safeNum

instance Functor SafeNum where
   fmap f = either Exception (return . f) . safeNum

instance Applicative.Applicative SafeNum where
   pure  = return
   (<*>) = ap

instance Monad SafeNum where
   return  = Ok
   fail    = Exception
   m >>= f = either Exception f (safeNum m)

instance Num a => Num (SafeNum a) where
   (+) = liftM2 (+)
   (*) = liftM2 (*)
   (-) = liftM2 (-)
   negate = liftM negate
   abs    = liftM abs
   signum = liftM signum
   fromInteger = return . fromInteger

instance (Eq a, Fractional a) => Fractional (SafeNum a) where
   a / b = liftM2 (/) a (safeDivisor b)
   recip = liftM recip . safeDivisor
   fromRational = return . fromRational

instance Num a => SemiRing (SafeNum a) where
   (|+|) = (+)
   (|*|) = (*)
   zero  = 0
   one   = 1

instance Num a => Ring (SafeNum a) where
   plusInverse = negate
   (|-|)       = (-)

instance (Eq a, Fractional a) => Field (SafeNum a) where
   timesInverse = recip
   (|/|)        = (/)

safeDivisor :: (Eq a, Num a) => SafeNum a -> SafeNum a
safeDivisor m = m >>= \a ->
   if a == 0 then fail "division by zero" else return a

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

class CoSemiRing a where
   -- additive
   isPlus  :: a -> Maybe (a, a)
   isZero  :: a -> Bool
   -- multiplicative
   isTimes :: a -> Maybe (a, a)
   isOne   :: a -> Bool

-- Minimal complete definition: plusInverse or <->
class CoSemiRing a => CoRing a where
   isNegate :: a -> Maybe a
   isMinus  :: a -> Maybe (a, a)
   -- default definition
   isMinus _ = Nothing

class CoRing a => CoField a where
   isRecip    :: a -> Maybe a
   isDivision :: a -> Maybe (a, a)
   -- default definition
   isDivision _ = Nothing

instance CoSemiRing a => CoMonoid (Additive a) where
   isEmpty  = isZero . fromAdditive
   isAppend = fmap (mapBoth Additive) . isPlus . fromAdditive

instance CoRing a => CoGroup (Additive a) where
   isInverse   = fmap Additive . isNegate . fromAdditive
   isAppendInv = fmap (mapBoth Additive) . isMinus . fromAdditive

instance CoSemiRing a => CoMonoid (Multiplicative a) where
   isEmpty  = isOne . fromMultiplicative
   isAppend = fmap (mapBoth Multiplicative) . isTimes . fromMultiplicative

instance CoField a => CoGroup (Multiplicative a) where
   isInverse   = fmap Multiplicative . isRecip . fromMultiplicative
   isAppendInv = fmap (mapBoth Multiplicative) . isDivision . fromMultiplicative

instance CoSemiRing a => CoMonoidZero (Multiplicative a) where
   isMonoidZero = isZero . fromMultiplicative