{-# LANGUAGE CPP #-}
{-# LANGUAGE InterruptibleFFI #-}
{-# LANGUAGE Trustworthy #-}
module Lukko.Internal.FD (
    FD (..),
    fdOpen,
    fdClose,
    handleToFd,
    ) where

import System.IO (Handle)
import GHC.Windows (iNVALID_HANDLE_VALUE, HANDLE, LPWSTR, BOOL, getLastError, failWith)
import Foreign.C.Types (CInt (..))
import Foreign.C.Error (throwErrnoIf)
import Foreign.C.String (withCWString)
import Foreign.Ptr (Ptr)

import qualified GHC.IO.FD        as GHC (FD (..))

import Lukko.Internal.HandleToFD (ghcHandleToFd)

-- | Opaque /file descriptor/
--
-- This is a wrapper over 'HANDLE'
newtype FD = FD HANDLE

-- | Open file to be used for locking
--
-- @
-- createFileW(path,
--   GENERIC_WRITE | GENERIC_READ,
--   FILE_SHARE_READ | FILE_SHARE_WRITE,
--   securityAttributes, // bInheritHandle = TRUE
--   OPEN_ALWAYS,
--   FILE_ATTRIBUTE_NORMAL,
--   NULL);
-- @
fdOpen :: FilePath -> IO FD
fdOpen fp = withCWString fp $ \cfp -> do
    fw <- c_fdOpen cfp
    if fw /= iNVALID_HANDLE_VALUE
    then return (FD fw)
    else getLastError >>= failWith "fdOpen"

-- | Close lock file.
--
-- @
-- CloseHandle(h);
-- @
fdClose :: FD -> IO ()
fdClose (FD fw) = do
    r <- c_CloseHandle fw
    if r
    then return ()
    else getLastError >>= failWith "fdClose"

-- | Convert GHC 'Handle' to lukko 'FD'.
handleToFd :: Handle -> IO FD
handleToFd h = do
    GHC.FD {GHC.fdFD = fd} <- ghcHandleToFd h
    wh <- throwErrnoIf (== iNVALID_HANDLE_VALUE) "handleToFd" $ c_get_osfhandle fd
    return (FD wh)

-- https://msdn.microsoft.com/en-us/library/aa297958.aspx
foreign import ccall unsafe "_get_osfhandle"
  c_get_osfhandle :: CInt -> IO HANDLE

-- Opening file is complicated
foreign import ccall interruptible "fdOpen"
  c_fdOpen :: LPWSTR -> IO HANDLE

#if defined(i386_HOST_ARCH)

-- https://docs.microsoft.com/en-gb/windows/win32/api/handleapi/nf-handleapi-closehandle
foreign import stdcall interruptible "CloseHandle"
  c_CloseHandle :: HANDLE -> IO BOOL

#elif defined(x86_64_HOST_ARCH)

-- https://docs.microsoft.com/en-gb/windows/win32/api/handleapi/nf-handleapi-closehandle
foreign import ccall interruptible "CloseHandle"
  c_CloseHandle :: HANDLE -> IO BOOL

#else
#error Unknown mingw32 arch
#endif