{-# LANGUAGE NoImplicitPrelude, FlexibleInstances, UndecidableInstances, DefaultSignatures #-}
module Numeric.Domain.Internal where

import Data.Maybe(fromJust)
import Numeric.Additive.Group
import Numeric.Algebra.Class
import Numeric.Algebra.Commutative
import Numeric.Algebra.Division
import Numeric.Natural (Natural)
import Numeric.Semiring.ZeroProduct
import Numeric.Algebra.Unital.UnitNormalForm
import Numeric.Ring.Class
import Numeric.Decidable.Zero
import Numeric.Decidable.Units

import Prelude (Integer, Maybe (..), Bool(..),
                otherwise, fst, snd, ($), (.))
import qualified Prelude                 as P

infixl 7 `quot`, `rem`
infix  7 `divide`, `divides`, `maybeQuot`

-- | (Integral) domain is the integral semiring.
class (ZeroProductSemiring d, Ring d) => Domain d
instance (ZeroProductSemiring d, Ring d) => Domain d

-- | An integral domain is a commutative domain in which 1≠0.
class (Domain d, Commutative d) => IntegralDomain d where
    divides :: d -> d -> Bool
    default divides :: (Euclidean d) => d -> d -> Bool
    m `divides` n 
        | isZero m = False
        | otherwise = isZero (n `rem` m)
    maybeQuot :: d -> d -> Maybe d
    default maybeQuot :: (Euclidean d) => d -> d -> Maybe d
    m `maybeQuot` n
        | isZero n = Nothing
        | otherwise = let (q,r) = m `divide` n in
                      if isZero r then Just q else Nothing

instance IntegralDomain Integer

class (IntegralDomain d, UnitNormalForm d, DecidableZero d) => GCDDomain d where
    gcd :: d -> d -> d
    default gcd :: (PID d) => d -> d -> d
    gcd a b = let (r,_,_) = egcd a b in r
    {-# INLINE gcd #-}

    reduceFraction :: d -> d -> (d,d)
    reduceFraction a b =
        let c = gcd a b in
        (fromJust (a `maybeQuot` c), fromJust (b `maybeQuot` c))

    lcm :: d -> d -> d
    lcm p q = fromJust $ (p * q) `maybeQuot` (gcd p q)

instance GCDDomain Integer

class (GCDDomain d) => UFD d

instance UFD Integer

class (UFD d) => PID d where
    egcd :: d -> d -> (d,d,d)
    default egcd :: (Euclidean d) => d -> d -> (d,d,d)
    egcd a b = P.head (euclid a b)
    {-# INLINE egcd #-}

instance PID Integer

class (PID d) => Euclidean d where
  -- | Euclidean (degree) function on @r@.
  degree :: d -> Maybe Natural
  default degree :: (Division d) => d -> Maybe Natural
  degree a | isZero a = Nothing
           | otherwise = Just zero
  -- | Division algorithm. @a `divide` b@ calculates
  --   quotient and remainder of @a@ divided by @b@.
  --
  -- prop> let (q, r) = divide a p in p*q + r == a && degree r < degree q
  divide :: d                   -- ^ elements divided by
         -> d                   -- ^ divisor
         -> (d,d)               -- ^ quotient and remainder
  default divide :: (Division d) => d -> d -> (d,d)
  -- Be strict in order to make sure division by zero gets caught
  divide a b = let q = a/b in (q,P.seq q zero)

  quot :: d -> d -> d
  quot a b = fst $ a `divide` b
  {-# INLINE quot #-}

  rem :: d -> d -> d
  rem a b = snd $ a `divide` b
  {-# INLINE rem #-}

instance Euclidean Integer where
  degree = Just . P.fromInteger . P.abs
  {-# INLINE degree #-}

  divide = P.divMod
  {-# INLINE divide #-}


-- | Extended euclidean algorithm.
--
-- prop> euclid f g == xs ==> all (\(r, s, t) -> r == f * s + g * t) xs
euclid :: (Euclidean d) =>  d -> d -> [(d,d,d)]
euclid f g =
  let (ug, g') = splitUnit g
      Just t'  = recipUnit ug
      (uf, f') = splitUnit f
      Just s   = recipUnit uf
  in step [(g', zero, t'), (f', s, zero)]
  where
    step acc@((r',s',t'):(r,s,t):_)
      | isZero r' = P.tail acc
      | otherwise =
        let q         = r `quot` r'
            (ur, r'') = splitUnit $ r - q * r'
            Just u    = recipUnit ur
            s''       = (s - q * s') * u
            t''       = (t - q * t') * u
        in step ((r'', s'', t'') : acc)
    step _ = P.error "cannot happen!"