{-# LANGUAGE OverloadedStrings #-}

module Network.HTTP.Pony.Serve where

import           Control.Exception (IOException)
import           Control.Monad.Catch (MonadCatch)
import           Control.Monad.Catch (MonadMask)
import           Control.Monad.IO.Class (MonadIO(..))
import           Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import           Data.Monoid ((<>))
import qualified Network.Socket as NS
import qualified Network.Socket.ByteString as NSB
import           Pipes (Effect, Producer, Consumer, runEffect, (>->), await)
import           Pipes.Network.TCP.Safe (HostPreference())
import           Pipes.Network.TCP.Safe (fromSocket, toSocket, connect, connectSock
                                        , closeSock)
import qualified Pipes.Network.TCP.Safe as PipesNetwork
import           Pipes.Safe (MonadSafe(), runSafeT, SafeT, tryP, throwM)

import           Network.HTTP.Pony.Helper ((-), shutdownSend, shutdownReceive)
import           Network.HTTP.Pony.ServeSafe (serveWithPipe)
import           Prelude hiding ((-), log)


serveWithSocket :: (MonadSafe m, MonadCatch m)  => (NS.Socket, NS.SockAddr)
                                -> (Producer ByteString m () -> m (Producer ByteString m ()))
                                -> m ()
serveWithSocket (s,_) =
  let
    pull = fromSocket s 4096
    push = do
      x <- await
      -- liftIO (putStrLn ( "pony chunk: " ++ show (B.length x) ++ " - " ++ B.unpack x ))

      if B.null x
        then do
          -- liftIO - do
          --   connected <- NS.isConnected s
          --   putStrLn - "socket connected? " <> show connected
          --   if connected
          --      then do
          --       shutdownSend s
          --      else
          --       pure ()

          let cont = () <$ await
          r <- tryP (shutdownSend s)
          case r of
            Right _ -> cont
            Left e -> do
              -- liftIO - putStrLn - "Caught: " <> show e

              cont

              throwM (e :: IOException)

        else do
          liftIO (NSB.send s x)
          push
  in

  serveWithPipe pull push

serveT :: (MonadSafe m, MonadMask n, MonadIO n)
               => (n () -> IO ())
               -> HostPreference
               -> NS.ServiceName
               -> (Producer ByteString (SafeT n) ()
                   -> (SafeT n) (Producer ByteString (SafeT n) ()))
               -> m ()
serveT exit host service app =
  PipesNetwork.serve host service - \socket -> do
    -- no-delay is specifically encouraged in HTTP/2
    -- https://http2.github.io/faq/#will-i-need-tcpnodelay-for-my-http2-connections
    liftIO - NS.setSocketOption (fst socket) NS.NoDelay 1

    exit - runSafeT (serveWithSocket socket app)


serve, run :: (MonadSafe m)  => HostPreference
                        -> NS.ServiceName
                        -> (Producer ByteString (SafeT IO) ()
                            -> (SafeT IO) (Producer ByteString (SafeT IO) ()))
                        -> m ()
serve = serveT id
run = serve