{-# OPTIONS_GHC -Wall #-}

-- | Integral classes
module NumHask.Algebra.Integral
  ( Integral(..)
  , ToInteger(..)
  , FromInteger(..)
  , fromIntegral
  , even
  , odd
  , (^)
  , (^^)
  ) where

import Data.Int (Int8, Int16, Int32, Int64)
import Data.Word (Word, Word8, Word16, Word32, Word64)
import GHC.Natural (Natural(..))
import NumHask.Algebra.Ring
import NumHask.Algebra.Additive
import NumHask.Algebra.Multiplicative
import qualified Prelude as P
import Prelude (Double, Float, Int, Integer, (.), fst, snd)

-- | Integral laws
--
-- > b == zero || b * (a `div` b) + (a `mod` b) == a
class (Semiring a) =>
      Integral a where
  infixl 7 `div`, `mod`
  div :: a -> a -> a
  div a1 a2 = fst (divMod a1 a2)
  mod :: a -> a -> a
  mod a1 a2 = snd (divMod a1 a2)

  divMod :: a -> a -> (a, a)

  quot :: a -> a -> a
  quot a1 a2 = fst (quotRem a1 a2)
  rem :: a -> a -> a
  rem a1 a2 = snd (quotRem a1 a2)

  quotRem :: a -> a -> (a, a)

instance Integral Int where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Integer where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Natural where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Int8 where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Int16 where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Int32 where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Int64 where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Word where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Word8 where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Word16 where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Word32 where
  divMod = P.divMod
  quotRem = P.quotRem

instance Integral Word64 where
  divMod = P.divMod
  quotRem = P.quotRem

-- | toInteger is kept separate from Integral to help with compatability issues.
class ToInteger a where
  toInteger :: a -> Integer

instance ToInteger Int where
  toInteger = P.toInteger

instance ToInteger Integer where
  toInteger = P.toInteger

instance ToInteger Natural where
  toInteger = P.toInteger

instance ToInteger Int8 where
  toInteger = P.toInteger

instance ToInteger Int16 where
  toInteger = P.toInteger

instance ToInteger Int32 where
  toInteger = P.toInteger

instance ToInteger Int64 where
  toInteger = P.toInteger

instance ToInteger Word where
  toInteger = P.toInteger

instance ToInteger Word8 where
  toInteger = P.toInteger

instance ToInteger Word16 where
  toInteger = P.toInteger

instance ToInteger Word32 where
  toInteger = P.toInteger

instance ToInteger Word64 where
  toInteger = P.toInteger

-- | fromInteger is the most problematic of the 'Num' class operators.  Particularly heinous, it is assumed that any number type can be constructed from an Integer, so that the broad classes of objects that are composed of multiple elements is avoided in haskell.
class FromInteger a where
  fromInteger :: Integer -> a

-- | coercion of 'Integral's
--
-- > fromIntegral a == a
fromIntegral :: (ToInteger a, FromInteger b) => a -> b
fromIntegral = fromInteger . toInteger

instance FromInteger Double where
  fromInteger = P.fromInteger

instance FromInteger Float where
  fromInteger = P.fromInteger

instance FromInteger Int where
  fromInteger = P.fromInteger

instance FromInteger Integer where
  fromInteger = P.fromInteger

instance FromInteger Natural where
  fromInteger = P.fromInteger

instance FromInteger Int8 where
  fromInteger = P.fromInteger

instance FromInteger Int16 where
  fromInteger = P.fromInteger

instance FromInteger Int32 where
  fromInteger = P.fromInteger

instance FromInteger Int64 where
  fromInteger = P.fromInteger

instance FromInteger Word where
  fromInteger = P.fromInteger

instance FromInteger Word8 where
  fromInteger = P.fromInteger

instance FromInteger Word16 where
  fromInteger = P.fromInteger

instance FromInteger Word32 where
  fromInteger = P.fromInteger

instance FromInteger Word64 where
  fromInteger = P.fromInteger

-- $operators

even :: (P.Eq a, Integral a) => a -> P.Bool
even n = n `rem` (one + one) P.== zero

odd :: (P.Eq a, Integral a) => a -> P.Bool
odd =  P.not . even

-------------------------------------------------------
-- | raise a number to a non-negative integral power
(^) :: (P.Ord b, Integral b, Multiplicative a) => a -> b -> a
x0 ^ y0 | y0 P.< zero = P.undefined -- P.errorWithoutStackTrace "Negative exponent"
        | y0 P.== zero = one
        | P.otherwise = f x0 y0
    where
      two = one+one

      -- f : x0 ^ y0 = x ^ y
      f x y | even y = f (x * x) (y `quot` two)
            | y P.== one = x
            | P.otherwise = g (x * x) (y `quot` two) x
              -- See Note [Half of y - 1]
      -- g : x0 ^ y0 = (x ^ y) * z
      g x y z | even y = g (x * x) (y `quot` two) z
              | y P.== one = x * z
              | P.otherwise = g (x * x) (y `quot` two) (x * z)
                -- See Note [Half of y - 1]

(^^) :: (MultiplicativeGroup a) => a -> Integer -> a
(^^) x n = if n P.>= zero then x^n else recip (x ^ negate n)