{-# LANGUAGE CPP #-}
{-
	Copyright (C) 2011-2015 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 <http://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]

	* Describes a <https://en.wikipedia.org/wiki/Monomial> and operations on it.

	* A /monomial/ is merely a /polynomial/ with a single non-zero term; cf. /Binomial/.
-}

module Factory.Data.Monomial(
-- * Types
-- ** Type-synonyms
	Monomial,
-- * Functions
	double,
	mod',
	negateCoefficient,
	realCoefficientToFrac,
	shiftCoefficient,
	shiftExponent,
	square,
-- ** Accessors
	getExponent,
	getCoefficient,
-- ** Operators
	(<=>),
	(</>),
	(<*>),	-- CAVEAT: this clashes with the Prelude from 'base-4.8'.
	(=~),
-- ** Predicates
	isMonomial
) where

import qualified	Control.Arrow

#if MIN_VERSION_base(4,8,0)
import Prelude hiding ((<*>))	-- The "Prelude" from 'base-4.8' exports this symbol.
#endif

infix 4 <=>	-- Same as (==).
infix 4 =~	-- Same as (==).
infixl 7 </>	-- Same as (/).
infixl 7 <*>	-- Same as (*).

{- |
	* The type of an arbitrary monomial.

	* CAVEAT: though a /monomial/ has an integral power, this contraint is only imposed at the function-level.
-}
type Monomial coefficient exponent	= (coefficient, exponent)

-- | Accessor.
{-# INLINE getCoefficient #-}
getCoefficient :: Monomial c e -> c
getCoefficient	= fst

-- | Accessor.
{-# INLINE getExponent #-}
getExponent :: Monomial c e -> e
getExponent	= snd

{- |
	* 'True' if the /exponent/ is both integral and non-/negative/.

	* CAVEAT: one can't even call this function unless the /exponent/ is integral.
-}
isMonomial :: Integral e => Monomial c e -> Bool
isMonomial	= (>= 0) . getExponent

-- | Compares the /exponents/ of the specified 'Monomial's.
{-# INLINE (<=>) #-}
(<=>) :: Ord e => Monomial c e -> Monomial c e -> Ordering
(_, l) <=> (_, r)	= l `compare` r

-- | True if the /exponents/ are equal.
(=~) :: Eq e => Monomial c e -> Monomial c e -> Bool
(_, l) =~ (_, r)	= l == r

-- | Multiply the two specified 'Monomial's.
{-# INLINE (<*>) #-}
(<*>) :: (Num c, Num e) => Monomial c e -> Monomial c e -> Monomial c e
(cL, eL) <*> (cR, eR)	= (cL * cR, eL + eR)

-- | Divide the two specified 'Monomial's.
(</>) :: (Eq c, Fractional c, Num e)
	=> Monomial c e	-- ^ Numerator.
	-> Monomial c e	-- ^ Denominator.
	-> Monomial c e
(cN, eN) </> (1, eD)	= (cN, eN - eD)
(cN, eN) </> (cD, eD)	= (cN / cD, eN - eD)

-- | Square the specified 'Monomial'.
square :: (Num c, Num e) => Monomial c e -> Monomial c e
square (c, e)	= (c ^ (2 :: Int), 2 * e)

-- | Double the specified 'Monomial'.
{-# INLINE double #-}
double :: Num c => Monomial c e -> Monomial c e
double (c, e)	= (2 * c, e)

-- | Shift the /coefficient/, by the specified amount.
{-# INLINE shiftCoefficient #-}
shiftCoefficient :: Num c
	=> Monomial c e
	-> c	-- ^ The magnitude of the shift.
	-> Monomial c e
-- m `shiftCoefficient` i	= Control.Arrow.first (+ i) m	-- CAVEAT: Too slow.
(c, e) `shiftCoefficient` i	= (c + i, e)

-- | Shift the /exponent/, by the specified amount.
{-# INLINE shiftExponent #-}
shiftExponent :: Num e
	=> Monomial c e
	-> e	-- ^ The magnitude of the shift.
	-> Monomial c e
-- m `shiftExponent` i	= Control.Arrow.second (+ i) m	-- CAVEAT: Too slow.
(c, e) `shiftExponent` i	= (c, e + i)

-- | Negate the coefficient.
negateCoefficient :: Num c => Monomial c e -> Monomial c e
negateCoefficient	= Control.Arrow.first negate

-- | Reduce the coefficient using /modular/ arithmetic.
{-# INLINE mod' #-}
mod' :: Integral c
	=> Monomial c e
	-> c	-- ^ Modulus.
	-> Monomial c e
monomial `mod'` modulus	= Control.Arrow.first (`mod` modulus) monomial

-- | Convert the type of the /coefficient/.
realCoefficientToFrac :: (Real r, Fractional f) => Monomial r e -> Monomial f e
realCoefficientToFrac	= Control.Arrow.first realToFrac