/*
 * Copyright (C) 2015-2018 Alibaba Group Holding Limited
 */


#include "nghttp2_buf.h"

#include <stdio.h>

#include "nghttp2_helper.h"
#include "nghttp2_debug.h"

void nghttp2_buf_init(nghttp2_buf *buf) {
  buf->begin = NULL;
  buf->end = NULL;
  buf->pos = NULL;
  buf->last = NULL;
  buf->mark = NULL;
}

int nghttp2_buf_init2(nghttp2_buf *buf, size_t initial, nghttp2_mem *mem) {
  nghttp2_buf_init(buf);
  return nghttp2_buf_reserve(buf, initial, mem);
}

void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem) {
  if (buf == NULL) {
    return;
  }

  nghttp2_mem_free(mem, buf->begin);
  buf->begin = NULL;
}

int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem) {
  uint8_t *ptr;
  size_t cap;

  cap = nghttp2_buf_cap(buf);

  if (cap >= new_cap) {
    return 0;
  }

  new_cap = nghttp2_max(new_cap, cap * 2);

  ptr = nghttp2_mem_realloc(mem, buf->begin, new_cap);
  if (ptr == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }

  buf->pos = ptr + (buf->pos - buf->begin);
  buf->last = ptr + (buf->last - buf->begin);
  buf->mark = ptr + (buf->mark - buf->begin);
  buf->begin = ptr;
  buf->end = ptr + new_cap;

  return 0;
}

void nghttp2_buf_reset(nghttp2_buf *buf) {
  buf->pos = buf->last = buf->mark = buf->begin;
}

void nghttp2_buf_wrap_init(nghttp2_buf *buf, uint8_t *begin, size_t len) {
  buf->begin = buf->pos = buf->last = buf->mark = begin;
  buf->end = begin + len;
}

static int buf_chain_new(nghttp2_buf_chain **chain, size_t chunk_length,
                         nghttp2_mem *mem) {
  int rv;

  *chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain));
  if (*chain == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }

  (*chain)->next = NULL;

  rv = nghttp2_buf_init2(&(*chain)->buf, chunk_length, mem);
  if (rv != 0) {
    nghttp2_mem_free(mem, *chain);
    return NGHTTP2_ERR_NOMEM;
  }

  return 0;
}

static void buf_chain_del(nghttp2_buf_chain *chain, nghttp2_mem *mem) {
  nghttp2_buf_free(&chain->buf, mem);
  nghttp2_mem_free(mem, chain);
}

int nghttp2_bufs_init(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk,
                      nghttp2_mem *mem) {
  return nghttp2_bufs_init2(bufs, chunk_length, max_chunk, 0, mem);
}

int nghttp2_bufs_init2(nghttp2_bufs *bufs, size_t chunk_length,
                       size_t max_chunk, size_t offset, nghttp2_mem *mem) {
  return nghttp2_bufs_init3(bufs, chunk_length, max_chunk, max_chunk, offset,
                            mem);
}

int nghttp2_bufs_init3(nghttp2_bufs *bufs, size_t chunk_length,
                       size_t max_chunk, size_t chunk_keep, size_t offset,
                       nghttp2_mem *mem) {
  int rv;
  nghttp2_buf_chain *chain;

  if (chunk_keep == 0 || max_chunk < chunk_keep || chunk_length < offset) {
    return NGHTTP2_ERR_INVALID_ARGUMENT;
  }

  rv = buf_chain_new(&chain, chunk_length, mem);
  if (rv != 0) {
    return rv;
  }

  bufs->mem = mem;
  bufs->offset = offset;

  bufs->head = chain;
  bufs->cur = bufs->head;

  nghttp2_buf_shift_right(&bufs->cur->buf, offset);

  bufs->chunk_length = chunk_length;
  bufs->chunk_used = 1;
  bufs->max_chunk = max_chunk;
  bufs->chunk_keep = chunk_keep;

  return 0;
}

int nghttp2_bufs_realloc(nghttp2_bufs *bufs, size_t chunk_length) {
  int rv;
  nghttp2_buf_chain *chain;

  if (chunk_length < bufs->offset) {
    return NGHTTP2_ERR_INVALID_ARGUMENT;
  }

  rv = buf_chain_new(&chain, chunk_length, bufs->mem);
  if (rv != 0) {
    return rv;
  }

  nghttp2_bufs_free(bufs);

  bufs->head = chain;
  bufs->cur = bufs->head;

  nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset);

  bufs->chunk_length = chunk_length;
  bufs->chunk_used = 1;

  return 0;
}

void nghttp2_bufs_free(nghttp2_bufs *bufs) {
  nghttp2_buf_chain *chain, *next_chain;

  if (bufs == NULL) {
    return;
  }

  for (chain = bufs->head; chain;) {
    next_chain = chain->next;

    buf_chain_del(chain, bufs->mem);

    chain = next_chain;
  }

  bufs->head = NULL;
}

int nghttp2_bufs_wrap_init(nghttp2_bufs *bufs, uint8_t *begin, size_t len,
                           nghttp2_mem *mem) {
  nghttp2_buf_chain *chain;

  chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain));
  if (chain == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }

  chain->next = NULL;

  nghttp2_buf_wrap_init(&chain->buf, begin, len);

  bufs->mem = mem;
  bufs->offset = 0;

  bufs->head = chain;
  bufs->cur = bufs->head;

  bufs->chunk_length = len;
  bufs->chunk_used = 1;
  bufs->max_chunk = 1;
  bufs->chunk_keep = 1;

  return 0;
}

int nghttp2_bufs_wrap_init2(nghttp2_bufs *bufs, const nghttp2_vec *vec,
                            size_t veclen, nghttp2_mem *mem) {
  size_t i = 0;
  nghttp2_buf_chain *cur_chain;
  nghttp2_buf_chain *head_chain;
  nghttp2_buf_chain **dst_chain = &head_chain;

  if (veclen == 0) {
    return nghttp2_bufs_wrap_init(bufs, NULL, 0, mem);
  }

  head_chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain) * veclen);
  if (head_chain == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }

  for (i = 0; i < veclen; ++i) {
    cur_chain = &head_chain[i];
    cur_chain->next = NULL;
    nghttp2_buf_wrap_init(&cur_chain->buf, vec[i].base, vec[i].len);

    *dst_chain = cur_chain;
    dst_chain = &cur_chain->next;
  }

  bufs->mem = mem;
  bufs->offset = 0;

  bufs->head = head_chain;
  bufs->cur = bufs->head;

  /* We don't use chunk_length since no allocation is expected. */
  bufs->chunk_length = 0;
  bufs->chunk_used = veclen;
  bufs->max_chunk = veclen;
  bufs->chunk_keep = veclen;

  return 0;
}

void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs) {
  if (bufs == NULL) {
    return;
  }

  if (bufs->head) {
    nghttp2_mem_free(bufs->mem, bufs->head);
  }
}

void nghttp2_bufs_seek_last_present(nghttp2_bufs *bufs) {
  nghttp2_buf_chain *ci;

  for (ci = bufs->cur; ci; ci = ci->next) {
    if (nghttp2_buf_len(&ci->buf) == 0) {
      return;
    } else {
      bufs->cur = ci;
    }
  }
}

size_t nghttp2_bufs_len(nghttp2_bufs *bufs) {
  nghttp2_buf_chain *ci;
  size_t len;

  len = 0;
  for (ci = bufs->head; ci; ci = ci->next) {
    len += nghttp2_buf_len(&ci->buf);
  }

  return len;
}

static int bufs_alloc_chain(nghttp2_bufs *bufs) {
  int rv;
  nghttp2_buf_chain *chain;

  if (bufs->cur->next) {
    bufs->cur = bufs->cur->next;

    return 0;
  }

  if (bufs->max_chunk == bufs->chunk_used) {
    return NGHTTP2_ERR_BUFFER_ERROR;
  }

  rv = buf_chain_new(&chain, bufs->chunk_length, bufs->mem);
  if (rv != 0) {
    return rv;
  }

  DEBUGF("new buffer %zu bytes allocated for bufs %p, used %zu\n",
         bufs->chunk_length, bufs, bufs->chunk_used);

  ++bufs->chunk_used;

  bufs->cur->next = chain;
  bufs->cur = chain;

  nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset);

  return 0;
}

int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len) {
  int rv;
  size_t nwrite;
  nghttp2_buf *buf;
  const uint8_t *p;

  p = data;

  while (len) {
    buf = &bufs->cur->buf;

    nwrite = nghttp2_min(nghttp2_buf_avail(buf), len);
    if (nwrite == 0) {
      rv = bufs_alloc_chain(bufs);
      if (rv != 0) {
        return rv;
      }
      continue;
    }

    buf->last = nghttp2_cpymem(buf->last, p, nwrite);
    p += nwrite;
    len -= nwrite;
  }

  return 0;
}

static int bufs_ensure_addb(nghttp2_bufs *bufs) {
  int rv;
  nghttp2_buf *buf;

  buf = &bufs->cur->buf;

  if (nghttp2_buf_avail(buf) > 0) {
    return 0;
  }

  rv = bufs_alloc_chain(bufs);
  if (rv != 0) {
    return rv;
  }

  return 0;
}

int nghttp2_bufs_addb(nghttp2_bufs *bufs, uint8_t b) {
  int rv;

  rv = bufs_ensure_addb(bufs);
  if (rv != 0) {
    return rv;
  }

  *bufs->cur->buf.last++ = b;

  return 0;
}

int nghttp2_bufs_addb_hold(nghttp2_bufs *bufs, uint8_t b) {
  int rv;

  rv = bufs_ensure_addb(bufs);
  if (rv != 0) {
    return rv;
  }

  *bufs->cur->buf.last = b;

  return 0;
}

int nghttp2_bufs_orb(nghttp2_bufs *bufs, uint8_t b) {
  int rv;

  rv = bufs_ensure_addb(bufs);
  if (rv != 0) {
    return rv;
  }

  *bufs->cur->buf.last++ |= b;

  return 0;
}

int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b) {
  int rv;

  rv = bufs_ensure_addb(bufs);
  if (rv != 0) {
    return rv;
  }

  *bufs->cur->buf.last |= b;

  return 0;
}

ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out) {
  size_t len;
  nghttp2_buf_chain *chain;
  nghttp2_buf *buf;
  uint8_t *res;
  nghttp2_buf resbuf;

  len = 0;

  for (chain = bufs->head; chain; chain = chain->next) {
    len += nghttp2_buf_len(&chain->buf);
  }

  if (len == 0) {
    res = NULL;
    return 0;
  }

  res = nghttp2_mem_malloc(bufs->mem, len);
  if (res == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }

  nghttp2_buf_wrap_init(&resbuf, res, len);

  for (chain = bufs->head; chain; chain = chain->next) {
    buf = &chain->buf;
    resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf));
  }

  *out = res;

  return (ssize_t)len;
}

size_t nghttp2_bufs_remove_copy(nghttp2_bufs *bufs, uint8_t *out) {
  size_t len;
  nghttp2_buf_chain *chain;
  nghttp2_buf *buf;
  nghttp2_buf resbuf;

  len = nghttp2_bufs_len(bufs);

  nghttp2_buf_wrap_init(&resbuf, out, len);

  for (chain = bufs->head; chain; chain = chain->next) {
    buf = &chain->buf;
    resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf));
  }

  return len;
}

void nghttp2_bufs_reset(nghttp2_bufs *bufs) {
  nghttp2_buf_chain *chain, *ci;
  size_t k;

  k = bufs->chunk_keep;

  for (ci = bufs->head; ci; ci = ci->next) {
    nghttp2_buf_reset(&ci->buf);
    nghttp2_buf_shift_right(&ci->buf, bufs->offset);

    if (--k == 0) {
      break;
    }
  }

  if (ci) {
    chain = ci->next;
    ci->next = NULL;

    for (ci = chain; ci;) {
      chain = ci->next;

      buf_chain_del(ci, bufs->mem);

      ci = chain;
    }

    bufs->chunk_used = bufs->chunk_keep;
  }

  bufs->cur = bufs->head;
}

int nghttp2_bufs_advance(nghttp2_bufs *bufs) { return bufs_alloc_chain(bufs); }

int nghttp2_bufs_next_present(nghttp2_bufs *bufs) {
  nghttp2_buf_chain *chain;

  chain = bufs->cur->next;

  return chain && nghttp2_buf_len(&chain->buf);
}
