module Network.HTTP.Lucu.ResponseWriter
    ( responseWriter
    )
    where

import qualified Data.ByteString.Lazy.Char8 as C8
import           Control.Concurrent
import           Control.Concurrent.STM
import           Control.Exception
import           Control.Monad
import qualified Data.Sequence as S
import           Data.Sequence (ViewR(..))
import           Network.HTTP.Lucu.Config
import           Network.HTTP.Lucu.Format
import           Network.HTTP.Lucu.Headers
import           Network.HTTP.Lucu.HttpVersion
import           Network.HTTP.Lucu.Interaction
import           Network.HTTP.Lucu.Postprocess
import           Network.HTTP.Lucu.Response
import           Prelude hiding (catch)
import           System.IO


responseWriter :: Config -> Handle -> InteractionQueue -> ThreadId -> IO ()
responseWriter !cnf !h !tQueue !readerTID
    = awaitSomethingToWrite
      `catches`
      [ Handler (( \ _                   -> return () ) :: IOException -> IO ())
      , Handler  ( \ ThreadKilled        -> return () )
      , Handler  ( \ BlockedIndefinitely -> hPutStrLn stderr "requestWriter: blocked indefinitely" )
      , Handler (( \ e                   -> hPutStrLn stderr (show e) ) :: SomeException -> IO ())
      ]
    where
      awaitSomethingToWrite :: IO ()
      awaitSomethingToWrite 
          = {-# SCC "awaitSomethingToWrite" #-}
            do action
                   <- atomically $!
                      -- キューが空でなくなるまで待つ
                      do queue <- readTVar tQueue
                         -- GettingBody 状態にあり、Continue が期待され
                         -- てゐて、それがまだ送信前なのであれば、
                         -- Continue を送信する。
                         case S.viewr queue of
                           EmptyR   -> retry
                           _ :> itr -> do state <- readItr itr itrState id

                                          if state == GettingBody then
                                              writeContinueIfNecessary itr
                                            else
                                              if state >= DecidingBody then
                                                  writeHeaderOrBodyIfNecessary itr
                                              else
                                                  retry
               action

      writeContinueIfNecessary :: Interaction -> STM (IO ())
      writeContinueIfNecessary itr
          = {-# SCC "writeContinueIfNecessary" #-}
            itr `seq`
            do expectedContinue <- readItr itr itrExpectedContinue id
               if expectedContinue then
                   do wroteContinue <- readItr itr itrWroteContinue id
                      if wroteContinue then
                          -- 既に Continue を書込み濟
                          retry
                        else
                          do reqBodyWanted <- readItr itr itrReqBodyWanted id
                             if reqBodyWanted /= Nothing then
                                 return $ writeContinue itr
                               else
                                 retry
                 else
                   retry

      writeHeaderOrBodyIfNecessary :: Interaction -> STM (IO ())
      writeHeaderOrBodyIfNecessary itr
          -- DecidingBody 以降の状態にあり、まだヘッダを出力する前であ
          -- れば、ヘッダを出力する。ヘッダ出力後であり、bodyToSend が
          -- 空でなければ、それを出力する。空である時は、もし状態が
          -- Done であれば後処理をする。
          = {-# SCC "writeHeaderOrBodyIfNecessary" #-}
            itr `seq`
            do wroteHeader <- readItr itr itrWroteHeader id
               
               if not wroteHeader then
                   return $! writeHeader itr
                 else
                   do bodyToSend <- readItr itr itrBodyToSend id

                      if C8.null bodyToSend then
                          do state <- readItr itr itrState id

                             if state == Done then
                                 return $! finalize itr
                               else
                                 retry
                        else
                          return $! writeBodyChunk itr

      writeContinue :: Interaction -> IO ()
      writeContinue itr
          = {-# SCC "writeContinue" #-}
            itr `seq`
            do let cont = Response {
                            resVersion = HttpVersion 1 1
                          , resStatus  = Continue
                          , resHeaders = emptyHeaders
                          }
               cont' <- completeUnconditionalHeaders cnf cont
               hPutResponse h cont'
               hFlush h
               atomically $! writeItr itr itrWroteContinue True
               awaitSomethingToWrite

      writeHeader :: Interaction -> IO ()
      writeHeader itr
          = {-# SCC "writeHeader" #-}
            itr `seq`
            do res <- atomically $! do writeItr itr itrWroteHeader True
                                       readItr itr itrResponse id
               hPutResponse h res
               hFlush h
               awaitSomethingToWrite
      
      writeBodyChunk :: Interaction -> IO ()
      writeBodyChunk itr
          = {-# SCC "writeBodyChunk" #-}
            itr `seq`
            do willDiscardBody <- atomically $! readItr itr itrWillDiscardBody id
               willChunkBody   <- atomically $! readItr itr itrWillChunkBody   id
               chunk           <- atomically $! do chunk <- readItr itr itrBodyToSend id
                                                   writeItr itr itrBodyToSend C8.empty
                                                   return chunk
               unless willDiscardBody
                          $ do if willChunkBody then
                                   do hPutStr h (fmtHex False 0 $! fromIntegral $! C8.length chunk)
                                      C8.hPut h (C8.pack "\r\n")
                                      C8.hPut h chunk
                                      C8.hPut h (C8.pack "\r\n")
                                 else
                                   C8.hPut h chunk
                               hFlush h
               awaitSomethingToWrite

      finishBodyChunk :: Interaction -> IO ()
      finishBodyChunk itr
          = {-# SCC "finishBodyChunk" #-}
            itr `seq`
            do willDiscardBody <- atomically $! readItr itr itrWillDiscardBody id
               willChunkBody   <- atomically $! readItr itr itrWillChunkBody   id
               when (not willDiscardBody && willChunkBody)
                        $ C8.hPut h (C8.pack "0\r\n\r\n") >> hFlush h

      finalize :: Interaction -> IO ()
      finalize itr
          = {-# SCC "finalize" #-}
            itr `seq`
            do finishBodyChunk itr
               willClose <- atomically $!
                            do queue <- readTVar tQueue

                               case S.viewr queue of
                                 EmptyR         -> return () -- this should never happen
                                 remaining :> _ -> writeTVar tQueue remaining

                               readItr itr itrWillClose id
               if willClose then
                   -- reader は恐らく hWaitForInput してゐる最中なので、
                   -- スレッドを豫め殺して置かないとをかしくなる。
                   do killThread readerTID
                      hClose h
                 else
                   awaitSomethingToWrite