/* 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 <ctype.h>

#include <assert.h>

#include <ucontext.h>

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

/* TODO: limit size of individual simple strings */
/* TODO: limit nesting of sexps */

static sexp_t *read_simple_string(IOHandle *h, cmsg_bytes_t buf) {
  int i = 0;
  sexp_t *result;

  while (1) {
    buf = iohandle_readwait(h, buf.len + 1);
    if (buf.len == 0) return NULL;
    /* Don't reset i to zero: avoids scanning the beginning of the
       number repeatedly */

    while (i < buf.len) {
      if (i > 10) {
	/* More than ten digits of length prefix. We're unlikely to be
	   able to cope with anything that large. */
	h->error_kind = SEXP_ERROR_OVERFLOW;
	return NULL;
      }
      if (buf.bytes[i] == ':') {
	size_t count;
	buf.bytes[i] = '\0';
	count = atoi((char *) buf.bytes);
	iohandle_drain(h, i + 1);
	buf = iohandle_readwait(h, count);
	if (buf.len < count) {
	  /* Error or EOF. */
	  return NULL;
	}
	buf.len = count;
	result = sexp_bytes(buf);
	iohandle_drain(h, count);
	return result;
      }

      if (!isdigit(buf.bytes[i])) {
	h->error_kind = SEXP_ERROR_SYNTAX;
	return NULL;
      }

      i++;
    }
  }
}

sexp_t *sexp_read_atom(IOHandle *h) {
  return read_simple_string(h, EMPTY_BYTES);
}

#define READ1					\
  buf = iohandle_readwait(h, 1);		\
  if (buf.len == 0) goto error;

int sexp_read(IOHandle *h, sexp_t **result_ptr) {
  cmsg_bytes_t buf;
  sexp_t *stack = NULL; /* held */
  sexp_t *hint = NULL; /* held */
  sexp_t *body = NULL; /* held */
  sexp_t *accumulator = NULL; /* not held */

  while (1) {
    READ1;
    switch (buf.bytes[0]) {
      case '[': {
	iohandle_drain(h, 1);
	hint = INCREF(read_simple_string(h, EMPTY_BYTES));
	if (hint == NULL) goto error;
	READ1;
	if (buf.bytes[0] != ']') {
	  h->error_kind = SEXP_ERROR_SYNTAX;
	  goto error;
	}
	iohandle_drain(h, 1);
      skip_whitespace_in_display_hint:
	READ1;
	if (isspace(buf.bytes[0])) {
	  iohandle_drain(h, 1);
	  goto skip_whitespace_in_display_hint;
	}
	body = INCREF(read_simple_string(h, EMPTY_BYTES));
	if (body == NULL) goto error;
	accumulator = sexp_display_hint(hint, body);
	DECREF(hint, sexp_destructor); /* these could be UNGRABs */
	DECREF(body, sexp_destructor);
	break;
      }

      case '(':
	iohandle_drain(h, 1);
	stack = sexp_push(stack, sexp_cons(NULL, NULL));
	continue;

      case ')': {
	sexp_t *current;
	if (stack == NULL) {
	  h->error_kind = SEXP_ERROR_SYNTAX;
	  goto error;
	}
	stack = sexp_pop(stack, &current);
	INCREF(current);
	iohandle_drain(h, 1);
	accumulator = INCREF(sexp_head(current));
	DECREF(current, sexp_destructor);
	UNGRAB(accumulator);
	break;
      }

      default:
	if (isspace(buf.bytes[0])) {
	  iohandle_drain(h, 1);
	  continue;
	}
	buf.len = 1; /* needed to avoid reading too much in read_simple_string */
	accumulator = read_simple_string(h, buf);
	if (accumulator == NULL) goto error;
	break;
    }

    if (stack == NULL) {
      *result_ptr = accumulator;
      return 1;
    } else {
      sexp_t *current = sexp_head(stack); /* not held */
      sexp_t *cell = sexp_cons(accumulator, NULL);
      if (sexp_tail(current) == NULL) {
	sexp_sethead(current, cell);
      } else {
	sexp_settail(sexp_tail(current), cell);
      }
      sexp_settail(current, cell);
    }
  }

 error:
  DECREF(stack, sexp_destructor);
  DECREF(hint, sexp_destructor);
  DECREF(body, sexp_destructor);
  return 0;
}

void write_simple_string(IOHandle *h, sexp_t *x) {
  cmsg_bytes_t data = sexp_data(x);
  char lenstr[16];
  snprintf(lenstr, sizeof(lenstr), "%u:", (unsigned int) data.len);
  lenstr[sizeof(lenstr) - 1] = '\0';
  iohandle_write(h, cmsg_cstring_bytes(lenstr));
  iohandle_write(h, data);
}

unsigned short sexp_write(IOHandle *h, sexp_t *x) {
  sexp_t *stack = NULL; /* held */
  sexp_t *current = x;

 write1:
  if (current == NULL) {
    iohandle_write(h, cmsg_cstring_bytes("()"));
  } else {
    switch (current->kind) {
      case SEXP_BYTES:
      case SEXP_SLICE:
	write_simple_string(h, current);
	break;

      case SEXP_DISPLAY_HINT:
	iohandle_write(h, cmsg_cstring_bytes("["));
	write_simple_string(h, sexp_hint(current));
	iohandle_write(h, cmsg_cstring_bytes("]"));
	write_simple_string(h, sexp_body(current));
	break;

      case SEXP_PAIR:
	iohandle_write(h, cmsg_cstring_bytes("("));
	stack = sexp_push(stack, current);
	break;

      default:
	die("Unknown sexp kind %d in sexp_write\n", current->kind);
    }
  }

 check_stack:
  if (stack == NULL) {
    return 0;
  }

  {
    sexp_t *cell = sexp_head(stack);
    if (cell == NULL) {
      iohandle_write(h, cmsg_cstring_bytes(")"));
      stack = sexp_pop(stack, NULL); /* no need to worry about incref/decref: val is NULL! */
      goto check_stack;
    }

    if (sexp_pairp(cell)) {
      current = sexp_head(cell);
      sexp_sethead(stack, sexp_tail(cell));
      goto write1;
    }

    return SEXP_ERROR_SYNTAX;
  }
}

unsigned short sexp_writeln(IOHandle *h, sexp_t *x) {
  unsigned short result;

  fflush(NULL);
  result = sexp_write(h, x);
  if (result == 0) {
    iohandle_write(h, cmsg_cstring_bytes("\n"));
    ICHECK(iohandle_flush(h), "sexp_writeln iohandle_flush");
  }
  return result;
}
