{-# LANGUAGE CPP, StandaloneDeriving, DeriveGeneric, DeriveDataTypeable #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

-- This module provies an interface for using sockets in RPC communication.
module IdeSession.RPC.Sockets (
    portToString
  , makeSocket
  , connectToPort
  , stringToPort
  , acceptHandle
  , ReadChannel(..)
  , WriteChannel(..)
) where

import System.IO (Handle)

import GHC.Generics (Generic)
import Data.Typeable (Typeable)
import Data.Binary
import Network
import Data.ByteString.Lazy.Char8
import qualified Data.ByteString.Base64.Lazy as Base64
import Network.Socket hiding (close, sClose, accept, socketPort)

#ifdef VERSION_unix
-- import Control.Exception
-- import Control.Concurrent.MVar
import System.IO.Temp (createTempDirectory)
import System.FilePath ((</>))
import System.Directory (getTemporaryDirectory)
#endif

newtype ReadChannel = ReadChannel PortID deriving (Generic, Typeable)
newtype WriteChannel = WriteChannel PortID deriving (Generic, Typeable)

instance Binary ReadChannel
instance Binary WriteChannel

-- Creating a socket with auto generated port (or file in case Unix domain
-- sockets are used)
makeSocket :: IO Socket

#ifdef VERSION_unix
makeSocket = do
  tmpDir <- getTemporaryDirectory
  rpcDir <- createTempDirectory tmpDir "ide-backend-rpc."
  let rpcFile = rpcDir </> "rpc"
  s <- listenOn $ UnixSocket rpcFile
  p <- socketPort s
  return s


#else
{- On non-Unix we use a plain socket -}
makeSocket = listenOn $ PortNumber aNY_PORT
#endif

connectToPort :: PortID -> IO Handle
connectToPort = connectTo "localhost"

acceptHandle :: Socket -> IO Handle
acceptHandle s = do
  (h, _, _) <- accept s
  return h

portToString :: PortID -> String
portToString = unpack . Base64.encode . encode

stringToPort :: String -> PortID
stringToPort = decode . Base64.decodeLenient . pack


{- Orphans -}
deriving instance Generic PortID
deriving instance Generic PortNumber
instance Binary PortID
instance Binary PortNumber