{-# LANGUAGE CPP #-}

module Import
  ( module X
  -- * Utilities borrowed from 'errors'
  , hush
  , note
  , fmapL
    -- * Text-based string marshallers
  , withTString
  , withTStringLen
  , withMaybeTString
  , peekMaybeTString
    -- * memory cleanup
  , scrubbing
  , scrubbing_
  , scrubWith_
  , scrubWith
  ) where

#if MIN_VERSION_base(4,8,0)
#else
import Control.Applicative as X
#endif
import Data.Char (chr)
import Data.Monoid as X
import Foreign as X
import Foreign.C.Types as X

import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Foreign as T
import System.Win32.Error as X
import System.Win32.Error.Foreign as X
import System.Win32.Types as X
    hiding ( ErrCode, failIfNull, failWith, failUnlessSuccess
           , failIfFalse_, failIf, errorWin, withTString, withTStringLen)
import qualified System.Win32.Types as W32


-- |Peek a string that might be null. Null pointers become Nothing
peekMaybeTString :: LPTSTR -> IO (Maybe String)
peekMaybeTString ptr
  | ptr == nullPtr = return Nothing
  | otherwise      = Just <$> peekTString ptr

-- |Temporarily marshal a Maybe String. A nothing becomes a null pointer.
withMaybeTString :: Maybe String -> (LPTSTR -> IO a) -> IO a
withMaybeTString Nothing    f = f nullPtr
withMaybeTString (Just str) f = W32.withTString str f

withTStringLen :: Text -> (LPTSTR -> T.I16 -> IO a) -> IO a
withTStringLen text act = T.useAsPtr (T.snoc text (chr 0x0)) $ \ptr len ->
    act (castPtr ptr) len

withTString :: Text -> (LPTSTR -> IO a) -> IO a
withTString text act = withTStringLen text $ \ptr _ -> act ptr

-- | Suppress the 'Left' value of an 'Either'
-- taken from the errors package
hush :: Either a b -> Maybe b
hush = either (const Nothing) Just

-- taken from the errors package
note :: e -> Maybe a -> Either e a
note e Nothing = Left e
note _ (Just x) = Right x

-- taken from the errors package
fmapL :: (a -> b) -> Either a r -> Either b r
fmapL f (Left x) = Left (f x)
fmapL _ (Right x) = (Right x)

-- | Perform an action over a double pointer, and then zero it out. If the
-- double pointer is already zeroed out, do nothing and return 'Nothing'.
scrubbing :: (Ptr a -> IO b) -> Ptr (Ptr a) -> IO (Maybe b)
scrubbing f pptr = do
    ptr <- peek pptr
    if ptr == nullPtr
      then return Nothing
      else do
        ret <- f ptr
        poke pptr nullPtr
        return $ Just ret

-- |Perform a cleanup operation on memory.
scrubbing_ :: (Ptr a -> IO ()) -> Ptr (Ptr a) -> IO ()
scrubbing_ f pptr = do
    _ <- scrubbing f pptr
    return ()

scrubWith :: Ptr (Ptr a) -> (Ptr a -> IO b) -> IO (Maybe b)
scrubWith = flip scrubbing

scrubWith_ :: Ptr (Ptr a) -> (Ptr a -> IO ()) -> IO ()
scrubWith_ pptr f = do
    _ <-scrubWith pptr f
    return ()