-- | This module contains functioanlity only for use
-- by other XHB modules, while still trying to hide some
-- of the implementation details of the 'Connection'
-- data type.

module Graphics.XHB.Connection.Internal
    (sendRequest
    ,sendRequestWithReply
    ,lookupExtension
    ,cacheExtension
    ,Connection
    ) where

import Data.Word(Word16)
import Control.Exception(bracket)

import Control.Concurrent.STM
import Control.Concurrent

import System.IO

import Data.ByteString.Lazy(ByteString)
import qualified Data.ByteString.Lazy as BS

import Data.Maybe
import qualified Data.Map as M

import Graphics.XHB.Connection.Types
import Graphics.XHB.Shared

import Graphics.XHB.Gen.Xproto.Types

-- Assumes that the input bytestring is a properly formatted
-- and padded request.
sendRequest :: Connection -> ByteString -> IO ()
sendRequest c bytes = withConnectionHandle c $ \h -> do

  -- send bytes onto connection
  BS.hPut h bytes

  -- increment sequence
  _ <- atomically $ nextSequence c

  return ()

sendRequestWithReply :: Connection -> ByteString -> RawReceipt -> IO ()
sendRequestWithReply c bytes r = withConnectionHandle c $ \h -> do

  BS.hPut h bytes

  atomically $ do
    seq <- nextSequence c
    writeTChan (conn_reps c) $ PendedReply seq r


-- Returns the next sequence ID
nextSequence :: Connection -> STM SequenceId
nextSequence c = do
  let tv = conn_next_sequence c
  seq <- readTVar tv
  writeTVar tv (seq + 1)
  return seq

-- Locks the handle for use by the passed in function.  Intended for
-- write access only.
--
-- NOTE: the read loop has a separate reference to the handle,
-- so it will not be blocked by this.
withConnectionHandle :: Connection -> (Handle -> IO a) -> IO a
withConnectionHandle c f = do
  let mv = conn_handle c
  bracket
     (takeMVar mv)
     (putMVar mv)
     f
{-# INLINE withConnectionHandle #-}

-- | Lookup an extension in the extension cache.  Returns 'Nothing'
-- if queried extension is not cached
lookupExtension :: Connection -> ExtensionId -> IO (Maybe QueryExtensionReply)
lookupExtension c extId = atomically $ do
   m <- readTVar $ conn_extensions c
   return $ M.lookup extId m

-- | Add an extension to the extension cache.
cacheExtension :: Connection -> ExtensionId -> QueryExtensionReply -> IO ()
cacheExtension c extId ext = atomically $ do
   let tv = conn_extensions c

   m <- readTVar tv
   if isNothing (M.lookup extId m)
    then 
      let m' = M.insert extId ext m
      in writeTVar tv m'
    else return ()