/*
 * "Copyright (c) 2008, 2009 The Regents of the University  of California.
 * All rights reserved."
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice, the following
 * two paragraphs and the author appear in all copies of this software.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
 *
 */

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

#include "tcplib.h"

struct circ_buf {
  uint8_t  *data_start;
  uint8_t  *data_head;
  uint16_t  data_len;
  uint32_t  head_seqno;
};

int circ_buf_init(void *data, int len, uint32_t seqno) {
  struct circ_buf *b = (struct circ_buf *)data;

  if (len < sizeof(struct circ_buf))
    return -1;

  b->data_head = b->data_start = (uint8_t *)(b + 1);
  b->data_len = len - sizeof(struct circ_buf);
  b->head_seqno = seqno;
  return 0;
}

uint32_t circ_get_seqno(void *buf) {
  struct circ_buf *b = (struct circ_buf *)buf;
  return b->head_seqno;
}

static void get_ptr_off_1(struct circ_buf *b, uint32_t sseqno, int len,
                          uint8_t **writeptr, int *w_len) {
  uint8_t *endptr =  b->data_start + b->data_len;
  int offset;

  *writeptr = NULL;
  *w_len = len;

  /* write up to either the end of the buffer */
  offset = sseqno - b->head_seqno;
  if (b->data_head + offset < endptr) {
    *writeptr = b->data_head + offset;
  } else {
    offset -= (endptr - b->data_head);
    *writeptr = b->data_start + offset;
  }
  if (*writeptr + *w_len > endptr) {
    *w_len = endptr - *writeptr;
  }
}

int circ_shorten_head(void *buf, uint32_t seqno) {
  struct circ_buf *b = (struct circ_buf *)buf;
  int offset = seqno - b->head_seqno;

  b->head_seqno = seqno;
  b->data_head += offset;

  while (b->data_head > b->data_start + b->data_len)
    b->data_head -= b->data_len;

  return 0;
}

int circ_buf_read(void *buf, uint32_t sseqno,
                  uint8_t *data, int len) {
  struct circ_buf *b = (struct circ_buf *)buf;
  uint8_t *readptr;
  int r_len, rc = 0;

  get_ptr_off_1(b, sseqno, len, &readptr, &r_len);
  memcpy(data, readptr, r_len);
  data += r_len;
  rc += r_len;
  
  if (r_len != len) {
    readptr = b->data_start;
    r_len = min(len - r_len, b->data_head - b->data_start);
    memcpy(data, readptr, r_len);
    rc += r_len;
  }
  return rc;
}

int circ_buf_write(char *buf, uint32_t sseqno,
                   uint8_t *data, int len) {
  struct circ_buf *b = (struct circ_buf *)buf;
  uint8_t *writeptr;
  int w_len;
  /* we can't write any bytes since we're trying to write too far
     ahead  */
  if (sseqno > b->head_seqno + b->data_len)
    return -1;
  if (len == 0) return 0;

  get_ptr_off_1(b, sseqno, len, &writeptr, &w_len);

  memcpy(writeptr, data, w_len);
  data += w_len;

  if (w_len != len) {
    writeptr = b->data_start;
    w_len = min(len - w_len, b->data_head - b->data_start);
    memcpy(writeptr, data, w_len);
  }

  return 0;
}

#ifdef PC             
void circ_buf_dump(void *buf) {
  struct circ_buf *b = (struct circ_buf *)buf;
  uint8_t *d;
  int i;
/*   printf("circ buf: %p\n\tmap: %p\n\tmap_len: %i\n\tdata_start: %p\n\t" */
/*          "data_head: %p\n\tdata_len: %i\n\thead_seqno: %i\n",  */
/*          b, b->map, */
/*          b->map_len, b->data_start, b->data_head, b->data_len, b->head_seqno); */
  for (d = b->data_start; d < b->data_start + b->data_len; d++) {
    if (d == b->data_head) putc('|', stdout);
    printf("%2.x ", *d);
  }
  putc('\n', stdout);
}
#endif
