-- | Start an overlapped I/O operation, and wait for its completion.  If
-- 'withOverlapped' is interrupted by an asynchronous exception, the operation
-- will be canceled using @CancelIo@.
--
-- 'withOverlapped' waits for a completion to arrive before returning or
-- throwing an exception.  This means you can use functions like
-- 'Foreign.Marshal.Alloc.alloca' to allocate buffers for the operation.
withOverlapped :: Manager
               -> HANDLE
               -> Word64 -- ^ Value to use for the @OVERLAPPED@
                         --   structure's Offset/OffsetHigh members.
               -> StartCallback
               -> CompletionCallback a
               -> IO a
withOverlapped mgr h offset startCB completionCB = do
    signal <- newEmptyMVar
    let signalReturn a = void $ tryPutMVar signal $ return a
        signalThrow ex = void $ tryPutMVar signal $ throwIO (ex :: SomeException)

    mask_ $ withWorker mgr h $ \enqueue -> do
        enqueue $ do
            let completionCB' e b = liftIO $
                    (completionCB e b >>= signalReturn) `E.catch` signalThrow

            e <- try $ newOverlapped offset completionCB'
            case e of
                Left ex -> signalThrow ex
                Right ol ->
                    startCB ol `E.catch` \ex -> do
                        FFI.discardOverlapped ol
                        signalThrow ex

        let cancel = uninterruptibleMask_ $ do
                cancelDone <- newEmptyMVar
                enqueue $ do
                    FFI.cancelIo h `E.catch` \ex -> do
                        traceIO $ "CancelIo failed: " ++ show (ex :: SomeException)
                        signalThrow ex
                    putMVar cancelDone ()
                _ <- takeMVar signal
                takeMVar cancelDone

        join (takeMVar signal `onException` cancel)

------------------------------------------------------------------------
-- Timeouts
withOverlapped :: Manager
               -> HANDLE
               -> Word64 -- ^ Value to use for the @OVERLAPPED@
                         --   structure's Offset/OffsetHigh members.
               -> StartCallback
               -> CompletionCallback a
               -> IO a
withOverlapped mgr h offset startCB completionCB = do
    signal <- newEmptyMVar
    let signalReturn a = void $ tryPutMVar signal $ return a
        signalThrow ex = void $ tryPutMVar signal $ throwIO (ex :: SomeException)

    mask_ $ withWorker mgr h $ \enqueue -> do
        enqueue $ do
            let completionCB' e b = liftIO $
                    (completionCB e b >>= signalReturn) `E.catch` signalThrow

            e <- try $ newOverlapped offset completionCB'
            case e of
                Left ex -> signalThrow ex
                Right ol ->
                    startCB ol `E.catch` \ex -> do
                        FFI.discardOverlapped ol
                        signalThrow ex

        let cancel = uninterruptibleMask_ $ do
                cancelDone <- newEmptyMVar
                enqueue $ do
                    FFI.cancelIo h `E.catch` \ex -> do
                        traceIO $ "CancelIo failed: " ++ show (ex :: SomeException)
                        signalThrow ex
                    putMVar cancelDone ()
                _ <- takeMVar signal
                takeMVar cancelDone

        join (takeMVar signal `onException` cancel)

------------------------------------------------------------------------
-- Timeouts
withOverlapped mgr h offset startCB completionCB = do
    signal <- newEmptyMVar
    let signalReturn a = void $ tryPutMVar signal $ return a
        signalThrow ex = void $ tryPutMVar signal $ throwIO (ex :: SomeException)

    mask_ $ withWorker mgr h $ \enqueue -> do
        enqueue $ do
            let completionCB' e b = liftIO $
                    (completionCB e b >>= signalReturn) `E.catch` signalThrow

            e <- try $ newOverlapped offset completionCB'
            case e of
                Left ex -> signalThrow ex
                Right ol ->
                    startCB ol `E.catch` \ex -> do
                        FFI.discardOverlapped ol
                        signalThrow ex

        let cancel = uninterruptibleMask_ $ do
                cancelDone <- newEmptyMVar
                enqueue $ do
                    FFI.cancelIo h `E.catch` \ex -> do
                        traceIO $ "CancelIo failed: " ++ show (ex :: SomeException)
                        signalThrow ex
                    putMVar cancelDone ()
                _ <- takeMVar signal
                takeMVar cancelDone

        join (takeMVar signal `onException` cancel)

------------------------------------------------------------------------
-- Timeouts