module Data.Number.Flint.Fmpq.Instances (
  Fmpq (..)
) where

import System.IO.Unsafe
import Control.Monad

import qualified Data.Ratio as Ratio
import Data.Ratio ((%))

import Foreign.Storable
import Foreign.C.Types
import Foreign.C.String
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc

import Data.Char
import Text.Read
import Data.Number.Flint.Fmpz
import Data.Number.Flint.Fmpz.Instances
import Data.Number.Flint.Fmpq
import Data.Number.Flint.Quotient ((//))

instance Show Fmpq where
  show x = snd $ unsafePerformIO $ do
    let base = 10 :: CInt
    withFmpq x $ \x -> do
      cs <- fmpq_get_str nullPtr 10 x
      s <- peekCString cs
      free cs
      return s

instance Read Fmpq where
  readPrec = parens $
               (prec app_prec $ do
                  x <- step readPrec
                  Symbol "/" <- lexP
                  y <- step readPrec
                  return (x // y))
           +++ (prec up_prec $ do
                  x <- step readPrec
                  return (x // 1))
          where app_prec = 10
                up_prec = 5
       
instance Eq Fmpq where
  (==) x y = snd $ snd $ unsafePerformIO $ 
    withFmpq x $ \x ->
      withFmpq y $ \y -> do
        result <- fmpq_equal x y
        return $ result == 1

instance Ord Fmpq where
  compare x y = snd $ snd $ unsafePerformIO $ 
    withFmpq x $ \x ->
      withFmpq y $ \y -> do
        ord <- fmpq_cmp x y
        return $ if ord > 0 then GT else (if ord < 0 then LT else EQ)
    
instance Num Fmpq where
  {-# INLINE (+) #-}
  (+) = lift2 fmpq_add
  {-# INLINE (-) #-}
  (-) = lift2 fmpq_sub
  {-# INLINE (*) #-}
  (*) = lift2 fmpq_mul
  negate = lift1 fmpq_neg
  abs    = lift1 fmpq_abs
  fromInteger x = unsafePerformIO $ do
    let n = fromInteger x 
        d = 1 :: Fmpz
    result <- newFmpq
    withFmpz n $ \n ->
      withFmpz d $ \d ->
        withFmpq result $ \result -> do
          fmpz_one d
          fmpq_set_fmpz_frac result n d
          fmpq_canonicalise result
    return result
  signum = lift1 sgn where
    sgn result x = do
      s <- fmpq_sgn x
      fmpq_set_si result (fromIntegral s) 1

instance Fractional Fmpq where
  (/) = lift2 fmpq_div
  recip = lift1 fmpq_inv
  fromRational x = unsafePerformIO $ do
    result <- newFmpq
    let n = fromInteger $ Ratio.numerator x
        d = fromInteger $ Ratio.denominator x
    withFmpz n $ \n ->
      withFmpz d $ \d ->
        withFmpq result $ \result -> do
          fmpq_set_fmpz_frac result n d
          fmpq_canonicalise result
    return result

instance Real Fmpq where
  toRational x = unsafePerformIO $ do
    p <- newFmpz
    q <- newFmpz
    withFmpq x $ \x -> do
      withFmpz p $ \p -> do
        withFmpz q $ \q -> do
          fmpq_get_fmpz_frac p q x
    return $ (toInteger p) % (toInteger q)

instance RealFrac Fmpq where
  properFraction x =  unsafePerformIO $ do
    p <- newFmpz
    q <- newFmpz
    r <- newFmpq
    withFmpq x $ \x -> do
      withFmpz p $ \p -> do
        withFmpz q $ \q -> do
          withFmpq r $ \r -> do
            withNewFmpz $ \tmp -> do
              fmpq_get_fmpz_frac p q x
              fmpz_tdiv_qr p tmp p q
              fmpq_set_fmpz_frac r tmp q
    return (fromIntegral p, r)
   
lift1 f x = fst $ unsafePerformIO $ 
  withNewFmpq $ \result -> 
    withFmpq x $ \x ->
      f result x
  
lift2 f x y = fst $ unsafePerformIO $ 
  withNewFmpq $ \result ->
    withFmpq x $ \x ->
      withFmpq y $ \y ->
        f result x y

