{-# LANGUAGE CPP, RecordWildCards #-}
module IdeSession.Util.PortableIO where

import System.IO (Handle, IOMode(..))

#ifdef VERSION_unix
import System.Posix.Types (Fd)
import qualified System.Posix.IO as Posix
import System.Posix.Files
#else
import Foreign.C.Error (throwErrnoIfMinus1_, throwErrnoIfMinus1)
import Foreign.C.Types (CInt(..), CUInt(..))
import Foreign.Ptr (Ptr)
import Foreign.Marshal.Array (allocaArray)
import Foreign.Storable (peek, peekElemOff)
import GHC.IO.FD (mkFD)
import GHC.IO.Device (IODeviceType(Stream))
import GHC.IO.Handle.FD (mkHandleFromFD)
import System.IO (openFile)

{- For the reimplementation of handleToFd from System.Posix.IO -}
import System.IO.Error
import GHC.IO.Handle.Internals
import GHC.IO.Handle.Types hiding (Handle)
import qualified GHC.IO.FD as FD
import GHC.IO.Exception
import Data.Typeable (cast)
#endif

#ifdef VERSION_unix
type FileDescriptor = Fd
#else
type FileDescriptor = CInt
#endif

createPipe :: IO (FileDescriptor, FileDescriptor)
fdToHandle :: FileDescriptor -> IOMode -> IO Handle
handleToFd :: Handle -> IO FileDescriptor
closeFd :: FileDescriptor -> IO ()

dup :: FileDescriptor -> IO FileDescriptor
dupTo :: FileDescriptor -> FileDescriptor -> IO FileDescriptor

-- Opens, and possibly creates, the given file for writing
openWritableFile :: FilePath -> IO FileDescriptor

stdInput, stdOutput, stdError :: FileDescriptor

#ifdef VERSION_unix

createPipe = Posix.createPipe
fdToHandle fd _ = Posix.fdToHandle fd
handleToFd = Posix.handleToFd
closeFd = Posix.closeFd

dup = Posix.dup
dupTo = Posix.dupTo

openWritableFile fp = Posix.openFd fp Posix.WriteOnly (Just mode) Posix.defaultFileFlags
  where
    mode = unionFileModes ownerReadMode ownerWriteMode

stdInput = Posix.stdInput
stdOutput = Posix.stdOutput
stdError = Posix.stdError

#else

createPipe = allocaArray 2 $ \pfds -> do
    throwErrnoIfMinus1_ "_pipe" $ c__pipe pfds 2 ({- _O_BINARY -} 32768)
    readfd <- peek pfds
    writefd <- peekElemOff pfds 1
    return (readfd, writefd)

fdToHandle fd mode = do
    (fd', deviceType) <- mkFD fd mode (Just (Stream, 0, 0)) False False
    mkHandleFromFD fd' deviceType "" mode False Nothing

closeFd = throwErrnoIfMinus1_ "_close" . c__close

dup = throwErrnoIfMinus1 "_dup" . c__dup

dupTo fd1 fd2 = throwErrnoIfMinus1 "_dup2" $ c__dup2 fd1 fd2

-- Previously this was a ccall to '_open' in 'io.h'
-- TODO could this implementation work for the Unix version?
openWritableFile fp = openFile fp WriteMode >>= handleToFd

stdInput = 0
stdOutput = 1
stdError = 2

{- Copied from System.Posix.IO -}
handleToFd h@(FileHandle _ m) =
  withHandle' "handleToFd" h m $ handleToFd' h
handleToFd h@(DuplexHandle _ r w) = do
  _ <- withHandle' "handleToFd" h r $ handleToFd' h
  withHandle' "handleToFd" h w $ handleToFd' h
  -- for a DuplexHandle, make sure we mark both sides as closed,
  -- otherwise a finalizer will come along later and close the other
  -- side. (#3914)

handleToFd' :: Handle -> Handle__ -> IO (Handle__, FileDescriptor)
handleToFd' h h_@Handle__{haType=_,..} =
  case cast haDevice of
    Nothing -> ioError (ioeSetErrorString (mkIOError IllegalOperation
                                           "handleToFd" (Just h) Nothing)
                        "handle is not a file descriptor")
    Just fd -> do
     -- converting a Handle into an Fd effectively means
     -- letting go of the Handle; it is put into a closed
     -- state as a result.
     flushWriteBuffer h_
     FD.release fd
     return (Handle__{haType=ClosedHandle,..}, FD.fdFD fd)

foreign import ccall "io.h _pipe" c__pipe ::
    Ptr CInt -> CUInt -> CInt -> IO CInt

foreign import ccall "io.h _close" c__close ::
    CInt -> IO CInt

foreign import ccall "io.h _dup" c__dup ::
    CInt -> IO CInt

foreign import ccall "io.h _dup2" c__dup2 ::
    CInt -> CInt -> IO CInt

#endif