{-# LANGUAGE CPP #-}
{-
	Copyright (C) 2011 Dr. Alistair Ward

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]

	* Describes a /ring/ and operations on its members.

	* <https://en.wikipedia.org/wiki/Ring_%28mathematics%29>.

	* <https://www.numericana.com/answer/rings.htm>.
-}

module Factory.Data.Ring(
-- * Type-classes
        Ring(..),
-- * Types
-- ** Data-types
--	Product,
--	Sum,
-- * Functions
        product',
        sum',
-- ** Operators
        (=^)
) where

import qualified        Data.Monoid
import qualified        Factory.Math.DivideAndConquer   as Math.DivideAndConquer

infixl 6 =+=    -- Same as (+).
infixl 6 =-=    -- Same as (-).
infixl 7 =*=    -- Same as (*).
infixr 8 =^     -- Same as (^).

{- |
	* Define both the operations applicable to all members of the /ring/, and its mandatory members.

	* Minimal definition; '=+=', '=*=', 'additiveInverse', 'multiplicativeIdentity', 'additiveIdentity'.
-}
class Ring r    where
        (=+=)                   :: r -> r -> r  -- ^ Addition of two members; required to be /commutative/; <https://en.wikipedia.org/wiki/Commutativity>.
        (=*=)                   :: r -> r -> r  -- ^ Multiplication of two members.
        additiveInverse         :: r -> r       -- ^ The operand required to yield /zero/ under addition; <https://en.wikipedia.org/wiki/Additive_inverse>.
        multiplicativeIdentity  :: r            -- ^ The /identity/-member under multiplication; <https://mathworld.wolfram.com/MultiplicativeIdentity.html>.
        additiveIdentity        :: r            -- ^ The /identity/-member under addition (AKA /zero/); <https://en.wikipedia.org/wiki/Additive_identity>.

        (=-=) :: r -> r -> r                    -- ^ Subtract the two specified /ring/-members.
        l =-= r = l =+= additiveInverse r       -- Default implementation.

        square :: r -> r                        -- ^ Square the ring.
        square r        = r =*= r               -- Default implementation; there may be a more efficient one.

{- |
	* Raise a /ring/-member to the specified positive integral power.

	* Exponentiation is implemented as a sequence of either squares of, or multiplications by, the /ring/-member;
	<https://en.wikipedia.org/wiki/Exponentiation_by_squaring>.
-}
(=^) :: (
        Eq              r,
        Integral        power,
        Ring            r,
        Show            power
 ) => r -> power -> r
_ =^ 0  = multiplicativeIdentity
ring =^ power
        | power < 0                                                     = error $ "Factory.Data.Ring.(=^):\tthe result isn't guaranteed to be a ring-member, for power=" ++ show power
        | ring `elem` [additiveIdentity, multiplicativeIdentity]        = ring
        | otherwise                                                     = slave power
        where
                slave 1 = ring
                slave n = (if r == 0 {-even-} then id else (=*= ring)) . square $ slave q       where
                        (q, r)  = n `quotRem` 2

-- | Does for 'Ring', what 'Data.Monoid.Product' does for type 'Num', in that it makes it an instance of 'Data.Monoid.Monoid' under multiplication.
newtype Product p       = MkProduct {
        getProduct :: p -- ^ Access the polymorphic payload.
} deriving (Read, Show)

-- Added for 'ghc-8.4', when 'Semigroup' became a superclass of 'Monoid'.
#if MIN_VERSION_base(4,11,0)
instance Ring r => Semigroup (Product r)        where
        MkProduct x <> MkProduct y      = MkProduct $ x =*= y
#endif

instance Ring r => Data.Monoid.Monoid (Product r)       where
        mempty                                  = MkProduct multiplicativeIdentity
#if !MIN_VERSION_base(4,11,0)
        MkProduct x `mappend` MkProduct y       = MkProduct $ x =*= y
#endif

-- | Returns the /product/ of the list of /ring/-members.
product' :: Ring r => Math.DivideAndConquer.BisectionRatio -> Math.DivideAndConquer.MinLength -> [r] -> r
-- product' _ _			= getProduct . Data.Monoid.mconcat . map MkProduct
product' ratio minLength        = getProduct . Math.DivideAndConquer.divideAndConquer ratio minLength . map MkProduct

-- | Does for 'Ring', what 'Data.Monoid.Sum' does for type 'Num', in that it makes it an instance of 'Data.Monoid.Monoid' under addition.
newtype Sum s   = MkSum {
        getSum :: s     -- ^ Access the polymorphic payload.
} deriving (Read, Show)

-- Added for 'ghc-8.4', when 'Semigroup' became a superclass of 'Monoid'.
#if MIN_VERSION_base(4,11,0)
instance Ring r => Semigroup (Sum r)    where
        MkSum x <> MkSum y      = MkSum $ x =+= y
#endif

instance Ring r => Data.Monoid.Monoid (Sum r)   where
        mempty                          = MkSum additiveIdentity
#if !MIN_VERSION_base(4,11,0)
        MkSum x `mappend` MkSum y       = MkSum $ x =+= y
#endif

-- | Returns the /sum/ of the list of /ring/-members.
sum' :: Ring r => Math.DivideAndConquer.BisectionRatio -> Math.DivideAndConquer.MinLength -> [r] -> r
-- sum' _ _		= getSum . Data.Monoid.mconcat . map MkSum
sum' ratio minLength    = getSum . Math.DivideAndConquer.divideAndConquer ratio minLength . map MkSum