{-# LANGUAGE
    CPP
  , FlexibleContexts
  , FlexibleInstances
  , TypeFamilies
  , UnicodeSyntax
  #-}
-- |Type class for things behaves like a 'So.Socket'.
module Network.HTTP.Lucu.SocketLike
    ( SocketLike(..)
    )
    where
#if defined(HAVE_SSL)
import Control.Exception
#endif
import qualified Network.Socket as So
import Network.HTTP.Lucu.HandleLike
#if defined(HAVE_SSL)
import qualified OpenSSL.Session as SSL
import Prelude hiding (catch)
import Prelude.Unicode
#endif
import qualified System.IO as I

class (HandleLike (Handle s))  SocketLike s where
    type Handle s  
    accept         s  IO (Handle s, So.SockAddr)
    socketPort     s  IO So.PortNumber

instance SocketLike So.Socket where
    type Handle So.Socket = I.Handle

    accept soSelf
        = do (soPeer, addr)  So.accept soSelf
             hPeer           So.socketToHandle soPeer I.ReadWriteMode
             return (hPeer, addr)

    socketPort = So.socketPort

#if defined(HAVE_SSL)
instance SocketLike (SSL.SSLContext, So.Socket) where
    type Handle (SSL.SSLContext, So.Socket) = SSL.SSL

    accept (ctx, soSelf)
        = do (soPeer, addr)  So.accept soSelf
             ssl             SSL.connection ctx soPeer
             handshake ssl addr `catch` next ssl addr
        where
          handshake  SSL.SSL  So.SockAddr  IO (SSL.SSL, So.SockAddr)
          handshake ssl addr
              = do SSL.accept ssl
                   return (ssl, addr)

          next  SSL.SSL
                So.SockAddr
                SSL.SomeSSLException
                IO (SSL.SSL, So.SockAddr)
          next ssl addr e
              = do I.hPutStrLn I.stderr
                       $ "Lucu: failed to accept an SSL connection from "
                        show addr
                        ":"
                   I.hPutStrLn I.stderr
                       $ show e
                   SSL.shutdown ssl SSL.Bidirectional
                   accept (ctx, soSelf)

    socketPort = So.socketPort  snd
#endif