
-- | C implementation of GF(p^m) via precomputed tables of Zech's logarithm.
--
-- This way I can test the C implementation using the Haskell test framework.
--

{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}
{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls #-}
{-# LANGUAGE TypeFamilies, StandaloneDeriving, ExistentialQuantification #-}

module Math.FiniteField.GaloisField.Zech.C where

--------------------------------------------------------------------------------

import Data.Int

import GHC.TypeNats (Nat)

import Foreign.C
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.Storable
import Foreign.Marshal

import System.Random ( RandomGen , randomR )

import System.IO
import System.IO.Unsafe as Unsafe

import qualified Data.Vector.Unboxed as Vec

import Math.FiniteField.Class
import Math.FiniteField.TypeLevel.Singleton

import qualified Math.FiniteField.GaloisField.Zech as Z

--------------------------------------------------------------------------------

data WitnessC (p :: Nat) (m :: Nat) 
  = WitnessC (ForeignPtr Int32)
  deriving Show

fromWitnessC :: WitnessC p m -> ForeignPtr Int32
fromWitnessC (WitnessC fptr) = fptr

data SomeWitnessC 
  = forall p m. SomeWitnessC (WitnessC p m)

deriving instance Show SomeWitnessC

mkCField :: Int -> Int -> Maybe SomeWitnessC
mkCField p m = case Z.mkZechField p m of 
  Nothing   -> Nothing
  Just some -> case some of
    Z.SomeWitnessZech wzech -> Just (SomeWitnessC (makeCZechTable wzech))

unsafeCField :: Int -> Int -> SomeWitnessC
unsafeCField p m = case mkCField p m of 
  Nothing   -> error $ "unsafeCField: cannot find Conway polynomial for GF(" ++ show p ++ "^" ++ show m ++ ")"
  Just some -> some

-- instance FieldWitness (WitnessC p m) where
--   type FieldElem    (WitnessC p m) = CFq p m
--   type WitnessPrime (WitnessC p m) = p
--   type WitnessDim   (WitnessC p m) = m

--------------------------------------------------------------------------------

makeCZechTable :: Z.WitnessZech p m -> WitnessC p m
makeCZechTable (Z.WitnessZech zechtable) = unsafePerformIO (WitnessC <$> marshalZechTable zechtable)

marshalZechTable :: Z.ZechTable -> IO (ForeignPtr Int32)
marshalZechTable ztable = do

  let (p,m) = Z._zechParams ztable
  let q = p ^ m 
  let e = if p == 2 then 0 else div (q-1) 2
  let len = 4 * fromIntegral (4 + p + q-1)
  fptr <- mallocForeignPtrBytes len :: IO (ForeignPtr Int32)

  withForeignPtr fptr $ \ptr -> do
    pokeElemOff ptr 0 p
    pokeElemOff ptr 1 m
    pokeElemOff ptr 2 (q-1)
    pokeElemOff ptr 3 e
    let ofs = 4 * (4 + fromIntegral p)
    pokeArray (plusPtr ptr 16 ) (Vec.toList (Z._embedding ztable))
    pokeArray (plusPtr ptr ofs) (Vec.toList (Z._zechLogs  ztable))

  return fptr

-- | Save the data necessary to do computations to a file
saveCZechTable :: FilePath -> WitnessC p q -> IO ()
saveCZechTable fname w@(WitnessC fptr) = do
  let p = rawPrime w
  let m = rawDim   w
  let q = p^m
  let len = 4 * fromIntegral (4 + p + q-1)
  withForeignPtr fptr $ \ptr -> do
    h <- openBinaryFile fname WriteMode 
    hPutBuf h ptr len
    hClose h

-- | Load the data necessary to do computations from a file
loadCZechTable :: FilePath -> IO (Maybe SomeWitnessC)
loadCZechTable fname = do
  h  <- openBinaryFile fname ReadMode 
  mb <- allocaBytes 16 $ \(header :: Ptr Int32) -> do
    hGetBuf h header 16
    p   <- peekElemOff header 0 
    m   <- peekElemOff header 1
    qm1 <- peekElemOff header 2
    e   <- peekElemOff header 3
    let ok1 = qm1 + 1 == p^m
        ok2 = if p == 2 then e == 0 else e == div qm1 2 
    if not (ok1 && ok2) 
      then return Nothing
      else do
        hSeek h AbsoluteSeek 0
        let len = 16 + 4 * (fromIntegral qm1 + fromIntegral p) 
        fptr <- mallocForeignPtrBytes len
        withForeignPtr fptr $ \ptr -> hGetBuf h ptr len  
        return $ case (someSNat64 (fromIntegral p), someSNat64 (fromIntegral m)) of
          (SomeSNat64 sp, SomeSNat64 sm) -> Just (SomeWitnessC (constructWitnessC sp sm fptr))
  hClose h
  return mb

constructWitnessC :: SNat64 p -> SNat64 m -> ForeignPtr Int32 -> WitnessC p m
constructWitnessC _ _ fptr = WitnessC fptr

--------------------------------------------------------------------------------

-- | An element of the field
data CFq (p :: Nat) (m :: Nat) 
  = CFq {-# UNPACK #-} !(ForeignPtr Int32) {-# UNPACK #-} !Int32 

instance Eq (CFq p m) where
  (==) (CFq _ x) (CFq _ y) = x == y

instance Ord (CFq p m) where
  compare (CFq _ x) (CFq _ y) = compare x y

instance Show (CFq p m) where
  show (CFq _ k)
    | k == -1    = "0"
    | k ==  0    = "1"
    | k ==  1    = "g"
    | otherwise  = "g^" ++ show k

instance Num (CFq p m) where
  fromInteger = error "GaloisField/Zech/C/fromInteger: cannot be implemented; use `embed` instead"
  negate (CFq fptr x)           = CFq fptr (fromRaw (rawNeg (WitnessC fptr) (Raw x)        ))
  (+)    (CFq fptr x) (CFq _ y) = CFq fptr (fromRaw (rawAdd (WitnessC fptr) (Raw x) (Raw y)))
  (-)    (CFq fptr x) (CFq _ y) = CFq fptr (fromRaw (rawSub (WitnessC fptr) (Raw x) (Raw y)))
  (*)    (CFq fptr x) (CFq _ y) = CFq fptr (fromRaw (rawMul (WitnessC fptr) (Raw x) (Raw y)))
  abs    (CFq fptr x)           = CFq fptr x
  signum (CFq fptr x)           = CFq fptr 0

instance Fractional (CFq p m) where
  fromRational = error "GaloisField/Zech/C/fromRational: cannot be implemented; use `embed` instead"
  recip  (CFq fptr x)           = CFq fptr (fromRaw (rawInv (WitnessC fptr) (Raw x)        ))
  (/)    (CFq fptr x) (CFq _ y) = CFq fptr (fromRaw (rawDiv (WitnessC fptr) (Raw x) (Raw y)))

instance Field (CFq p m) where
  type Witness (CFq p m) = WitnessC p m
  type Prime   (CFq p m) = p
  type Dim     (CFq p m) = m

  characteristic    w = fromIntegral (rawPrime     w)
  dimension         w = fromIntegral (rawDim       w)
  fieldSize         w = fromIntegral (rawFieldSize w)
  witnessOf        !x = case x of { CFq fptr _ -> WitnessC fptr }

  enumerate  (WitnessC fptr)    = map (\r -> CFq fptr (fromRaw r)) (rawEnumerate (WitnessC fptr))
  embed      (WitnessC fptr) !k = CFq fptr (fromRaw (rawEmbed (WitnessC fptr) (fromInteger k)))
  embedSmall (WitnessC fptr) !k = CFq fptr (fromRaw (rawEmbed (WitnessC fptr)              k ))

  randomFieldElem   w = randomCFq    w
  randomInvertible  w = randomInvCFq w

  power      (CFq fptr x) e = CFq fptr (fromRaw (rawPow (WitnessC fptr) (Raw x) (fromIntegral e)))
  powerSmall (CFq fptr x) e = CFq fptr (fromRaw (rawPow (WitnessC fptr) (Raw x)               e ))

  zero    (WitnessC fptr) = CFq fptr (-1)
  one     (WitnessC fptr) = CFq fptr   0
  primGen (WitnessC fptr) = CFq fptr   1
  isZero  (CFq _ a)       = a == -1
  isOne   (CFq _ a)       = a == 0

--------------------------------------------------------------------------------

randomCFq :: RandomGen gen => WitnessC p m -> gen -> (CFq p m, gen)
randomCFq w@(WitnessC fptr) g = 
  let q = rawFieldSize w 
  in  case randomR (-1,q-2) g of (k,g') -> (CFq fptr (fromIntegral k), g')

randomInvCFq :: RandomGen gen => WitnessC p m -> gen -> (CFq p m, gen)
randomInvCFq w@(WitnessC fptr) g = 
  let q = rawFieldSize w 
  in  case randomR (0,q-2) g of (k,g') -> (CFq fptr (fromIntegral k), g')

--------------------------------------------------------------------------------
-- * The \"raw\" interface, where you have to manually supply the tables

newtype Raw (p :: Nat) (m :: Nat) 
  = Raw Int32 
  deriving (Eq,Ord)

fromRaw :: Raw p m -> Int32
fromRaw (Raw k) = k

instance Show (Raw p m) where
  show (Raw k)
    | k == -1    = "0"
    | k ==  0    = "1"
    | k ==  1    = "g"
    | otherwise  = "g^" ++ show k

rawNeg :: WitnessC p m -> Raw p m -> Raw p m
rawNeg (WitnessC fptr) (Raw x) = unsafePerformIO (withForeignPtr fptr (\ptr -> Raw <$> zech_neg ptr x))

rawAdd :: WitnessC p m -> Raw p m -> Raw p m -> Raw p m
rawAdd (WitnessC fptr) (Raw x) (Raw y) = unsafePerformIO (withForeignPtr fptr (\ptr -> Raw <$> zech_add ptr x y))

rawSub :: WitnessC p m -> Raw p m -> Raw p m -> Raw p m
rawSub (WitnessC fptr) (Raw x) (Raw y) = unsafePerformIO (withForeignPtr fptr (\ptr -> Raw <$> zech_sub ptr x y))

rawInv :: WitnessC p m -> Raw p m -> Raw p m
rawInv (WitnessC fptr) (Raw x) = unsafePerformIO (withForeignPtr fptr (\ptr -> Raw <$> zech_inv ptr x))

rawMul :: WitnessC p m -> Raw p m -> Raw p m -> Raw p m
rawMul (WitnessC fptr) (Raw x) (Raw y) = unsafePerformIO (withForeignPtr fptr (\ptr -> Raw <$> zech_mul ptr x y))

rawDiv :: WitnessC p m -> Raw p m -> Raw p m -> Raw p m
rawDiv (WitnessC fptr) (Raw x) (Raw y) = unsafePerformIO (withForeignPtr fptr (\ptr -> Raw <$> zech_div ptr x y))

rawPow :: WitnessC p m -> Raw p m -> Int -> Raw p m
rawPow (WitnessC fptr) (Raw x) e = unsafePerformIO (withForeignPtr fptr (\ptr -> Raw <$> zech_pow ptr x (fromIntegral e)))

rawIsZero :: Raw p m -> Bool
rawIsZero (Raw x) = (cboolToBool $ zech_is_zero x)

rawIsOne :: Raw p m -> Bool
rawIsOne (Raw x) = (cboolToBool $ zech_is_one x)

rawZero :: Raw p m
rawZero = Raw (-1)

rawOne :: Raw p m
rawOne = Raw 0

rawPrim :: Raw p m
rawPrim = Raw 1

rawEmbed :: WitnessC p m -> Int -> Raw p m
rawEmbed (WitnessC fptr) k = unsafePerformIO (withForeignPtr fptr (\ptr -> Raw <$> zech_embed ptr (fromIntegral k)))

rawEnumerate :: WitnessC p m -> [Raw p m]
rawEnumerate (WitnessC fptr) = unsafePerformIO $ do
  withForeignPtr fptr $ \ptr -> do
    qminus1 <- peekElemOff ptr 2     :: IO Int32
    let q = fromIntegral qminus1 + 1 :: Int
    allocaBytes (4*q) $ \tgt -> do
      _ <- zech_enumerate ptr tgt
      map Raw <$> peekArray q tgt

rawPrime :: WitnessC p m -> Int
rawPrime (WitnessC fptr) = unsafePerformIO $ withForeignPtr fptr $ \ptr -> fromIntegral <$> peekElemOff ptr 0 

rawDim :: WitnessC p m -> Int
rawDim (WitnessC fptr) = unsafePerformIO $ withForeignPtr fptr $ \ptr -> fromIntegral <$> peekElemOff ptr 1 

rawFieldSize :: WitnessC p m -> Int
rawFieldSize (WitnessC fptr) = unsafePerformIO $ do
  withForeignPtr fptr $ \ptr -> do
    qminus1 <- peekElemOff ptr 2 :: IO Int32
    return (fromIntegral qminus1 + 1)

--------------------------------------------------------------------------------
-- * foreign imports

cboolToBool :: CBool -> Bool
cboolToBool b = (b /= 0)

foreign import ccall unsafe "zech_neg" zech_neg :: Ptr Int32 -> Int32 -> IO Int32
foreign import ccall unsafe "zech_add" zech_add :: Ptr Int32 -> Int32 -> Int32 -> IO Int32
foreign import ccall unsafe "zech_sub" zech_sub :: Ptr Int32 -> Int32 -> Int32 -> IO Int32
foreign import ccall unsafe "zech_inv" zech_inv :: Ptr Int32 -> Int32 -> IO Int32
foreign import ccall unsafe "zech_mul" zech_mul :: Ptr Int32 -> Int32 -> Int32 -> IO Int32
foreign import ccall unsafe "zech_div" zech_div :: Ptr Int32 -> Int32 -> Int32 -> IO Int32
foreign import ccall unsafe "zech_pow" zech_pow :: Ptr Int32 -> Int32 -> CInt  -> IO Int32

foreign import ccall unsafe "zech_zero" zech_zero :: Int32
foreign import ccall unsafe "zech_one"  zech_one  :: Int32
foreign import ccall unsafe "zech_prim" zech_prim :: Int32

foreign import ccall unsafe "zech_is_zero" zech_is_zero :: Int32 -> CBool
foreign import ccall unsafe "zech_is_one"  zech_is_one  :: Int32 -> CBool

foreign import ccall unsafe "zech_embed"     zech_embed     :: Ptr Int32 -> CInt      -> IO Int32
foreign import ccall unsafe "zech_enumerate" zech_enumerate :: Ptr Int32 -> Ptr Int32 -> IO CInt

--------------------------------------------------------------------------------
