import Options.Applicative
import Control.Monad

import System.TimeIt

import Foreign.C.Types
import Foreign.Marshal.Array

import Text.Printf

import Data.Number.Flint

main = timeIt $ run =<< customExecParser (prefs showHelpOnEmpty) opts where
  desc = "This program constructs the Hilbert matrixq as exact \
         \algebraic numbers, and verifies the exact trace and \
         \determinant formulas."

  opts = info (options <**> helper) (
         fullDesc
      <> progDesc desc
      <> header desc)

run params@(Options n useQQbar useVieta) = do
  print params
  if useQQbar then do
    withNewFmpqMat n n $ \mat -> do
      withNewQQbar $ \trace -> do
        withNewQQbar $ \det -> do
        
          eig <- _qqbar_vec_init n
          
          fmpq_mat_hilbert_matrix mat
          qqbar_eigenvalues_fmpq_mat eig mat 0
          
          putStrLn "Trace:"
          forM_ [0 .. fromIntegral n - 1] $ \i -> do
            qqbar_add trace trace (eig `advancePtr` i)
            deg <- qqbar_degree trace
            putStr $ show i ++ " " ++ show n ++ ": "; print deg
          qqbar_print trace; putStr "\n"
          
          putStrLn "Determinant:"
          qqbar_one det
          forM_ [0 .. fromIntegral n - 1] $ \i -> do
            qqbar_mul det det (eig `advancePtr` i)
            deg <- fromIntegral <$> qqbar_degree det 
            putStr $ show i ++ " " ++ show n ++ ": "; print deg
          qqbar_print det; putStr "\n"
          
          _qqbar_vec_clear eig n
    return ()
  else do
    ctx <- newCaCtx
    withCaCtx ctx $ \ctx -> do
      -- Verification requires high-degree algebraics.
      ca_ctx_set_option ctx ca_opt_qqbar_deg_limit 10000
      if useVieta then do 
        ca_ctx_set_option ctx ca_opt_vieta_limit n
      else do
        ca_ctx_set_option ctx ca_opt_vieta_limit 0
    withNewCaMat n n ctx $ \mat -> do
      withNewCa ctx $ \trace -> do
        withNewCa ctx $ \det -> do
          withNewCa ctx $ \t -> do
            withNewCaVec 0 ctx $ \eig -> do 
              withCaCtx ctx $ \ctx -> do 
        
                ca_mat_hilbert mat ctx

                allocaArray (fromIntegral n) $ \mul -> do
                  ca_mat_eigenvalues eig mul mat ctx

                -- note: in general, we should use the multiplicities, but
                -- we happen to know that the eigenvalues are simple here

                ca_mat_trace trace mat ctx
                ca_mat_det det mat ctx

                putStrLn "Trace:"
                forM_ [0 .. fromIntegral n - 1] $ \i -> do
                  ca_add t t  (ca_vec_entry_ptr eig i) ctx
                ca_print trace ctx; putStr "\n"
                ca_print t ctx; putStr "\n"
                res <- ca_check_equal trace t ctx
                putStr $ "Equal: " ++ show res ++ "\n\n"

                putStrLn "Det:"
                ca_one t ctx
                forM_ [0 .. fromIntegral n - 1] $ \i -> do
                  ca_mul t t  (ca_vec_entry_ptr eig i) ctx
                ca_print det ctx; putStr "\n"
                ca_print t ctx; putStr "\n"
                res <- ca_check_equal det t ctx
                putStr $ "Equal: " ++ show res ++ "\n\n"
    putStr "\n."
          
data Options = Options {
    n :: CLong
  , useQQbar :: Bool
  , useVieta :: Bool
} deriving Show

options :: Parser Options
options = Options
  <$> argument auto (
      help "n, the dimension of the Hilbert matrix"
   <> metavar "n")
  <*> switch (
      help "use QQbar arithmetic."
   <> long "qqbar")
  <*> switch (
      help "use Vieta formula."
   <> long "vieta")