/*
 * nghttp3
 *
 * Copyright (c) 2019 nghttp3 contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "nghttp3_stream.h"

#include <string.h>
#include <assert.h>
#include <stdio.h>

#include "nghttp3_conv.h"
#include "nghttp3_macro.h"
#include "nghttp3_frame.h"
#include "nghttp3_conn.h"
#include "nghttp3_str.h"
#include "nghttp3_http.h"
#include "nghttp3_vec.h"
#include "nghttp3_unreachable.h"

/* NGHTTP3_STREAM_MAX_COPY_THRES is the maximum size of buffer which
   makes a copy to outq. */
#define NGHTTP3_STREAM_MAX_COPY_THRES 128

/* NGHTTP3_MIN_RBLEN is the minimum length of nghttp3_ringbuf */
#define NGHTTP3_MIN_RBLEN 4

nghttp3_objalloc_def(stream, nghttp3_stream, oplent)

int nghttp3_stream_new(nghttp3_stream **pstream, int64_t stream_id,
                       const nghttp3_stream_callbacks *callbacks,
                       nghttp3_objalloc *out_chunk_objalloc,
                       nghttp3_objalloc *stream_objalloc,
                       const nghttp3_mem *mem) {
  nghttp3_stream *stream = nghttp3_objalloc_stream_get(stream_objalloc);

  if (stream == NULL) {
    return NGHTTP3_ERR_NOMEM;
  }

  *stream = (nghttp3_stream){
    .out_chunk_objalloc = out_chunk_objalloc,
    .stream_objalloc = stream_objalloc,
    .qpack_blocked_pe.index = NGHTTP3_PQ_BAD_INDEX,
    .mem = mem,
    .rx =
      {
        .http.status_code = -1,
        .http.content_length = -1,
        .http.pri.urgency = NGHTTP3_DEFAULT_URGENCY,
      },
    .error_code = NGHTTP3_H3_NO_ERROR,
  };

  nghttp3_tnode_init(&stream->node, stream_id);

  nghttp3_ringbuf_init(&stream->frq, 0, sizeof(nghttp3_frame), mem);
  nghttp3_ringbuf_init(&stream->chunks, 0, sizeof(nghttp3_buf), mem);
  nghttp3_ringbuf_init(&stream->outq, 0, sizeof(nghttp3_typed_buf), mem);
  nghttp3_ringbuf_init(&stream->inq, 0, sizeof(nghttp3_buf), mem);

  nghttp3_qpack_stream_context_init(&stream->qpack_sctx, stream_id, mem);

  if (callbacks) {
    stream->callbacks = *callbacks;
  }

  *pstream = stream;

  return 0;
}

static void delete_outq(nghttp3_ringbuf *outq, const nghttp3_mem *mem) {
  nghttp3_typed_buf *tbuf;
  size_t i, len = nghttp3_ringbuf_len(outq);

  for (i = 0; i < len; ++i) {
    tbuf = nghttp3_ringbuf_get(outq, i);
    if (tbuf->type == NGHTTP3_BUF_TYPE_PRIVATE) {
      nghttp3_buf_free(&tbuf->buf, mem);
    }
  }

  nghttp3_ringbuf_free(outq);
}

static void delete_chunks(nghttp3_ringbuf *chunks, const nghttp3_mem *mem) {
  nghttp3_buf *buf;
  size_t i, len = nghttp3_ringbuf_len(chunks);

  for (i = 0; i < len; ++i) {
    buf = nghttp3_ringbuf_get(chunks, i);
    nghttp3_buf_free(buf, mem);
  }

  nghttp3_ringbuf_free(chunks);
}

static void delete_out_chunks(nghttp3_ringbuf *chunks,
                              nghttp3_objalloc *out_chunk_objalloc,
                              const nghttp3_mem *mem) {
  nghttp3_buf *buf;
  size_t i, len = nghttp3_ringbuf_len(chunks);

  for (i = 0; i < len; ++i) {
    buf = nghttp3_ringbuf_get(chunks, i);

    if (nghttp3_buf_cap(buf) == NGHTTP3_STREAM_MIN_CHUNK_SIZE) {
      nghttp3_objalloc_chunk_release(out_chunk_objalloc, (void *)buf->begin);
      continue;
    }

    nghttp3_buf_free(buf, mem);
  }

  nghttp3_ringbuf_free(chunks);
}

static void delete_frq(nghttp3_ringbuf *frq, const nghttp3_mem *mem) {
  nghttp3_frame *fr;
  size_t i, len = nghttp3_ringbuf_len(frq);

  for (i = 0; i < len; ++i) {
    fr = nghttp3_ringbuf_get(frq, i);
    switch (fr->hd.type) {
    case NGHTTP3_FRAME_HEADERS:
      nghttp3_frame_headers_free(&fr->headers, mem);
      break;
    case NGHTTP3_FRAME_PRIORITY_UPDATE:
      nghttp3_frame_priority_update_free(&fr->priority_update, mem);
      break;
    default:
      break;
    }
  }

  nghttp3_ringbuf_free(frq);
}

void nghttp3_stream_del(nghttp3_stream *stream) {
  if (stream == NULL) {
    return;
  }

  nghttp3_qpack_stream_context_free(&stream->qpack_sctx);
  delete_chunks(&stream->inq, stream->mem);
  delete_outq(&stream->outq, stream->mem);
  delete_out_chunks(&stream->chunks, stream->out_chunk_objalloc, stream->mem);
  delete_frq(&stream->frq, stream->mem);
  nghttp3_tnode_free(&stream->node);

  nghttp3_objalloc_stream_release(stream->stream_objalloc, stream);
}

void nghttp3_varint_read_state_reset(nghttp3_varint_read_state *rvint) {
  memset(rvint, 0, sizeof(*rvint));
}

void nghttp3_stream_read_state_reset(nghttp3_stream_read_state *rstate) {
  memset(rstate, 0, sizeof(*rstate));
}

nghttp3_ssize nghttp3_read_varint(nghttp3_varint_read_state *rvint,
                                  const uint8_t *begin, const uint8_t *end,
                                  int fin) {
  size_t len, vlen;
  uint8_t *p;

  assert(begin != end);

  if (rvint->left == 0) {
    assert(rvint->acc == 0);

    vlen = nghttp3_get_varintlen(begin);
    len = nghttp3_min_size(vlen, (size_t)(end - begin));
    if (vlen <= len) {
      nghttp3_get_varint(&rvint->acc, begin);
      return (nghttp3_ssize)vlen;
    }

    if (fin) {
      return NGHTTP3_ERR_INVALID_ARGUMENT;
    }

    p = (uint8_t *)&rvint->acc + (sizeof(rvint->acc) - vlen);
    memcpy(p, begin, len);
    *p &= 0x3f;
    rvint->left = vlen - len;

    return (nghttp3_ssize)len;
  }

  len = nghttp3_min_size(rvint->left, (size_t)(end - begin));
  p = (uint8_t *)&rvint->acc + (sizeof(rvint->acc) - rvint->left);
  memcpy(p, begin, len);
  rvint->left -= len;

  if (rvint->left == 0) {
    rvint->acc = (int64_t)nghttp3_ntohl64((uint64_t)rvint->acc);
  } else if (fin) {
    return NGHTTP3_ERR_INVALID_ARGUMENT;
  }

  return (nghttp3_ssize)len;
}

int nghttp3_stream_frq_add(nghttp3_stream *stream, const nghttp3_frame *fr) {
  nghttp3_ringbuf *frq = &stream->frq;
  nghttp3_frame *dest;
  int rv;

  if (nghttp3_ringbuf_full(frq)) {
    size_t nlen =
      nghttp3_max_size(NGHTTP3_MIN_RBLEN, nghttp3_ringbuf_len(frq) * 2);
    rv = nghttp3_ringbuf_reserve(frq, nlen);
    if (rv != 0) {
      return rv;
    }
  }

  dest = nghttp3_ringbuf_push_back(frq);
  *dest = *fr;

  return 0;
}

int nghttp3_stream_fill_outq(nghttp3_stream *stream) {
  nghttp3_ringbuf *frq = &stream->frq;
  nghttp3_frame *fr;
  int data_eof;
  int rv;

  for (; nghttp3_ringbuf_len(frq) &&
         stream->unsent_bytes < NGHTTP3_MIN_UNSENT_BYTES;) {
    fr = nghttp3_ringbuf_get(frq, 0);

    switch (fr->hd.type) {
    case NGHTTP3_FRAME_SETTINGS:
      rv = nghttp3_stream_write_settings(stream, &fr->settings);
      if (rv != 0) {
        return rv;
      }
      break;
    case NGHTTP3_FRAME_HEADERS:
      rv = nghttp3_stream_write_headers(stream, &fr->headers);
      if (rv != 0) {
        return rv;
      }
      nghttp3_frame_headers_free(&fr->headers, stream->mem);
      break;
    case NGHTTP3_FRAME_DATA:
      rv = nghttp3_stream_write_data(stream, &data_eof, &fr->data);
      if (rv != 0) {
        return rv;
      }
      if (stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED) {
        return 0;
      }
      if (!data_eof) {
        return 0;
      }
      break;
    case NGHTTP3_FRAME_GOAWAY:
      rv = nghttp3_stream_write_goaway(stream, &fr->goaway);
      if (rv != 0) {
        return rv;
      }
      break;
    case NGHTTP3_FRAME_PRIORITY_UPDATE:
      rv = nghttp3_stream_write_priority_update(stream, &fr->priority_update);
      if (rv != 0) {
        return rv;
      }
      nghttp3_frame_priority_update_free(&fr->priority_update, stream->mem);
      break;
    case NGHTTP3_FRAME_ORIGIN:
      rv = nghttp3_stream_write_origin(stream, &fr->origin);
      if (rv != 0) {
        return rv;
      }

      break;
    default:
      /* TODO Not implemented */
      break;
    }

    nghttp3_ringbuf_pop_front(frq);
  }

  return 0;
}

int nghttp3_stream_write_stream_type(nghttp3_stream *stream) {
  size_t len = nghttp3_put_varintlen((int64_t)stream->type);
  nghttp3_buf *chunk;
  nghttp3_typed_buf tbuf;
  int rv;

  rv = nghttp3_stream_ensure_chunk(stream, len);
  if (rv != 0) {
    return rv;
  }

  chunk = nghttp3_stream_get_chunk(stream);
  nghttp3_typed_buf_shared_init(&tbuf, chunk);

  chunk->last = nghttp3_put_varint(chunk->last, (int64_t)stream->type);
  tbuf.buf.last = chunk->last;

  return nghttp3_stream_outq_add(stream, &tbuf);
}

int nghttp3_stream_write_settings(nghttp3_stream *stream,
                                  const nghttp3_frame_settings *infr) {
  size_t len;
  int rv;
  nghttp3_buf *chunk;
  nghttp3_typed_buf tbuf;
  nghttp3_settings_entry ents[16];
  nghttp3_frame_settings fr = {
    .type = NGHTTP3_FRAME_SETTINGS,
    .niv = 3,
    .iv = ents,
  };
  const nghttp3_settings *local_settings = infr->local_settings;
  int64_t payloadlen;

  ents[0] = (nghttp3_settings_entry){
    .id = NGHTTP3_SETTINGS_ID_MAX_FIELD_SECTION_SIZE,
    .value = local_settings->max_field_section_size,
  };
  ents[1] = (nghttp3_settings_entry){
    .id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY,
    .value = local_settings->qpack_max_dtable_capacity,
  };
  ents[2] = (nghttp3_settings_entry){
    .id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS,
    .value = local_settings->qpack_blocked_streams,
  };

  if (local_settings->h3_datagram) {
    ents[fr.niv] = (nghttp3_settings_entry){
      .id = NGHTTP3_SETTINGS_ID_H3_DATAGRAM,
      .value = 1,
    };

    ++fr.niv;
  }

  if (local_settings->enable_connect_protocol) {
    ents[fr.niv] = (nghttp3_settings_entry){
      .id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL,
      .value = 1,
    };

    ++fr.niv;
  }

  len = nghttp3_frame_write_settings_len(&payloadlen, &fr);

  rv = nghttp3_stream_ensure_chunk(stream, len);
  if (rv != 0) {
    return rv;
  }

  chunk = nghttp3_stream_get_chunk(stream);
  nghttp3_typed_buf_shared_init(&tbuf, chunk);

  chunk->last = nghttp3_frame_write_settings(chunk->last, &fr, payloadlen);

  tbuf.buf.last = chunk->last;

  return nghttp3_stream_outq_add(stream, &tbuf);
}

int nghttp3_stream_write_goaway(nghttp3_stream *stream,
                                const nghttp3_frame_goaway *fr) {
  size_t len;
  int rv;
  nghttp3_buf *chunk;
  nghttp3_typed_buf tbuf;
  int64_t payloadlen;

  len = nghttp3_frame_write_goaway_len(&payloadlen, fr);

  rv = nghttp3_stream_ensure_chunk(stream, len);
  if (rv != 0) {
    return rv;
  }

  chunk = nghttp3_stream_get_chunk(stream);
  nghttp3_typed_buf_shared_init(&tbuf, chunk);

  chunk->last = nghttp3_frame_write_goaway(chunk->last, fr, payloadlen);

  tbuf.buf.last = chunk->last;

  return nghttp3_stream_outq_add(stream, &tbuf);
}

int nghttp3_stream_write_priority_update(
  nghttp3_stream *stream, const nghttp3_frame_priority_update *fr) {
  size_t len;
  int rv;
  nghttp3_buf *chunk;
  nghttp3_typed_buf tbuf;
  int64_t payloadlen;

  len = nghttp3_frame_write_priority_update_len(&payloadlen, fr);

  rv = nghttp3_stream_ensure_chunk(stream, len);
  if (rv != 0) {
    return rv;
  }

  chunk = nghttp3_stream_get_chunk(stream);
  nghttp3_typed_buf_shared_init(&tbuf, chunk);

  chunk->last =
    nghttp3_frame_write_priority_update(chunk->last, fr, payloadlen);

  tbuf.buf.last = chunk->last;

  return nghttp3_stream_outq_add(stream, &tbuf);
}

int nghttp3_stream_write_origin(nghttp3_stream *stream,
                                const nghttp3_frame_origin *fr) {
  nghttp3_buf *chunk;
  nghttp3_buf buf;
  nghttp3_typed_buf tbuf;
  int rv;

  rv = nghttp3_stream_ensure_chunk(
    stream, nghttp3_frame_write_hd_len(fr->type, (int64_t)fr->origin_list.len));
  if (rv != 0) {
    return rv;
  }

  chunk = nghttp3_stream_get_chunk(stream);
  nghttp3_typed_buf_shared_init(&tbuf, chunk);

  chunk->last =
    nghttp3_frame_write_hd(chunk->last, fr->type, (int64_t)fr->origin_list.len);

  tbuf.buf.last = chunk->last;

  rv = nghttp3_stream_outq_add(stream, &tbuf);
  if (rv != 0) {
    return rv;
  }

  if (fr->origin_list.len == 0) {
    return 0;
  }

  nghttp3_buf_wrap_init(&buf, (uint8_t *)fr->origin_list.base,
                        fr->origin_list.len);
  buf.last = buf.end;
  nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_ALIEN_NO_ACK);

  return nghttp3_stream_outq_add(stream, &tbuf);
}

int nghttp3_stream_write_headers(nghttp3_stream *stream,
                                 const nghttp3_frame_headers *fr) {
  nghttp3_conn *conn = stream->conn;

  assert(conn);

  return nghttp3_stream_write_header_block(
    stream, &conn->qenc, conn->tx.qenc, &conn->tx.qpack.rbuf,
    &conn->tx.qpack.ebuf, NGHTTP3_FRAME_HEADERS, fr->nva, fr->nvlen);
}

int nghttp3_stream_write_header_block(nghttp3_stream *stream,
                                      nghttp3_qpack_encoder *qenc,
                                      nghttp3_stream *qenc_stream,
                                      nghttp3_buf *rbuf, nghttp3_buf *ebuf,
                                      int64_t frame_type, const nghttp3_nv *nva,
                                      size_t nvlen) {
  nghttp3_buf pbuf;
  int rv;
  size_t len;
  nghttp3_buf *chunk;
  nghttp3_typed_buf tbuf;
  uint8_t raw_pbuf[16];
  size_t pbuflen, rbuflen, ebuflen;
  int64_t payloadlen;

  nghttp3_buf_wrap_init(&pbuf, raw_pbuf, sizeof(raw_pbuf));

  rv = nghttp3_qpack_encoder_encode(qenc, &pbuf, rbuf, ebuf, stream->node.id,
                                    nva, nvlen);
  if (rv != 0) {
    return rv;
  }

  pbuflen = nghttp3_buf_len(&pbuf);
  rbuflen = nghttp3_buf_len(rbuf);
  ebuflen = nghttp3_buf_len(ebuf);

  payloadlen = (int64_t)(pbuflen + rbuflen);

  len = nghttp3_frame_write_hd_len(frame_type, payloadlen) + pbuflen;

  if (rbuflen <= NGHTTP3_STREAM_MAX_COPY_THRES) {
    len += rbuflen;
  }

  rv = nghttp3_stream_ensure_chunk(stream, len);
  if (rv != 0) {
    return rv;
  }

  chunk = nghttp3_stream_get_chunk(stream);
  nghttp3_typed_buf_shared_init(&tbuf, chunk);

  chunk->last = nghttp3_frame_write_hd(chunk->last, frame_type, payloadlen);

  chunk->last = nghttp3_cpymem(chunk->last, pbuf.pos, pbuflen);
  nghttp3_buf_init(&pbuf);

  if (rbuflen > NGHTTP3_STREAM_MAX_COPY_THRES) {
    tbuf.buf.last = chunk->last;

    rv = nghttp3_stream_outq_add(stream, &tbuf);
    if (rv != 0) {
      return rv;
    }

    nghttp3_typed_buf_init(&tbuf, rbuf, NGHTTP3_BUF_TYPE_PRIVATE);
    rv = nghttp3_stream_outq_add(stream, &tbuf);
    if (rv != 0) {
      return rv;
    }
    nghttp3_buf_init(rbuf);
  } else if (rbuflen) {
    chunk->last = nghttp3_cpymem(chunk->last, rbuf->pos, rbuflen);
    tbuf.buf.last = chunk->last;

    rv = nghttp3_stream_outq_add(stream, &tbuf);
    if (rv != 0) {
      return rv;
    }
    nghttp3_buf_reset(rbuf);
  }

  if (ebuflen > NGHTTP3_STREAM_MAX_COPY_THRES) {
    assert(qenc_stream);

    nghttp3_typed_buf_init(&tbuf, ebuf, NGHTTP3_BUF_TYPE_PRIVATE);
    rv = nghttp3_stream_outq_add(qenc_stream, &tbuf);
    if (rv != 0) {
      return rv;
    }
    nghttp3_buf_init(ebuf);
  } else if (ebuflen) {
    assert(qenc_stream);

    rv = nghttp3_stream_ensure_chunk(qenc_stream, ebuflen);
    if (rv != 0) {
      return rv;
    }

    chunk = nghttp3_stream_get_chunk(qenc_stream);
    nghttp3_typed_buf_shared_init(&tbuf, chunk);

    chunk->last = nghttp3_cpymem(chunk->last, ebuf->pos, ebuflen);
    tbuf.buf.last = chunk->last;

    rv = nghttp3_stream_outq_add(qenc_stream, &tbuf);
    if (rv != 0) {
      return rv;
    }
    nghttp3_buf_reset(ebuf);
  }

  assert(0 == nghttp3_buf_len(&pbuf));
  assert(0 == nghttp3_buf_len(rbuf));
  assert(0 == nghttp3_buf_len(ebuf));

  return 0;
}

int nghttp3_stream_write_data(nghttp3_stream *stream, int *peof,
                              const nghttp3_frame_data *fr) {
  int rv;
  size_t len;
  nghttp3_typed_buf tbuf;
  nghttp3_buf buf;
  nghttp3_buf *chunk;
  nghttp3_read_data_callback read_data = fr->dr.read_data;
  nghttp3_conn *conn = stream->conn;
  int64_t datalen;
  uint32_t flags = 0;
  nghttp3_vec vec[8];
  nghttp3_vec *v;
  nghttp3_ssize sveccnt;
  size_t i;

  assert(!(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED));
  assert(read_data);
  assert(conn);

  *peof = 0;

  sveccnt = read_data(conn, stream->node.id, vec, nghttp3_arraylen(vec), &flags,
                      conn->user_data, stream->user_data);
  if (sveccnt < 0) {
    if (sveccnt == NGHTTP3_ERR_WOULDBLOCK) {
      stream->flags |= NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED;
      return 0;
    }
    return NGHTTP3_ERR_CALLBACK_FAILURE;
  }

  datalen = nghttp3_vec_len_varint(vec, (size_t)sveccnt);
  if (datalen == -1) {
    return NGHTTP3_ERR_STREAM_DATA_OVERFLOW;
  }

  assert(datalen || flags & NGHTTP3_DATA_FLAG_EOF);

  if (flags & NGHTTP3_DATA_FLAG_EOF) {
    *peof = 1;
    if (!(flags & NGHTTP3_DATA_FLAG_NO_END_STREAM)) {
      stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM;
      if (datalen == 0) {
        if (nghttp3_stream_outq_write_done(stream)) {
          /* If this is the last data and its is 0 length, we don't
             need send DATA frame.  We rely on the non-emptiness of
             outq to schedule stream, so add empty tbuf to outq to
             just send fin. */
          nghttp3_buf_init(&buf);
          nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_PRIVATE);
          return nghttp3_stream_outq_add(stream, &tbuf);
        }
        return 0;
      }
    }

    if (datalen == 0) {
      /* We are going to send more frames, but no DATA frame this
         time. */
      return 0;
    }
  }

  len = nghttp3_frame_write_hd_len(NGHTTP3_FRAME_DATA, datalen);

  rv = nghttp3_stream_ensure_chunk(stream, len);
  if (rv != 0) {
    return rv;
  }

  chunk = nghttp3_stream_get_chunk(stream);
  nghttp3_typed_buf_shared_init(&tbuf, chunk);

  chunk->last =
    nghttp3_frame_write_hd(chunk->last, NGHTTP3_FRAME_DATA, datalen);

  tbuf.buf.last = chunk->last;

  rv = nghttp3_stream_outq_add(stream, &tbuf);
  if (rv != 0) {
    return rv;
  }

  assert(datalen);

  for (i = 0; i < (size_t)sveccnt; ++i) {
    v = &vec[i];
    if (v->len == 0) {
      continue;
    }
    nghttp3_buf_wrap_init(&buf, v->base, v->len);
    buf.last = buf.end;
    nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_ALIEN);
    rv = nghttp3_stream_outq_add(stream, &tbuf);
    if (rv != 0) {
      return rv;
    }
  }

  return 0;
}

int nghttp3_stream_write_qpack_decoder_stream(nghttp3_stream *stream) {
  nghttp3_qpack_decoder *qdec;
  nghttp3_buf *chunk;
  int rv;
  nghttp3_typed_buf tbuf;
  size_t len;

  assert(stream->conn);
  assert(stream->conn->tx.qdec == stream);

  qdec = &stream->conn->qdec;

  assert(qdec);

  len = nghttp3_qpack_decoder_get_decoder_streamlen(qdec);
  if (len == 0) {
    return 0;
  }

  rv = nghttp3_stream_ensure_chunk(stream, len);
  if (rv != 0) {
    return rv;
  }

  chunk = nghttp3_stream_get_chunk(stream);
  nghttp3_typed_buf_shared_init(&tbuf, chunk);

  nghttp3_qpack_decoder_write_decoder(qdec, chunk);

  tbuf.buf.last = chunk->last;

  return nghttp3_stream_outq_add(stream, &tbuf);
}

int nghttp3_stream_outq_add(nghttp3_stream *stream,
                            const nghttp3_typed_buf *tbuf) {
  nghttp3_ringbuf *outq = &stream->outq;
  int rv;
  nghttp3_typed_buf *dest;
  size_t len = nghttp3_ringbuf_len(outq);
  size_t buflen = nghttp3_buf_len(&tbuf->buf);

  if (buflen > NGHTTP3_MAX_VARINT - stream->tx.offset) {
    return NGHTTP3_ERR_STREAM_DATA_OVERFLOW;
  }

  stream->tx.offset += buflen;
  stream->unsent_bytes += buflen;

  if (len) {
    dest = nghttp3_ringbuf_get(outq, len - 1);
    if (dest->type == tbuf->type && dest->type == NGHTTP3_BUF_TYPE_SHARED &&
        dest->buf.end == tbuf->buf.end && dest->buf.last == tbuf->buf.pos) {
      /* If we have already written last entry, adjust outq_idx and
         offset so that this entry is eligible to send. */
      if (len == stream->outq_idx) {
        --stream->outq_idx;
      }

      dest->buf.last = tbuf->buf.last;

      assert(dest->buf.end == tbuf->buf.end);

      return 0;
    }
  }

  if (nghttp3_ringbuf_full(outq)) {
    size_t nlen = nghttp3_max_size(NGHTTP3_MIN_RBLEN, len * 2);
    rv = nghttp3_ringbuf_reserve(outq, nlen);
    if (rv != 0) {
      return rv;
    }
  }

  dest = nghttp3_ringbuf_push_back(outq);
  *dest = *tbuf;

  return 0;
}

int nghttp3_stream_ensure_chunk(nghttp3_stream *stream, size_t need) {
  nghttp3_ringbuf *chunks = &stream->chunks;
  nghttp3_buf *chunk;
  size_t len = nghttp3_ringbuf_len(chunks);
  uint8_t *p;
  int rv;
  size_t n = NGHTTP3_STREAM_MIN_CHUNK_SIZE;

  if (len) {
    chunk = nghttp3_ringbuf_get(chunks, len - 1);
    if (nghttp3_buf_left(chunk) >= need) {
      return 0;
    }
  }

  for (; n < need; n *= 2)
    ;

  if (n == NGHTTP3_STREAM_MIN_CHUNK_SIZE) {
    p =
      (uint8_t *)nghttp3_objalloc_chunk_len_get(stream->out_chunk_objalloc, n);
  } else {
    p = nghttp3_mem_malloc(stream->mem, n);
  }
  if (p == NULL) {
    return NGHTTP3_ERR_NOMEM;
  }

  if (nghttp3_ringbuf_full(chunks)) {
    size_t nlen = nghttp3_max_size(NGHTTP3_MIN_RBLEN, len * 2);
    rv = nghttp3_ringbuf_reserve(chunks, nlen);
    if (rv != 0) {
      return rv;
    }
  }

  chunk = nghttp3_ringbuf_push_back(chunks);
  nghttp3_buf_wrap_init(chunk, p, n);

  return 0;
}

nghttp3_buf *nghttp3_stream_get_chunk(nghttp3_stream *stream) {
  nghttp3_ringbuf *chunks = &stream->chunks;
  size_t len = nghttp3_ringbuf_len(chunks);

  assert(len);

  return nghttp3_ringbuf_get(chunks, len - 1);
}

int nghttp3_stream_is_blocked(nghttp3_stream *stream) {
  return (stream->flags & NGHTTP3_STREAM_FLAG_FC_BLOCKED) ||
         (stream->flags & NGHTTP3_STREAM_FLAG_SHUT_WR) ||
         (stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED);
}

int nghttp3_stream_require_schedule(nghttp3_stream *stream) {
  return (!nghttp3_stream_outq_write_done(stream) &&
          !(stream->flags & NGHTTP3_STREAM_FLAG_FC_BLOCKED) &&
          !(stream->flags & NGHTTP3_STREAM_FLAG_SHUT_WR)) ||
         (nghttp3_ringbuf_len(&stream->frq) &&
          !(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED));
}

size_t nghttp3_stream_writev(nghttp3_stream *stream, int *pfin,
                             nghttp3_vec *vec, size_t veccnt) {
  nghttp3_ringbuf *outq = &stream->outq;
  size_t len = nghttp3_ringbuf_len(outq);
  size_t i = stream->outq_idx;
  size_t buflen;
  nghttp3_vec *vbegin = vec, *vend = vec + veccnt;
  nghttp3_typed_buf *tbuf;

  assert(veccnt > 0);

  for (; i < len && vec != vend; ++i) {
    tbuf = nghttp3_ringbuf_get(outq, i);
    buflen = nghttp3_buf_len(&tbuf->buf);

    if (buflen == 0) {
      continue;
    }

    vec->base = tbuf->buf.pos;
    vec->len = buflen;

    ++vec;
  }

  /* TODO Rework this if we have finished implementing HTTP
     messaging */
  *pfin = nghttp3_ringbuf_len(&stream->frq) == 0 && i == len &&
          (stream->flags & NGHTTP3_STREAM_FLAG_WRITE_END_STREAM);

  return (size_t)(vec - vbegin);
}

void nghttp3_stream_add_outq_offset(nghttp3_stream *stream, size_t n) {
  nghttp3_ringbuf *outq = &stream->outq;
  size_t i;
  size_t len = nghttp3_ringbuf_len(outq);
  size_t buflen;
  nghttp3_typed_buf *tbuf;

  stream->unsent_bytes -= n;

  for (i = stream->outq_idx; i < len; ++i) {
    tbuf = nghttp3_ringbuf_get(outq, i);
    buflen = nghttp3_buf_len(&tbuf->buf);
    if (n < buflen) {
      tbuf->buf.pos += n;

      break;
    }

    tbuf->buf.pos = tbuf->buf.last;
    n -= buflen;
  }

  assert(i < len || n == 0);

  stream->outq_idx = i;
}

int nghttp3_stream_outq_write_done(nghttp3_stream *stream) {
  nghttp3_ringbuf *outq = &stream->outq;
  size_t len = nghttp3_ringbuf_len(outq);

  return len == 0 || stream->outq_idx >= len;
}

static void stream_pop_outq_entry(nghttp3_stream *stream,
                                  nghttp3_typed_buf *tbuf) {
  nghttp3_ringbuf *chunks = &stream->chunks;
  nghttp3_buf *chunk;

  switch (tbuf->type) {
  case NGHTTP3_BUF_TYPE_PRIVATE:
    nghttp3_buf_free(&tbuf->buf, stream->mem);
    break;
  case NGHTTP3_BUF_TYPE_ALIEN:
  case NGHTTP3_BUF_TYPE_ALIEN_NO_ACK:
    break;
  case NGHTTP3_BUF_TYPE_SHARED:
    assert(nghttp3_ringbuf_len(chunks));

    chunk = nghttp3_ringbuf_get(chunks, 0);

    assert(chunk->end == tbuf->buf.end);

    if (chunk->last == tbuf->buf.last) {
      if (nghttp3_buf_cap(chunk) == NGHTTP3_STREAM_MIN_CHUNK_SIZE) {
        nghttp3_objalloc_chunk_release(stream->out_chunk_objalloc,
                                       (void *)chunk->begin);
      } else {
        nghttp3_buf_free(chunk, stream->mem);
      }
      nghttp3_ringbuf_pop_front(chunks);
    }
    break;
  default:
    nghttp3_unreachable();
  }

  nghttp3_ringbuf_pop_front(&stream->outq);
}

int nghttp3_stream_update_ack_offset(nghttp3_stream *stream, uint64_t offset) {
  nghttp3_ringbuf *outq = &stream->outq;
  size_t buflen;
  uint64_t nack;
  nghttp3_typed_buf *tbuf;
  int rv;

  for (; nghttp3_ringbuf_len(outq);) {
    tbuf = nghttp3_ringbuf_get(outq, 0);
    buflen = (size_t)(tbuf->buf.last - tbuf->buf.begin);

    /* For NGHTTP3_BUF_TYPE_ALIEN, we never add 0 length buffer. */
    if (tbuf->type == NGHTTP3_BUF_TYPE_ALIEN && stream->ack_offset < offset &&
        stream->callbacks.acked_data) {
      nack = nghttp3_min_uint64(offset, stream->ack_base + buflen) -
             stream->ack_offset;

      rv = stream->callbacks.acked_data(stream, stream->node.id, nack,
                                        stream->user_data);
      if (rv != 0) {
        return NGHTTP3_ERR_CALLBACK_FAILURE;
      }
    }

    if (stream->outq_idx > 0 && offset >= stream->ack_base + buflen) {
      stream_pop_outq_entry(stream, tbuf);

      stream->ack_base += buflen;
      stream->ack_offset = stream->ack_base;

      --stream->outq_idx;

      continue;
    }

    break;
  }

  stream->ack_offset = offset;

  return 0;
}

int nghttp3_stream_buffer_data(nghttp3_stream *stream, const uint8_t *data,
                               size_t datalen) {
  nghttp3_ringbuf *inq = &stream->inq;
  size_t len = nghttp3_ringbuf_len(inq);
  nghttp3_buf *buf;
  size_t nwrite;
  uint8_t *rawbuf;
  size_t bufleft;
  int rv;

  if (len) {
    buf = nghttp3_ringbuf_get(inq, len - 1);
    bufleft = nghttp3_buf_left(buf);
    nwrite = nghttp3_min_size(datalen, bufleft);
    buf->last = nghttp3_cpymem(buf->last, data, nwrite);
    data += nwrite;
    datalen -= nwrite;
  }

  for (; datalen;) {
    if (nghttp3_ringbuf_full(inq)) {
      size_t nlen =
        nghttp3_max_size(NGHTTP3_MIN_RBLEN, nghttp3_ringbuf_len(inq) * 2);
      rv = nghttp3_ringbuf_reserve(inq, nlen);
      if (rv != 0) {
        return rv;
      }
    }

    rawbuf = nghttp3_mem_malloc(stream->mem, 16384);
    if (rawbuf == NULL) {
      return NGHTTP3_ERR_NOMEM;
    }

    buf = nghttp3_ringbuf_push_back(inq);
    nghttp3_buf_wrap_init(buf, rawbuf, 16384);
    bufleft = nghttp3_buf_left(buf);
    nwrite = nghttp3_min_size(datalen, bufleft);
    buf->last = nghttp3_cpymem(buf->last, data, nwrite);
    data += nwrite;
    datalen -= nwrite;
  }

  return 0;
}

size_t nghttp3_stream_get_buffered_datalen(nghttp3_stream *stream) {
  nghttp3_ringbuf *inq = &stream->inq;
  size_t len = nghttp3_ringbuf_len(inq);
  size_t i, n = 0;
  nghttp3_buf *buf;

  for (i = 0; i < len; ++i) {
    buf = nghttp3_ringbuf_get(inq, i);
    n += nghttp3_buf_len(buf);
  }

  return n;
}

int nghttp3_stream_transit_rx_http_state(nghttp3_stream *stream,
                                         nghttp3_stream_http_event event) {
  int rv;

  switch (stream->rx.hstate) {
  case NGHTTP3_HTTP_STATE_NONE:
    nghttp3_unreachable();
  case NGHTTP3_HTTP_STATE_REQ_INITIAL:
    if (event != NGHTTP3_HTTP_EVENT_HEADERS_BEGIN) {
      return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
    }

    stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN;

    return 0;
  case NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN:
    assert(NGHTTP3_HTTP_EVENT_HEADERS_END == event);
    stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_HEADERS_END;
    return 0;
  case NGHTTP3_HTTP_STATE_REQ_HEADERS_END:
    switch (event) {
    case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
      /* TODO Better to check status code */
      if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) {
        return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN;
      return 0;
    case NGHTTP3_HTTP_EVENT_DATA_BEGIN:
      stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN;
      return 0;
    case NGHTTP3_HTTP_EVENT_MSG_END:
      rv = nghttp3_http_on_remote_end_stream(stream);
      if (rv != 0) {
        return rv;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END;
      return 0;
    default:
      nghttp3_unreachable();
    }
  case NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN:
    assert(NGHTTP3_HTTP_EVENT_DATA_END == event);
    stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_END;
    return 0;
  case NGHTTP3_HTTP_STATE_REQ_DATA_END:
    switch (event) {
    case NGHTTP3_HTTP_EVENT_DATA_BEGIN:
      stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN;
      return 0;
    case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
      /* TODO Better to check status code */
      if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) {
        return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN;
      return 0;
    case NGHTTP3_HTTP_EVENT_MSG_END:
      rv = nghttp3_http_on_remote_end_stream(stream);
      if (rv != 0) {
        return rv;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END;
      return 0;
    default:
      nghttp3_unreachable();
    }
  case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN:
    assert(NGHTTP3_HTTP_EVENT_HEADERS_END == event);
    stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_END;
    return 0;
  case NGHTTP3_HTTP_STATE_REQ_TRAILERS_END:
    if (event != NGHTTP3_HTTP_EVENT_MSG_END) {
      /* TODO Should ignore unexpected frame in this state as per
         spec. */
      return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
    }
    rv = nghttp3_http_on_remote_end_stream(stream);
    if (rv != 0) {
      return rv;
    }
    stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END;
    return 0;
  case NGHTTP3_HTTP_STATE_REQ_END:
    return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
  case NGHTTP3_HTTP_STATE_RESP_INITIAL:
    if (event != NGHTTP3_HTTP_EVENT_HEADERS_BEGIN) {
      return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
    }
    stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN;
    return 0;
  case NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN:
    assert(NGHTTP3_HTTP_EVENT_HEADERS_END == event);
    stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_END;
    return 0;
  case NGHTTP3_HTTP_STATE_RESP_HEADERS_END:
    switch (event) {
    case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
      if (stream->rx.http.status_code == -1) {
        stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN;
        return 0;
      }
      if ((stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) &&
          stream->rx.http.status_code / 100 == 2) {
        return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN;
      return 0;
    case NGHTTP3_HTTP_EVENT_DATA_BEGIN:
      if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_EXPECT_FINAL_RESPONSE) {
        return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN;
      return 0;
    case NGHTTP3_HTTP_EVENT_MSG_END:
      rv = nghttp3_http_on_remote_end_stream(stream);
      if (rv != 0) {
        return rv;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END;
      return 0;
    default:
      nghttp3_unreachable();
    }
  case NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN:
    assert(NGHTTP3_HTTP_EVENT_DATA_END == event);
    stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_END;
    return 0;
  case NGHTTP3_HTTP_STATE_RESP_DATA_END:
    switch (event) {
    case NGHTTP3_HTTP_EVENT_DATA_BEGIN:
      stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN;
      return 0;
    case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
      if ((stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) &&
          stream->rx.http.status_code / 100 == 2) {
        return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN;
      return 0;
    case NGHTTP3_HTTP_EVENT_MSG_END:
      rv = nghttp3_http_on_remote_end_stream(stream);
      if (rv != 0) {
        return rv;
      }
      stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END;
      return 0;
    default:
      nghttp3_unreachable();
    }
  case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN:
    assert(NGHTTP3_HTTP_EVENT_HEADERS_END == event);
    stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_END;
    return 0;
  case NGHTTP3_HTTP_STATE_RESP_TRAILERS_END:
    if (event != NGHTTP3_HTTP_EVENT_MSG_END) {
      return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
    }
    rv = nghttp3_http_on_remote_end_stream(stream);
    if (rv != 0) {
      return rv;
    }
    stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END;
    return 0;
  case NGHTTP3_HTTP_STATE_RESP_END:
    return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
  default:
    nghttp3_unreachable();
  }
}

int nghttp3_stream_empty_headers_allowed(nghttp3_stream *stream) {
  switch (stream->rx.hstate) {
  case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN:
  case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN:
    return 0;
  default:
    return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
  }
}

int nghttp3_stream_uni(int64_t stream_id) { return (stream_id & 0x2) != 0; }

int nghttp3_client_stream_bidi(int64_t stream_id) {
  return (stream_id & 0x3) == 0;
}

int nghttp3_client_stream_uni(int64_t stream_id) {
  return (stream_id & 0x3) == 0x2;
}

int nghttp3_server_stream_uni(int64_t stream_id) {
  return (stream_id & 0x3) == 0x3;
}
