/* Copyright 2010, 2011, 2012 Tony Garnock-Jones <tonygarnockjones@gmail.com>.
 *
 * This file is part of Hop.
 *
 * Hop is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Hop is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Hop.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <assert.h>

#include "cmsg_private.h"
#include "ref.h"
#include "sexp.h"

static sexp_t *freelist = NULL;

sexp_t *sexp_empty_bytes = NULL;

void init_sexp(void) {
  sexp_empty_bytes = INCREF(sexp_cstring(""));
}

void done_sexp(void) {
  int count = 0;

  DECREF(sexp_empty_bytes, sexp_destructor);
  sexp_empty_bytes = NULL;

  while (freelist != NULL) {
    sexp_t *x = freelist;
    freelist = x->data.pair.tail;
    free(x);
    count++;
  }
  info("Released %d cached sexp shells.\n", count);
}

static inline sexp_t *alloc_shell(sexp_type_t kind) {
  sexp_t *x = freelist;
  if (x == NULL) {
    x = malloc(sizeof(*x));
  } else {
    freelist = x->data.pair.tail;
  }
  x->refcount = ZERO_REFCOUNT();
  x->kind = kind;
  return x;
}

static inline void release_shell(sexp_t *x) {
  x->data.pair.tail = freelist;
  freelist = x;
}

sexp_t *sexp_incref(sexp_t *x) {
  return INCREF(x);
}

sexp_t *sexp_decref(sexp_t *x) {
  return DECREF(x, sexp_destructor);
}

void sexp_data_destructor(sexp_data_t *data) {
  cmsg_bytes_free(data->data);
  free(data);
}

void sexp_destructor(sexp_t *x) {
 tail_recursion:
  switch (x->kind) {
    case SEXP_BYTES:
      cmsg_bytes_free(x->data.bytes);
      break;
    case SEXP_SLICE:
      DECREF(x->data.slice.data, sexp_data_destructor);
      break;
    case SEXP_DISPLAY_HINT:
    case SEXP_PAIR: {
      sexp_t *next = x->data.pair.tail;
      DECREF(x->data.pair.head, sexp_destructor);
      if (next != NULL) {
	if (next->refcount.count == 1) {
	  release_shell(x);
	  x = next;
	  goto tail_recursion;
	} else {
	  DECREF(next, sexp_destructor);
	}
      }
      break;
    }
    default:
      die("Unknown sexp kind %d in dtor\n", x->kind);
  }
  release_shell(x);
}

sexp_data_t *sexp_data_copy(cmsg_bytes_t body, size_t offset, size_t length) {
  assert(offset + length <= body.len);
  return sexp_data_alias(cmsg_bytes_malloc_dup(CMSG_BYTES(length, body.bytes + offset)));
}

sexp_data_t *sexp_data_alias(cmsg_bytes_t body) {
  sexp_data_t *data = malloc(sizeof(*data));
  data->refcount = ZERO_REFCOUNT();
  data->data = body;
  return data;
}

sexp_t *sexp_cstring(char const *str) {
  return sexp_bytes(cmsg_cstring_bytes(str));
}

sexp_t *sexp_bytes(cmsg_bytes_t bytes) {
  sexp_t *x = alloc_shell(SEXP_BYTES);
  x->data.bytes = cmsg_bytes_malloc_dup(bytes);
  return x;
}

sexp_t *sexp_slice(sexp_data_t *data, size_t offset, size_t length) {
  sexp_t *x = alloc_shell(SEXP_SLICE);
  x->data.slice.data = INCREF(data);
  x->data.slice.offset = offset;
  x->data.slice.length = length;
  return x;
}

sexp_t *sexp_display_hint(sexp_t *hint, sexp_t *body) {
  sexp_t *x = alloc_shell(SEXP_DISPLAY_HINT);
  assert(sexp_simple_stringp(hint));
  assert(sexp_simple_stringp(body));
  x->data.pair.head = INCREF(hint);
  x->data.pair.tail = INCREF(body);
  return x;
}

sexp_t *sexp_cons(sexp_t *head, sexp_t *tail) {
  sexp_t *x = alloc_shell(SEXP_PAIR);
  x->data.pair.head = INCREF(head);
  x->data.pair.tail = INCREF(tail);
  return x;
}

cmsg_bytes_t sexp_data(sexp_t *x) {
 restart:
  switch (x->kind) {
    case SEXP_BYTES:
      return x->data.bytes;
    case SEXP_SLICE:
      return CMSG_BYTES(x->data.slice.length,
			x->data.slice.data->data.bytes + x->data.slice.offset);
    case SEXP_DISPLAY_HINT:
      x = x->data.pair.tail;
      goto restart;
    default:
      die("Unknown sexp kind %d in data accessor\n", x->kind);
  }
}

int sexp_cmp(sexp_t *a, sexp_t *b) {
 tail:
  if (a == b) return 0;
  if (sexp_stringp(a) && sexp_stringp(b)) {
    return cmsg_bytes_cmp(sexp_data(a), sexp_data(b));
  }
  if (sexp_pairp(a) && sexp_pairp(b)) {
    int result = sexp_cmp(sexp_head(a), sexp_head(b));
    if (result) return result;
    a = sexp_tail(a);
    b = sexp_tail(b);
    goto tail;
  }
  if (a == NULL) return -1;
  if (b == NULL) return 1;
  if (a->kind < b->kind) return -1;
  return 1;
}

sexp_t *sexp_assoc(sexp_t *list, cmsg_bytes_t key) {
  while (list != NULL) {
    sexp_t *candidate = sexp_head(list);
    if (sexp_stringp(candidate)) {
      cmsg_bytes_t candidate_data = sexp_data(candidate);
      if ((candidate_data.len == key.len)
	  && (!memcmp(candidate_data.bytes, key.bytes, key.len))) {
	return candidate;
      }
    }
    list = sexp_tail(list);
  }
  return NULL;
}

size_t sexp_length(sexp_t *list) {
  size_t result = 0;
  while (sexp_pairp(list)) {
    result++;
    list = sexp_tail(list);
  }
  return result;
}

sexp_t *sexp_new_queue(void) {
  return sexp_cons(NULL, NULL);
}

int sexp_queue_emptyp(sexp_t *q) {
  return sexp_head(q) == NULL;
}

void sexp_queue_pushback(sexp_t *q, sexp_t *x) {
  sexp_t *cell = sexp_cons(x, sexp_head(q));
  if (sexp_head(q) == NULL) {
    sexp_settail(q, cell);
  }
  sexp_sethead(q, cell);
}

void sexp_enqueue(sexp_t *q, sexp_t *x) {
  sexp_t *cell = sexp_cons(x, NULL);
  if (sexp_head(q) == NULL) {
    sexp_sethead(q, cell);
  } else {
    sexp_settail(sexp_tail(q), cell);
  }
  sexp_settail(q, cell);
}

sexp_t *sexp_dequeue(sexp_t *q) {
  if (sexp_head(q) == NULL) {
    return NULL;
  } else {
    sexp_t *x = INCREF(sexp_head(sexp_head(q)));
    sexp_t *cell = sexp_tail(sexp_head(q));
    sexp_sethead(q, cell);
    if (cell == NULL) {
      sexp_settail(q, NULL);
    }
    UNGRAB(x);
    return x;
  }
}
