module Data.Number.Flint.NMod.Poly.Instances (
    NModPoly (..)
  , module GHC.Exts
) where

import Test.QuickCheck

import GHC.Exts

import System.IO.Unsafe
import Control.Monad

import Foreign.Ptr
import Foreign.C.String
import Foreign.Storable
import Foreign.Marshal.Alloc (free)
import Foreign.Marshal.Array (advancePtr)

import Data.Number.Flint.NMod.Poly
import Data.Number.Flint.NMod.Poly.Factor

import Data.Number.Flint.UFD

instance Show NModPoly where
  show p = snd $ unsafePerformIO $ do
    withNModPoly p $ \p -> do
      withCString "x" $ \x -> do
        cs <- nmod_poly_get_str_pretty p x
        s <- peekCString cs
        free cs
        return s

instance Num NModPoly where
  (*) = lift2 nmod_poly_mul
  (+) = lift2 nmod_poly_add
  (-) = lift2 nmod_poly_sub
  abs = undefined
  signum = undefined
  fromInteger = undefined
  
instance Semigroup NModPoly where
  (<>) = lift2 nmod_poly_compose

instance Eq NModPoly where
  (==) x y = snd $ snd $ unsafePerformIO $ do
    withNModPoly x $ \x ->
      withNModPoly y $ \y -> do
        f <- nmod_poly_equal x y
        return $ f == 1

instance Ord NModPoly where
  compare = undefined
  
instance Real NModPoly where
  toRational = undefined

instance Enum NModPoly where
  toEnum = undefined
  fromEnum = undefined
  
instance Integral NModPoly where
  toInteger = undefined
  div x y = unsafePerformIO $ do
    (_, n) <- withNModPoly x nmod_poly_modulus
    (_, m) <- withNModPoly y nmod_poly_modulus
    when (n /= m) $ error "modulus does not agree."
    p <- newNModPoly n
    q <- newNModPoly n
    withNModPoly x $ \x ->
      withNModPoly y $ \y ->
        withNModPoly q $ \q ->
          nmod_poly_div q x y
    return q
  quotRem x y = unsafePerformIO $ do
    (_, n) <- withNModPoly x nmod_poly_modulus
    (_, m) <- withNModPoly y nmod_poly_modulus
    when (n /= m) $ error "modulus does not agree."
    p <- newNModPoly n 
    q <- newNModPoly n
    withNModPoly x $ \x ->
      withNModPoly y $ \y ->
        withNModPoly p $ \p ->
          withNModPoly q $ \q ->
            nmod_poly_divrem p q x y
    return (p, q)

instance UFD NModPoly where
  factor x = snd $ snd $ unsafePerformIO $ do
    withNModPoly x $ \x -> do
      m <- nmod_poly_modulus x
      f <- newNModPolyFactor
      withNModPolyFactor f $ \f -> do
        nmod_poly_factor f x
        (CNModPolyFactor d e n alloc) <- peek f
        forM [0..fromIntegral n-1] $ \j -> do
          m <- peek (e `advancePtr` j)
          r <- newNModPoly (fromIntegral m)
          withNModPoly r $ \r -> nmod_poly_set r (d `advancePtr` j)
          return (r, fromIntegral m)

lift2 f x y = unsafePerformIO $ do
  (_, n) <- withNModPoly x nmod_poly_modulus
  (_, m) <- withNModPoly y nmod_poly_modulus
  when (n /= m) $ error "modulus does not agree."
  result <- newNModPoly n
  withNModPoly result $ \result -> do
    withNModPoly x $ \x -> do
      withNModPoly y $ \y -> do
        f result x y
  return result

lift1 f x = unsafePerformIO $ do
  (_, n) <- withNModPoly x nmod_poly_modulus
  result <- newNModPoly n
  withNModPoly result $ \result ->
    withNModPoly x $ \x ->
    f result x
  return result

