#ifndef CN_CBOR_C
#define CN_CBOR_C

#ifdef  __cplusplus
extern "C" {
#endif
#ifdef EMACS_INDENTATION_HELPER
} /* Duh. */
#endif

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <arpa/inet.h> // needed for ntohl (e.g.) on Linux

#include "cn-cbor/cn-cbor.h"
#include "cbor.h"

#define CN_CBOR_FAIL(code) do { pb->err = code;  goto fail; } while(0)

void cn_cbor_free(cn_cbor* cb CBOR_CONTEXT) {
  cn_cbor* p = cb;
  assert(!p || !p->parent);
  while (p) {
    cn_cbor* p1;
    while ((p1 = p->first_child)) { /* go down */
      p = p1;
    }
    if (!(p1 = p->next)) {   /* go up next */
      if ((p1 = p->parent))
        p1->first_child = 0;
    }
    CN_CBOR_FREE_CONTEXT(p);
    p = p1;
  }
}

#ifndef CBOR_NO_FLOAT
static double decode_half(int half) {
  int exp = (half >> 10) & 0x1f;
  int mant = half & 0x3ff;
  double val;
  if (exp == 0) val = ldexp(mant, -24);
  else if (exp != 31) val = ldexp(mant + 1024, exp - 25);
  else val = mant == 0 ? INFINITY : NAN;
  return half & 0x8000 ? -val : val;
}
#endif /* CBOR_NO_FLOAT */

/* Fix these if you can't do non-aligned reads */
#define ntoh8p(p) (*(unsigned char*)(p))
#define ntoh16p(p) (ntohs(*(unsigned short*)(p)))
#define ntoh32p(p) (ntohl(*(unsigned long*)(p)))
static uint64_t ntoh64p(unsigned char *p) {
  uint64_t ret = ntoh32p(p);
  ret <<= 32;
  ret += ntoh32p(p+4);
  return ret;
}

static cn_cbor_type mt_trans[] = {
  CN_CBOR_UINT,    CN_CBOR_INT,
  CN_CBOR_BYTES,   CN_CBOR_TEXT,
  CN_CBOR_ARRAY,   CN_CBOR_MAP,
  CN_CBOR_TAG,     CN_CBOR_SIMPLE,
};

struct parse_buf {
  unsigned char *buf;
  unsigned char *ebuf;
  cn_cbor_error err;
};

#define TAKE(pos, ebuf, n, stmt)                \
  if (n > (size_t)(ebuf - pos))                 \
    CN_CBOR_FAIL(CN_CBOR_ERR_OUT_OF_DATA);      \
  stmt;                                         \
  pos += n;

static cn_cbor *decode_item (struct parse_buf *pb CBOR_CONTEXT, cn_cbor* top_parent) {
  unsigned char *pos = pb->buf;
  unsigned char *ebuf = pb->ebuf;
  cn_cbor* parent = top_parent;
  int ib;
  unsigned int mt;
  int ai;
  uint64_t val;
  cn_cbor* cb = NULL;
#ifndef CBOR_NO_FLOAT
  union {
    float f;
    uint32_t u;
  } u32;
  union {
    double d;
    uint64_t u;
  } u64;
#endif /* CBOR_NO_FLOAT */

again:
  TAKE(pos, ebuf, 1, ib = ntoh8p(pos) );
  if (ib == IB_BREAK) {
    if (!(parent->flags & CN_CBOR_FL_INDEF))
      CN_CBOR_FAIL(CN_CBOR_ERR_BREAK_OUTSIDE_INDEF);
    switch (parent->type) {
    case CN_CBOR_BYTES: case CN_CBOR_TEXT:
      parent->type += 2;            /* CN_CBOR_* -> CN_CBOR_*_CHUNKED */
      break;
    case CN_CBOR_MAP:
      if (parent->length & 1)
        CN_CBOR_FAIL(CN_CBOR_ERR_ODD_SIZE_INDEF_MAP);
    default:;
    }
    goto complete;
  }
  mt = ib >> 5;
  ai = ib & 0x1f;
  val = ai;

  cb = CN_CALLOC_CONTEXT();
  if (!cb)
    CN_CBOR_FAIL(CN_CBOR_ERR_OUT_OF_MEMORY);

  cb->type = mt_trans[mt];

  cb->parent = parent;
  if (parent->last_child) {
    parent->last_child->next = cb;
  } else {
    parent->first_child = cb;
  }
  parent->last_child = cb;
  parent->length++;

  switch (ai) {
  case AI_1: TAKE(pos, ebuf, 1, val = ntoh8p(pos)) ; break;
  case AI_2: TAKE(pos, ebuf, 2, val = ntoh16p(pos)) ; break;
  case AI_4: TAKE(pos, ebuf, 4, val = ntoh32p(pos)) ; break;
  case AI_8: TAKE(pos, ebuf, 8, val = ntoh64p(pos)) ; break;
  case 28: case 29: case 30: CN_CBOR_FAIL(CN_CBOR_ERR_RESERVED_AI);
  case AI_INDEF:
    if ((mt - MT_BYTES) <= MT_MAP) {
      cb->flags |= CN_CBOR_FL_INDEF;
      goto push;
    } else {
      CN_CBOR_FAIL(CN_CBOR_ERR_MT_UNDEF_FOR_INDEF);
    }
  }
  // process content
  switch (mt) {
  case MT_UNSIGNED:
    cb->v.uint = val;           /* to do: Overflow check */
    break;
  case MT_NEGATIVE:
    cb->v.sint = ~val;          /* to do: Overflow check */
    break;
  case MT_BYTES: case MT_TEXT:
    cb->v.str = (char *) pos;
    cb->length = val;
    TAKE(pos, ebuf, val, ;);
    break;
  case MT_MAP:
    val <<= 1;
    /* fall through */
  case MT_ARRAY:
    if ((cb->v.count = val)) {
      cb->flags |= CN_CBOR_FL_COUNT;
      goto push;
    }
    break;
  case MT_TAG:
    cb->v.uint = val;
    goto push;
  case MT_PRIM:
    switch (ai) {
    case VAL_FALSE: cb->type = CN_CBOR_FALSE; break;
    case VAL_TRUE:  cb->type = CN_CBOR_TRUE;  break;
    case VAL_NIL:   cb->type = CN_CBOR_NULL;  break;
    case VAL_UNDEF: cb->type = CN_CBOR_UNDEF; break;
    case AI_2:
#ifndef CBOR_NO_FLOAT
      cb->type = CN_CBOR_DOUBLE;
      cb->v.dbl = decode_half(val);
#else /*  CBOR_NO_FLOAT */
      CN_CBOR_FAIL(CN_CBOR_ERR_FLOAT_NOT_SUPPORTED);
#endif /*  CBOR_NO_FLOAT */
      break;
    case AI_4:
#ifndef CBOR_NO_FLOAT
      cb->type = CN_CBOR_DOUBLE;
      u32.u = val;
      cb->v.dbl = u32.f;
#else /*  CBOR_NO_FLOAT */
      CN_CBOR_FAIL(CN_CBOR_ERR_FLOAT_NOT_SUPPORTED);
#endif /*  CBOR_NO_FLOAT */
      break;
    case AI_8:
#ifndef CBOR_NO_FLOAT
      cb->type = CN_CBOR_DOUBLE;
      u64.u = val;
      cb->v.dbl = u64.d;
#else /*  CBOR_NO_FLOAT */
      CN_CBOR_FAIL(CN_CBOR_ERR_FLOAT_NOT_SUPPORTED);
#endif /*  CBOR_NO_FLOAT */
      break;
    default: cb->v.uint = val;
    }
  }
fill:                           /* emulate loops */
  if (parent->flags & CN_CBOR_FL_INDEF) {
    if (parent->type == CN_CBOR_BYTES || parent->type == CN_CBOR_TEXT)
      if (cb->type != parent->type)
          CN_CBOR_FAIL(CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING);
    goto again;
  }
  if (parent->flags & CN_CBOR_FL_COUNT) {
    if (--parent->v.count)
      goto again;
  }
  /* so we are done filling parent. */
complete:                       /* emulate return from call */
  if (parent == top_parent) {
    if (pos != ebuf)            /* XXX do this outside */
      CN_CBOR_FAIL(CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED);
    pb->buf = pos;
    return cb;
  }
  cb = parent;
  parent = parent->parent;
  goto fill;
push:                           /* emulate recursive call */
  parent = cb;
  goto again;
fail:
  pb->buf = pos;
  return 0;
}

cn_cbor* cn_cbor_decode(const unsigned char* buf, size_t len CBOR_CONTEXT, cn_cbor_errback *errp) {
  cn_cbor catcher = {CN_CBOR_INVALID, 0, {0}, 0, NULL, NULL, NULL, NULL};
  struct parse_buf pb;
  cn_cbor* ret;

  pb.buf  = (unsigned char *)buf;
  pb.ebuf = (unsigned char *)buf+len;
  pb.err  = CN_CBOR_NO_ERROR;
  ret = decode_item(&pb CBOR_CONTEXT_PARAM, &catcher);
  if (ret != NULL) {
    /* mark as top node */
    ret->parent = NULL;
  } else {
    if (catcher.first_child) {
      catcher.first_child->parent = 0;
      cn_cbor_free(catcher.first_child CBOR_CONTEXT_PARAM);
    }
//fail:
    if (errp) {
      errp->err = pb.err;
      errp->pos = pb.buf - (unsigned char *)buf;
    }
    return NULL;
  }
  return ret;
}

#ifdef  __cplusplus
}
#endif

#endif  /* CN_CBOR_C */