module Data.Number.Flint.Fmpz.Instances (
  Fmpz (..)
, UFD (..)
) where

import Test.QuickCheck
import System.IO.Unsafe
import Control.Monad

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 Foreign.Marshal.Array (advancePtr)

import Data.Number.Flint.Fmpz
import Data.Number.Flint.Fmpz.Factor
import Data.Number.Flint.UFD

instance Show Fmpz where
  show x = snd $ unsafePerformIO $ do
    let base = 10 :: CInt
    withFmpz x $ \x -> do
      cString <- fmpz_get_str nullPtr base x
      result <- peekCString cString
      free cString
      return result

instance Read Fmpz where
  readsPrec d s = unsafePerformIO $ do
    let n :: Integer
        [(n, r)] = readsPrec d s
    result <- newFmpz
    (_, flag) <- withFmpz result $ \result -> 
      withCString (show n) $ \s -> 
        fmpz_set_str result s 10
    if flag == 0 then 
      return [(result, r)]
    else
      return []

instance Eq Fmpz where
  (==) x y = snd $ snd $ unsafePerformIO $ 
    withFmpz x $ \x ->
      withFmpz y $ \y -> do
        result <- fmpz_equal x y
        return $ result == 1

instance Ord Fmpz where
  compare x y = snd $ snd $ unsafePerformIO $
    withFmpz x $ \x ->
      withFmpz y $ \y -> do
        ord <- fmpz_cmp x y
        return $ if ord > 0 then GT else (if ord < 0 then LT else EQ)

instance Enum Fmpz where
  toEnum = fromInteger . fromIntegral
  fromEnum = fromIntegral . toInteger
  succ x = unsafePerformIO $ do
    y <- newFmpz 
    withFmpz x $ \x -> withFmpz y $ \y -> fmpz_add_ui y x 1
    return y
    
instance Num Fmpz where
  {-# INLINE (+) #-}
  (+) = lift2 fmpz_add
  {-# INLINE (-) #-}
  (-) = lift2 fmpz_sub
  {-# INLINE (*) #-}
  (*) = lift2 fmpz_mul
  negate = lift1 fmpz_neg
  abs    = lift1 fmpz_abs
  fromInteger x = read (show x) :: Fmpz
  signum = lift1 sgn where
    sgn result x = do
      s <- fmpz_sgn x
      fmpz_set_si result (fromIntegral s)

instance Real Fmpz where
  toRational x = (toInteger x) % 1

instance Integral Fmpz where
  mod x y = unsafePerformIO $ do
    result <- newFmpz
    withFmpz result $ \result ->
      withFmpz x $ \x ->
        withFmpz y $ \y -> fmpz_mod result x y
    return result
  quotRem x y = unsafePerformIO $ do
    quot <- newFmpz
    rem <- newFmpz
    withFmpz x $ \x -> 
      withFmpz y $ \y -> 
        withFmpz quot $ \quot -> 
          withFmpz rem $ \rem -> 
            fmpz_tdiv_qr quot rem x y
    return (quot, rem)
  toInteger x = read (show x) :: Integer

instance UFD Fmpz where
  factor x = snd $ snd $ unsafePerformIO $
    withFmpz x $ \y -> do
      is_one <- fmpz_is_one y
      f <- newFmpzFactor
      withFmpzFactor f $ \f -> do
        if not (is_one == 1) then do
          fmpz_factor f y
          CFmpzFactor s d e _ n <- peek f
          result <- forM [0 .. fromIntegral n-1] $ \j -> do
            f <- newFmpz
            m <- peek (e `advancePtr` j)
            withFmpz f $ \f -> fmpz_set f (d `advancePtr` j)
            return (f, fromIntegral m)
          return $ if s < 1 then (-1, 1) : result else result
        else do
          return [(1, 1)]

instance Arbitrary Fmpz where
  arbitrary = do
    x <- arbitrary
    return $ fromInteger x
      
lift1 f x = unsafePerformIO $ do
  z <- newFmpz
  withFmpz x $ \x ->
    withFmpz z $ \z -> f z x
  return z
  
lift2 f x y = unsafePerformIO $ do
  z <- newFmpz
  withFmpz x $ \x ->
    withFmpz y $ \y ->
      withFmpz z $ \z -> f z x y
  return z
