-- | The field of fractions over a GCD domain. The reason that it is an GCD 
-- domain is that we only want to work over reduced quotients.
module Algebra.Structures.FieldOfFractions
  ( FieldOfFractions(..)
  , toFieldOfFractions, fromFieldOfFractions
  , reduce
  ) where

import Test.QuickCheck

import Algebra.Structures.Field
import Algebra.Structures.GCDDomain


-------------------------------------------------------------------------------
-- | Field of fractions

newtype GCDDomain a => FieldOfFractions a = F (a,a)


--------------------------------------------------------------------------------
-- Instances

instance (GCDDomain a, Show a, Eq a) => Show (FieldOfFractions a) where
  show (F (a,b)) | b == one  = show a
                 | otherwise = case show b of
                    ('-':xs) -> "-" ++ show a ++ "/" ++ xs
                    xs -> show a ++ "/" ++ xs

instance (GCDDomain a, Eq a, Arbitrary a) => Arbitrary (FieldOfFractions a) where
  arbitrary = do
    a <- arbitrary
    b <- arbitrary
    if b == zero 
       then return $ F (a,one)
       else return $ F (a,b)

instance (GCDDomain a, Eq a) => Eq (FieldOfFractions a) where
  f == g = a <*> d == b <*> c
    where
    F (a,b) = reduce f
    F (c,d) = reduce g

instance (GCDDomain a, Eq a) => Ring (FieldOfFractions a) where
  (F (a,b)) <+> (F (c,d)) = reduce (F (a <*> d <+> c <*> b,b <*> d))
  (F (a,b)) <*> (F (c,d)) = reduce (F (a <*> c,b <*> d))
  neg (F (a,b))           = reduce (F (neg a,b))
  one                     = toFieldOfFractions one
  zero                    = toFieldOfFractions zero

instance (GCDDomain a, Eq a) => CommutativeRing (FieldOfFractions a)
instance (GCDDomain a, Eq a) => IntegralDomain (FieldOfFractions a)

instance (GCDDomain a, Eq a) => Field (FieldOfFractions a) where
  inv (F (a,b)) | b /= zero && a /= zero = reduce $ F (b,a)
                | otherwise = error "FieldOfFraction: Division by zero"


--------------------------------------------------------------------------------
-- Operations

-- | Embed a value in the field of fractions.
toFieldOfFractions :: GCDDomain a => a -> FieldOfFractions a
toFieldOfFractions a = F (a,one)

-- | Extract a value from the field of fractions. This is only possible if the
-- divisor is one.
fromFieldOfFractions :: (GCDDomain a, Eq a) => FieldOfFractions a -> a
fromFieldOfFractions (F (a,b)) 
  | b == one  = a
  | otherwise = error "FieldOfFractions: Can't extract value"

-- | Reduce an element.
reduce :: (GCDDomain a, Eq a) => FieldOfFractions a -> FieldOfFractions a
reduce (F (a,b)) | b == zero = error "FieldOfFractions: Division by zero"
                 | a == zero = F (zero,one)
                 | otherwise = if g == one
                                  then F (a,b)
                                  else F (x,y)
  where
  (g,x,y) = gcd' a b

-- Specification of reduce.
propReduce :: (GCDDomain a, Eq a) => FieldOfFractions a -> Property
propReduce f@(F (a,b)) = a /= zero && b /= zero ==> g == one
  where
  F (c,d) = reduce f
  (g,_,_) = gcd' c d