import System.TimeIt
import System.IO.Unsafe

import Options.Applicative
import Control.Monad
import Control.Monad.State

import Foreign.C.Types
import Foreign.C.String
import Foreign.Ptr
import Foreign.Marshal.Array
import Foreign.Storable 

import Data.Number.Flint

main = run =<< customExecParser (prefs showHelpOnEmpty) opts where
  desc = "Factor integers."
  opts = info (parameters <**> helper) (
         fullDesc
      <> progDesc desc
      <> header desc)

run params@(Parameters expression num_threads timing) = do
  flint_set_num_threads num_threads
  case parseExpression expression of
    Just n ->
      if timing then do
        timeItNamed "time for factorization" $ print $ factor n
      else do
        print $ factor n
    _ -> putStrLn "Could not parse expression."

parseExpression expression = unsafePerformIO $ do
  mctx <- newFmpzMPolyCtx 0 ord_lex
  f <- newFmpzMPoly mctx
  fac <- newFmpzMPolyFactor mctx
  (_, (_, flag)) <- withFmpzMPolyCtx mctx $ \mctx -> do
    withFmpzMPoly f $ \f -> do
      withCString expression $ \poly -> do 
        fmpz_mpoly_set_str_pretty f poly nullPtr mctx
  if flag == 0 then do
    n <- newFmpz
    withFmpzMPolyCtx mctx $ \mctx -> do
      withFmpzMPoly f $ \f -> do
        withFmpz n $ \n -> do
          fmpz_mpoly_get_fmpz n f mctx
    return $ Just n
  else do
    return $ Nothing
    
data Parameters = Parameters {
    expression :: String
  , num_threads :: CInt
  , timing :: Bool
} deriving Show

parameters :: Parser Parameters
parameters = Parameters
  <$> argument str (
      help "Integer given as expression (e.g. 2^64+1)"
   <> metavar "INTEGER")
  <*> option auto (
      help "number of threads"
   <> short 't'
   <> long "threads"
   <> value 1
   <> metavar "THREADS")
  <*> switch (
      help "timing"
   <> long "timing")