module Network.HTTP2.Client.Channels (
   FramesChan
 , HeadersChan
 , PushPromisesChan
 , waitHeaders
 , waitHeadersWithStreamId
 , waitFrame
 , waitFrameWithStreamId
 , waitPushPromiseWithParentStreamId
 , waitFrameWithTypeId
 , waitFrameWithTypeIdForStreamId
 , isPingReply
 , isSettingsReply
 , hasStreamId
 , hasTypeId
 , whenFrame
 , whenFrameElse
 , module Control.Concurrent.Chan
 ) where

import           Control.Concurrent.Chan (Chan, readChan, newChan, dupChan, writeChan)
import           Control.Exception (Exception, throwIO)
import           Data.ByteString (ByteString)
import           Network.HPACK as HPACK
import           Network.HTTP2 as HTTP2

type FramesChan e = Chan (FrameHeader, Either e FramePayload)

type HeadersChanContent = (FrameHeader, StreamId, Either ErrorCode HeaderList)

type HeadersChan = Chan HeadersChanContent

type PushPromisesChanContent e = (StreamId, FramesChan e, HeadersChan, StreamId, HeaderList)

type PushPromisesChan e = Chan (PushPromisesChanContent e)

waitFrameWithStreamId
  :: Exception e
  => StreamId
  -> FramesChan e
  -> IO (FrameHeader, FramePayload)
waitFrameWithStreamId sid = waitFrame (\h _ -> streamId h == sid)

waitFrameWithTypeId
  :: (Exception e)
  => [FrameTypeId]
  -> FramesChan e
  -> IO (FrameHeader, FramePayload)
waitFrameWithTypeId tids = waitFrame (\_ p -> HTTP2.framePayloadToFrameTypeId p `elem` tids)

waitFrameWithTypeIdForStreamId
  :: (Exception e)
  => StreamId
  -> [FrameTypeId]
  -> FramesChan e
  -> IO (FrameHeader, FramePayload)
waitFrameWithTypeIdForStreamId sid tids =
    waitFrame (\h p -> streamId h == sid && HTTP2.framePayloadToFrameTypeId p `elem` tids)

waitFrame
  :: Exception e
  => (FrameHeader -> FramePayload -> Bool)
  -> FramesChan e
  -> IO (FrameHeader, FramePayload)
waitFrame test chan =
    loop
  where
    loop = do
        (fHead, fPayload) <- readChan chan
        dat <- either throwIO pure fPayload
        if test fHead dat
        then return (fHead, dat)
        else loop

whenFrame
  :: Exception e
  => (FrameHeader -> FramePayload -> Bool)
  -> (FrameHeader, Either e FramePayload)
  -> ((FrameHeader, FramePayload) -> IO ())
  -> IO ()
whenFrame test (fHead, fPayload) handle = do
    dat <- either throwIO pure fPayload
    if test fHead dat
    then handle (fHead, dat)
    else pure ()

whenFrameElse
  :: Exception e
  => (FrameHeader -> FramePayload -> Bool)
  -> (FrameHeader, Either e FramePayload)
  -> ((FrameHeader, FramePayload) -> IO a)
  -> ((FrameHeader, FramePayload) -> IO a)
  -> IO a
whenFrameElse test (fHead, fPayload) handleTrue handleFalse = do
    dat <- either throwIO pure fPayload
    if test fHead dat
    then handleTrue (fHead, dat)
    else handleFalse (fHead, dat)

hasStreamId :: StreamId -> FrameHeader -> FramePayload -> Bool
hasStreamId sid h _ = streamId h == sid

hasTypeId :: [FrameTypeId] -> FrameHeader -> FramePayload -> Bool
hasTypeId tids _ p = HTTP2.framePayloadToFrameTypeId p `elem` tids

isPingReply :: ByteString -> FrameHeader -> FramePayload -> Bool
isPingReply datSent _ (PingFrame datRcv) = datSent == datRcv
isPingReply _       _ _                  = False

isSettingsReply :: FrameHeader -> FramePayload -> Bool
isSettingsReply fh (SettingsFrame _) = HTTP2.testAck (flags fh)
isSettingsReply _ _                  = False

waitHeadersWithStreamId
  :: StreamId
  -> HeadersChan
  -> IO HeadersChanContent
waitHeadersWithStreamId sid =
    waitHeaders (\_ s _ -> s == sid)

waitHeaders
  :: (FrameHeader -> StreamId -> Either ErrorCode HeaderList -> Bool)
  -> HeadersChan
  -> IO HeadersChanContent
waitHeaders test chan =
    loop
  where
    loop = do
        tuple@(fH, sId, hdrs) <- readChan chan
        if test fH sId hdrs
        then return tuple
        else loop

waitPushPromiseWithParentStreamId
  :: StreamId
  -> PushPromisesChan e
  -> IO (PushPromisesChanContent e)
waitPushPromiseWithParentStreamId sid chan =
    loop
  where
    loop = do
        tuple@(parentSid,_,_,_,_) <- readChan chan
        if parentSid == sid
        then return tuple
        else loop