/*
Copyright (c) 2000-2004, 2007 Tony Garnock-Jones <tonyg@kcbbs.gen.nz>

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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <assert.h>
#include <stdarg.h>

#include "ubf_a.h"

char const *ubf_a_version(void) {
  return UBF_VERSION; // defined in makefile.
}

void init_ubf_a_pool(UBF_A_Pool *pool, size_t pagesize) {
  pool->root = NULL;

  pool->num_blocks = 0;
  pool->blocklist = NULL;

  pool->pagesize = pagesize ? pagesize : 4096;
  pool->alloc_block = NULL;
  pool->alloc_used = 0;
}

void empty_ubf_a_pool(UBF_A_Pool *pool) {
  int i;

  pool->root = NULL;

  for (i = 0; i < pool->num_blocks; i++) {
    free(pool->blocklist[i]);
  }
  if (pool->blocklist != NULL) {
    free(pool->blocklist);
  }
  pool->num_blocks = 0;
  pool->blocklist = NULL;

  if (pool->alloc_block != NULL) {
    free(pool->alloc_block);
  }
  pool->alloc_block = NULL;
  pool->alloc_used = 0;
}

static void record_pool_block(UBF_A_Pool *pool, void *block) {
  size_t blocklistlength = sizeof(void *) * (pool->num_blocks + 1);

  if (pool->blocklist == NULL) {
    pool->blocklist = malloc(blocklistlength);
  } else {
    pool->blocklist = realloc(pool->blocklist, blocklistlength);
  }

  pool->blocklist[pool->num_blocks] = block;
  pool->num_blocks++;
}

void *ubf_a_pool_alloc(UBF_A_Pool *pool, size_t amount) {
  if (amount == 0) {
    return NULL;
  }

  amount = (amount + 7) & (~7); /* round up to nearest 8-byte boundary */

  if (amount > (pool->pagesize >> 1)) {
    void *result = calloc(1, amount);
    record_pool_block(pool, result);
    return result;
  }

  if (pool->alloc_block != NULL) {
    assert(pool->alloc_used <= pool->pagesize);

    if (pool->alloc_used + amount <= pool->pagesize) {
      void *result = pool->alloc_block + pool->alloc_used;
      pool->alloc_used += amount;
      return result;
    }

    record_pool_block(pool, pool->alloc_block);
  }

  pool->alloc_block = calloc(1, pool->pagesize);
  pool->alloc_used = amount;
  return pool->alloc_block;
}

char const *ubf_a_error_message(UBF_A_Error error) {
  static char err_buf[1024];
  switch (error) {
    case UBF_A_NoError: return "No error";
    case UBF_A_EarlyEOF: return "End-of-stream reached early";
    case UBF_A_UnhandledCharacter: return "Unhandled character reached during decode";
    case UBF_A_UnsupportedQuotedCharacter: return "Unsupported quoted character in quoted entity";
    case UBF_A_OrphanSemanticTag: return "Semantic tag found without referent";
    case UBF_A_MissingBinaryLength: return "Binary object found without length";
    case UBF_A_MissingBinaryTilde: return "Binary object ill-formed";
    case UBF_A_StackUnderflow: return "Stack Underflow during decoding";
    case UBF_A_StackOverflow: return "Too many elements on stack at end-of-message";
    case UBF_A_ReservedCharacter: return "Attempt to bind to a reserved character";
    case UBF_A_CharacterOutOfRange: return "Bind character out-of-range of 8-bit-byte";
    case UBF_A_MissingOpenStruct: return "Missing open-struct";
    case UBF_A_UnknownObjectKind: return "Could not encode unknown UBF_A_ObjectKind";
    default:
      sprintf(err_buf, "Unknown error (%d)", (int) error);
      return err_buf;
  }
}

UBF_A_Object *ubf_a_number(UBF_A_Pool *pool, signed long long num) {
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object));
  o->kind = UBF_A_NUMBER;
  o->body.num = num;
  return o;
}

UBF_A_Object *ubf_a_string(UBF_A_Pool *pool, char const *str) {
  size_t len = strlen(str);
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object) + len + 1);
  o->kind = UBF_A_STRING;
  o->body.data.length = len;
  memcpy(o->body.data.vec, str, len);
  o->body.data.vec[len] = '\0';
  return o;
}

UBF_A_Object *ubf_a_string_nocopy(UBF_A_Pool *pool, size_t len) {
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object) + len + 1);
  o->kind = UBF_A_STRING;
  o->body.data.length = len;
  o->body.data.vec[len] = '\0';
  return o;
}

UBF_A_Object *ubf_a_symbol(UBF_A_Pool *pool, char const *sym) {
  size_t len = strlen(sym);
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object) + len + 1);
  o->kind = UBF_A_SYMBOL;
  o->body.data.length = len;
  memcpy(o->body.data.vec, sym, len);
  o->body.data.vec[len] = '\0';
  return o;
}

UBF_A_Object *ubf_a_binary(UBF_A_Pool *pool, char const *bin, size_t len) {
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object) + len);
  o->kind = UBF_A_BINARY;
  o->body.data.length = len;
  memcpy(o->body.data.vec, bin, len);
  return o;
}

UBF_A_Object *ubf_a_binary_nocopy(UBF_A_Pool *pool, size_t len) {
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object) + len);
  o->kind = UBF_A_BINARY;
  o->body.data.length = len;
  return o;
}

UBF_A_Object *ubf_a_tag(UBF_A_Pool *pool, char const *key, UBF_A_Object *value) {
  size_t len = strlen(key);
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object) + len + 1);
  o->kind = UBF_A_TAG;
  o->body.tag.value = value;
  strcpy(o->body.tag.key, key);
  return o;
}

UBF_A_Object *ubf_a_pair(UBF_A_Pool *pool, UBF_A_Object *head, UBF_A_Object *tail) {
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object));
  o->kind = UBF_A_PAIR;
  o->body.pair.head = head;
  o->body.pair.tail = tail;
  return o;
}

UBF_A_Object *ubf_a_list(UBF_A_Pool *pool, ...) {
  va_list vl;
  UBF_A_Object *list = NULL;
  va_start(vl, pool);
  while (1) {
    UBF_A_Object *o = va_arg(vl, UBF_A_Object *);
    if (o == NULL) break;
    list = ubf_a_pair(pool, o, list);
  }
  va_end(vl);
  ubf_a_list_reverse(&list);
  return list;
}

UBF_A_Object *ubf_a_vector_init(UBF_A_Pool *pool, size_t len, ...) {
  va_list vl;
  size_t i;
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object) + len * sizeof(UBF_A_Object *));
  o->kind = UBF_A_VECTOR;
  o->body.vector.length = len;

  va_start(vl, len);
  for (i = 0; i < len; i++) {
    o->body.vector.vec[i] = va_arg(vl, UBF_A_Object *);
  }
  va_end(vl);

  return o;
}

UBF_A_Object *ubf_a_vector(UBF_A_Pool *pool, size_t len) {
  UBF_A_Object *o = ubf_a_pool_alloc(pool, sizeof(UBF_A_Object) + len * sizeof(UBF_A_Object *));
  o->kind = UBF_A_VECTOR;
  o->body.vector.length = len;
  return o;
}

void ubf_a_list_reverse(UBF_A_Object **listarg) {
  UBF_A_Object *prev = NULL;
  UBF_A_Object *list = *listarg;

  while (list != NULL) {
    UBF_A_Object *next = list->body.pair.tail;
    list->body.pair.tail = prev;
    prev = list;
    list = next;
  }

  *listarg = prev;
}

#define TWOCMP(a, b)   ((a < b) ? -1 : ((a > b) ? 1 : 0))

int ubf_a_compare(UBF_A_Object const *left, UBF_A_Object const *right) {
  if (left == NULL)
    return (right == NULL) ? 0 : -1;

  if (right == NULL)
    return (left == NULL) ? 0 : 1;

  if (left->kind != right->kind)
    return (int) left->kind - (int) right->kind;

  switch (left->kind) {
    case UBF_A_NUMBER:
      return TWOCMP(left->body.num, right->body.num);

    case UBF_A_STRING:
    case UBF_A_SYMBOL:
    case UBF_A_BINARY: {
      size_t minlen = (left->body.data.length < right->body.data.length)
	? left->body.data.length : right->body.data.length;
      int cmpresult = memcmp(left->body.data.vec, right->body.data.vec, minlen);
      return cmpresult ? cmpresult : TWOCMP(left->body.data.length, right->body.data.length);
    }

    case UBF_A_TAG: {
      int cmpresult = strcmp(left->body.tag.key, right->body.tag.key);
      return cmpresult ? cmpresult : ubf_a_compare(left->body.tag.value, right->body.tag.value);
    }

    case UBF_A_PAIR: {
      int cmpresult = ubf_a_compare(left->body.pair.head, right->body.pair.head);
      return cmpresult ? cmpresult : ubf_a_compare(left->body.pair.tail, right->body.pair.tail);
    }

    case UBF_A_VECTOR: {
      size_t minlen = (left->body.vector.length < right->body.vector.length)
	? left->body.vector.length : right->body.vector.length;
      int i;
      for (i = 0; i < minlen; i++) {
	int cmpresult = ubf_a_compare(left->body.vector.vec[i], right->body.vector.vec[i]);
	if (cmpresult) return cmpresult;
      }
      return TWOCMP(left->body.vector.length, right->body.vector.length);
    }

    default:
      return -1;
  }
}

