#include <stdlib.h>
#include <string.h>
#include "avrbuf.h"

void buf_init(struct buf_t *p, void *buf, uint8_t sz) {
  p->buf = (uint8_t *) buf;
  p->sz = sz;
  p->len = 0;
  p->sta = 0;
}

// first the simple stuff

uint8_t buf_sz(struct buf_t *p) { return p->sz; }
uint8_t buf_len(struct buf_t *p) { return p->len; }
void buf_flush(struct buf_t *p) { p->len = p->sta = 0; }
uint8_t buf_end(struct buf_t *p) { return (p->sta + p->len - 1) % p->sz; } // only valid if p->len > 0
uint8_t buf_nxt(struct buf_t *p) { return (p->sta + p->len) % p->sz; }
int8_t buf_check(struct buf_t *p) {
  if (p == NULL) return -1;
  if (p->buf == NULL) return -1;
  return 0;
}


// main functions, put/get chars to/from buffer

uint8_t buf_putlast(struct buf_t *p, uint8_t cc) { // push
  if (buf_check(p) < 0) return 0;
  if (p->len >= p->sz) return 0;
  p->buf[buf_nxt(p)] = cc;
  p->len++;
  return 1;
}

uint8_t buf_getlast(struct buf_t *p, uint8_t *cc) { // pop
  if (buf_check(p) < 0 || cc == NULL) return 0;
  if (p->len < 1) return 0;
  *cc = p->buf[buf_end(p)];
  p->len--;
  if (p->len == 0) p->sta = 0;
  return 1;
}

uint8_t buf_peeklast(struct buf_t *p, uint8_t *cc) {
  if (buf_check(p) < 0 || cc == NULL) return 0;
  if (p->len < 1) return 0;
  *cc = p->buf[buf_end(p)];
  return 1;
}

uint8_t buf_droplast(struct buf_t *p) {
  if (buf_check(p) < 0) return 0;
  if (p->len < 1) return 0;
  p->len--;
  if (p->len == 0) p->sta = 0;
  return 0;
}

uint8_t buf_putfirst(struct buf_t *p, uint8_t cc) { // unshift
  if (buf_check(p) < 0) return 0;
  if (p->len >= p->sz) return 0;
  if (p->sta == 0) p->sta = p->sz;
  p->sta--;
  p->len++;
  p->buf[p->sta] = cc;
  return 1;
}

uint8_t buf_getfirst(struct buf_t *p, uint8_t *cc) { // shift
  if (buf_check(p) < 0 || cc == NULL) return 0;
  if (p->len < 1) return 0;
  *cc = p->buf[p->sta];
  p->len--;
  p->sta++;
  if (p->len == 0 || p->sta >= p->sz) p->sta = 0;
  return 1;
}

uint8_t buf_peekfirst(struct buf_t *p, uint8_t *cc) {
  if (buf_check(p) < 0 || cc == NULL) return 0;
  if (p->len < 1) return 0;
  *cc = p->buf[p->sta];
  return 1;
}

uint8_t buf_dropfirst(struct buf_t *p) {
  if (buf_check(p) < 0) return 0;
  if (p->len < 1) return 0;
  p->len--;
  p->sta++;
  if (p->len == 0 || p->sta >= p->sz) p->sta = 0;
  return 1;
}

uint8_t buf_read(struct buf_t *p, uint8_t *buf, uint8_t len) {
  if (buf_check(p) < 0 || buf == NULL) return 0;
  if (len < p->len) return 0;
  else {
    uint8_t ix;
    for (ix = 0; ix < p->len; ix++) {
      buf_getfirst(p, buf++);
    }
    buf_flush(p);
    return ix;
  }
}

uint8_t buf_write(struct buf_t *p, const uint8_t *buf, uint8_t len) {
  if (buf_check(p) < 0 || buf == NULL) return 0;
  if ((len == 0) || (p->sz - p->len) < len) {
    return 0;
  } else {
    uint8_t ix;
    for (ix = 0; ix < len; ix++) {
      buf_putlast(p, *buf++);
    }
    return len;
  }
}

// convenience functions

uint8_t buf_chars(struct buf_t *p, const char *buf) {
  return buf_write(p, (const uint8_t *) buf, strlen(buf));
}

uint8_t buf_nl(struct buf_t *p) {
  return buf_write(p, (const uint8_t *) "\r\n", 2);
}

uint8_t buf_uint8(struct buf_t *p, uint8_t val) {
  char str[10];
  utoa(val, str, 10);
  return buf_chars(p, str);
}
uint8_t buf_uint16(struct buf_t *p, uint16_t val) {
  char str[10];
  utoa(val, str, 10);
  return buf_chars(p, str);
}
uint8_t buf_uint32(struct buf_t *p, uint32_t val) {
  char str[10];
  ultoa(val, str, 10);
  return buf_chars(p, str);
}
uint8_t buf_float(struct buf_t *p, float val) {
  char str[12];
  dtostre(val, str, 3, DTOSTR_ALWAYS_SIGN);
  return buf_chars(p, str);
}
uint8_t buf_double(struct buf_t *p, double val) {
  char str[12];
  dtostre(val, str, 3, DTOSTR_ALWAYS_SIGN);
  return buf_chars(p, str);
}
