module Hans.Socket.Handle(makeHansHandle) where

import Control.Concurrent
import Control.Concurrent.MVar
import Control.Exception
import Control.Monad
import qualified Data.ByteString as BSS
import Data.ByteString.Lazy(ByteString)
import qualified Data.ByteString.Lazy as BS
import Data.Typeable
import Foreign.Ptr
import GHC.IO.Buffer
import GHC.IO.BufferedIO
import GHC.IO.Device
import GHC.IO.Handle
import Hans.Layer.Tcp.Socket(canSend, canRecv)
import Hans.NetworkStack
import Prelude hiding (read)
import System.IO

data BufferedSocket = BS {
    bsSocket   :: Socket
  , bsRefCount :: MVar Int
  }
 deriving (Typeable)

newBufferedSocket :: Socket -> IO BufferedSocket
newBufferedSocket sock =
  do mv <- newMVar 1
     return (BS sock mv)

addRef :: BufferedSocket -> IO ()
addRef bs = modifyMVar_ (bsRefCount bs) (return . (+ 1))

dropRef :: BufferedSocket -> IO ()
dropRef bs =
  do count <- modifyMVar (bsRefCount bs) $
                \ x ->
                  let x' = x - 1
                  in return (x', x')
     when (count == 0) $ Hans.NetworkStack.close (bsSocket bs)

instance IODevice BufferedSocket where
  ready dev write msecs =
    do let tester = if write then canSend else canRecv
       canDo <- tester (bsSocket dev)
       if | canDo      -> return True
          | msecs <= 0 -> return False
          | otherwise  -> do let delay = min msecs 100
                             threadDelay (delay * 1000)
                             ready dev write (msecs - delay)
  close bs     = dropRef bs
  isTerminal _ = return False
  isSeekable _ = return False
  seek _ _ _   = throwIO (userError "Seek on HaNS socket.")
  tell _       = throwIO (userError "Tell on HaNS socket.")
  getSize _    = throwIO (userError "getSize on HaNS socket.")
  setSize _ _  = throwIO (userError "setSize on HaNS socket.")
  setEcho _ _  = throwIO (userError "setEcho on HaNS socket.")
  getEcho _    = throwIO (userError "getEcho on HaNS socket.")
  setRaw _ _   = return ()
  devType _    = return Stream
  dup bs       = addRef bs >> return bs
  dup2 _ _     = throwIO (userError "dup2 on HaNS socket.")

instance RawIO BufferedSocket where
  read sock dptr sz =
    do bstr <- recvBytes (bsSocket sock) (fromIntegral sz)
       when (BS.length bstr > 0) $ copyBS (BS.toChunks bstr) dptr sz
       return (fromIntegral (BS.length bstr))
  readNonBlocking sock dptr sz =
    do canGo <- canRecv (bsSocket sock)
       if canGo
          then Just `fmap` read sock dptr sz
          else return (Just 0)
  write sock ptr sz =
    do bstr <- BSS.packCStringLen (castPtr ptr, sz)
       sendAll (bsSocket sock) (BS.fromStrict bstr)
   where
    sendAll sock bstr
      | BS.null bstr = return ()
      | otherwise    = do num <- sendBytes sock bstr
                          sendAll sock (BS.drop (fromIntegral num) bstr)
  writeNonBlocking sock ptr sz =
    do canGo <- canSend (bsSocket sock)
       if canGo
          then do bstr <- BSS.packCStringLen (castPtr ptr, sz)
                  num  <- sendBytes (bsSocket sock) (BS.fromStrict bstr)
                  return (fromIntegral num)
          else return 0

instance BufferedIO BufferedSocket where
  newBuffer         _ = newByteBuffer (64 * 1024)
  fillReadBuffer      = readBuf
  fillReadBuffer0     = readBufNonBlocking
  flushWriteBuffer    = writeBuf
  flushWriteBuffer0   = writeBufNonBlocking

makeHansHandle :: Socket -> IOMode -> IO Handle
makeHansHandle socket mode =
  do buffSocket <- newBufferedSocket socket
     mkFileHandle buffSocket "<socket>" mode Nothing noNewlineTranslation

copyBS :: [BSS.ByteString] -> Ptr a -> Int -> IO ()
copyBS [] _ _ = return ()
copyBS (f:rest) sptr szLeft
  | BSS.null f   = copyBS rest sptr szLeft
  | szLeft <= 0  = return ()
  | otherwise    =
      do let (chunk1, chunk2) = BSS.splitAt szLeft f
             amt              = fromIntegral (BSS.length chunk1)
         BSS.useAsCString chunk1 $ \ dptr -> memcpy dptr sptr amt
         copyBS (chunk2 : rest) (sptr `plusPtr` amt) (szLeft - amt)

foreign import ccall unsafe "string.h memcpy"
  memcpy :: Ptr a -> Ptr b -> Int -> IO ()
