{-# LANGUAGE CPP             #-}
{-# LANGUAGE RoleAnnotations #-}
-- | Exposed internals of `Foreign.SharedObjectName`.
--
module Foreign.SharedObjectName.Internal
  ( SOName (..), hPutSOName, hGetSOName, unsafeWithSOName
  , genSOName, newEmptySOName
  ) where

import           Foreign.C.String
import           Foreign.C.Types
import           Foreign.ForeignPtr
import           Foreign.ForeignPtr.Unsafe
import           Foreign.Ptr
import           Foreign.Storable
import           System.IO
import           System.IO.Unsafe
import           Text.Read

#define HS_IMPORT_CONSTANTS_ONLY
#include "SharedObjectName.h"
#include "MachDeps.h"

-- | Reference to a shared object; can be sent to other processes.
newtype SOName a = SOName (ForeignPtr CChar)
type role SOName phantom

instance Show (SOName a) where
    showsPrec d (SOName a)
      = showParen (d >= 10) $ showString "SOName " . showsPrec 10 getstr
      where
        getstr = unsafePerformIO $ withForeignPtr a peekCAString
        {-# NOINLINE getstr #-}

instance Read (SOName a) where
    readPrec = parens $ prec 10 $ do
        Ident "SOName" <- lexP
        s <- step readPrec
        return $ putstr s
      where
        writeStr [] n     ptr
          = pokeElemOff ptr n 0 -- put end of string character
        writeStr (c:cs) n ptr
          = pokeElemOff ptr n (castCharToCChar c) >> writeStr cs (n+1) ptr
        putstr s = unsafePerformIO $ do
          n <- newEmptySOName
          unsafeWithSOName n $ writeStr s 0
          return n
        {-# NOINLINE putstr #-}

instance Eq (SOName a) where
    (SOName a) == (SOName b)
      = cmpCStrings (unsafeForeignPtrToPtr a) (unsafeForeignPtrToPtr b) == EQ

instance Ord (SOName a) where
    compare (SOName a) (SOName b)
      = cmpCStrings (unsafeForeignPtrToPtr a) (unsafeForeignPtrToPtr b)

instance Storable (SOName a) where
    sizeOf _ = SHARED_OBJECT_NAME_LENGTH
    alignment _ = SIZEOF_HSWORD
    poke p (SOName qp) = withForeignPtr qp
        $ \q -> c_memcpy p q SHARED_OBJECT_NAME_LENGTH
    peek p = do
      qp <- mallocForeignPtrBytes SHARED_OBJECT_NAME_LENGTH
      withForeignPtr qp
        $ \q -> c_memcpy q p SHARED_OBJECT_NAME_LENGTH
      return $ SOName qp

-- | Write a shared object name into somwhere referenced by a handle.
--   Useful for sending references to other processes via pipes.
hPutSOName :: Handle -> SOName a -> IO ()
hPutSOName h (SOName q)
    = withForeignPtr q $ flip (hPutBuf h) SHARED_OBJECT_NAME_LENGTH

-- | Read a shared object name from somwhere referenced by a handle.
--   Returns @Nothing@ if @hGetBuf@ gets less than @SHARED_OBJECT_NAME_LENGTH@ bytes.
--   Useful for sending references to other processes via pipes.
hGetSOName :: Handle -> IO (Maybe (SOName a))
hGetSOName h = do
    let n = SHARED_OBJECT_NAME_LENGTH
    q <- mallocForeignPtrBytes n
    n' <- withForeignPtr q $ \p -> hGetBuf h p n
    return $
      if n' < n
      then Nothing
      else Just (SOName q)

-- | Generate a new unique shared object name.
genSOName :: IO (SOName a)
genSOName = do
    fp <- mallocForeignPtrBytes SHARED_OBJECT_NAME_LENGTH
    withForeignPtr fp c_genSharedObjectName
    return $ SOName fp

-- | Allocate a new shared object name.
newEmptySOName :: IO (SOName a)
newEmptySOName = SOName <$> mallocForeignPtrBytes SHARED_OBJECT_NAME_LENGTH


-- | Use a pointer to a C string to pass to some low-level (e.g. foreign) functions.
--   `SOName` is asserted immutable, so do not modify it!
unsafeWithSOName :: SOName a -> (CString -> IO b) -> IO b
unsafeWithSOName (SOName fp) = withForeignPtr fp

-- | Check first if two CString point to the same memory location.
--   Otherwise, compare them using C @strcmp@ function.
cmpCStrings :: CString -> CString -> Ordering
cmpCStrings a b
    | a == b = EQ
    | otherwise = c_strcmp a b `compare` 0

foreign import ccall unsafe "strcmp"
    c_strcmp :: CString -> CString -> CInt

foreign import ccall unsafe "memcpy"
    c_memcpy :: Ptr a -> Ptr b -> CInt -> IO ()

foreign import ccall unsafe "genSharedObjectName"
    c_genSharedObjectName :: CString -> IO ()