-- | Guessing the location of factory\'s gftables.
--
-- For the Galois field domains to work (for non-primer order), you have to
-- first initialize singular-factory by setting this directory.
--
-- This can be done either by `initGFTables` or `initGFTables'`.
--

{-# LANGUAGE CPP, BangPatterns, ForeignFunctionInterface #-}
module Math.Singular.Factory.GFTables where

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

import Control.Monad

import Foreign.Ptr
import Foreign.C
import Foreign.C.String

import System.FilePath
import System.Directory
import System.Process

import Data.IORef
import System.IO.Unsafe as Unsafe

--------------------------------------------------------------------------------
-- * A global variable

{-# NOINLINE theGFTablesDir #-}
theGFTablesDir :: IORef (Maybe FilePath)
theGFTablesDir = Unsafe.unsafePerformIO $ newIORef Nothing

getGFTablesDir :: IO (Maybe FilePath)
getGFTablesDir = readIORef theGFTablesDir

--------------------------------------------------------------------------------
-- * Initialization

-- | We try to guess the location.
initGFTables :: IO ()
initGFTables = initGFTables' Nothing

-- | Set the location of the small finite field table files.
--
-- If you know where they are located, please set it.
-- If you don't know, we try to guess it, but I have no idea how
-- to figure this out in general (pkg-config does not seem to have this information...)
--
initGFTables' :: Maybe FilePath -> IO ()
initGFTables' mbdir = case mbdir of
  Just fpath -> setGFTablesDir fpath
  Nothing    -> guessGFTablesDir >>= \d -> case d of
    Just fpath -> do
      -- putStrLn $ "gftables dir = " ++ (fpath </> "gftables")
      setGFTablesDir fpath
    Nothing    -> do
      writeIORef theGFTablesDir Nothing
      putStrLn "WARNING: cannot find factory's gftables"

setGFTablesDir :: FilePath -> IO ()
setGFTablesDir fpath0 = do
  fpath1 <- canonicalizePath fpath0
  withCString (fpath1 ++ "/") $ \ptr -> set_gftable_dir ptr
  writeIORef theGFTablesDir (Just fpath1)

-- | Apparently we need to manually find the directory containing the GF tables...
--
-- On my debian install it is at @\/usr\/share\/singular\/factory\/gftables\/@, but how
-- to figure that out???
foreign import ccall "set_gftable_dir" set_gftable_dir :: Ptr CChar -> IO ()

------------------------------------------------------------------------------------------
-- * Guessing

guessGFTablesDir :: IO (Maybe FilePath)
guessGFTablesDir = do

#if defined(linux_HOST_OS)
  guessLinux
#elif defined(darwin_HOST_OS)
  guessHomebrew
#elif defined(mingw32_HOST_OS) || defined(mingw64_HOST_OS) 
  guessCygwin
#else
  return Nothing
#endif

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

infixr 5 >>>

(>>>) :: IO (Maybe a) -> IO (Maybe a) -> IO (Maybe a)
(>>>) action1 action2 = do
  mb <- action1
  case mb of
    Just x  -> return (Just x)
    Nothing -> action2

testDir :: FilePath -> IO (Maybe FilePath)
testDir dir = doesFileExist (dir </> "gftables/361") >>= \b -> if b
  then return (Just dir)
  else return Nothing

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

-- macOS w/ Homebrew
-- on my machine it looks like this: /usr/local/Cellar/singular/4.1.2p1_2/share/factory
guessHomebrew :: IO (Maybe FilePath)
guessHomebrew = do
  let brew_prefix = "/usr/local"            -- TODO: maybe use "brew config" (but it's rather slow)
  let cellar    = brew_prefix </> "Cellar"
  let sing_root = cellar      </> "singular"
  entries <- map (sing_root </>) <$> listDirectory sing_root
  subdirs <- filterM doesDirectoryExist entries
  -- print entries
  -- print subdirs
  foldl (>>>) (return Nothing) [ testDir (sing_root </> d </> "share/factory") | d <- subdirs]

-- generic Linux
-- I guess typically it looks like this:
guessLinux :: IO (Maybe FilePath)
guessLinux =
  testDir "/usr/share/singular/factory" >>>
  testDir "/usr/share/singular"         >>>
  testDir "/usr/share/factory"          >>>
  testDir "/usr/local/share/singular/factory" >>>
  testDir "/usr/local/share/singular"   >>>
  testDir "/usr/local/share/factory"

-- cygwin
guessCygwin ::IO (Maybe FilePath)
guessCygwin = do
  cygwin_root <- readCreateProcess (shell "cygpath -w /") ""
  let test1 dir = testDir (cygwin_root </> dir)
  id <$>
    test1 "/usr/share/singular/factory" >>>
    test1 "/usr/share/singular"         >>>
    test1 "/usr/share/factory"          >>>
    test1 "/usr/local/share/singular/factory" >>>
    test1 "/usr/local/share/singular"   >>>
    test1 "/usr/local/share/factory"

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