/* 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/>.
 */
#ifndef cmsg_sexp_h
#define cmsg_sexp_h

typedef struct sexp_data_t_ {
  refcount_t refcount;
  cmsg_bytes_t data;
} sexp_data_t;

typedef enum sexp_type_t_ {
  SEXP_BYTES,
  SEXP_SLICE,
  SEXP_DISPLAY_HINT,
  SEXP_PAIR
} sexp_type_t;

typedef struct sexp_t_ {
  refcount_t refcount;
  sexp_type_t kind;
  union {
    cmsg_bytes_t bytes;
    struct {
      sexp_data_t *data;
      size_t offset;
      size_t length;
    } slice;
    struct {
      struct sexp_t_ *head;
      struct sexp_t_ *tail;
    } pair; /* and display-hint */
  } data;
} sexp_t;

extern sexp_t *sexp_empty_bytes;

extern void init_sexp(void);
extern void done_sexp(void);

extern sexp_t *sexp_incref(sexp_t *x);
extern sexp_t *sexp_decref(sexp_t *x);

extern void sexp_data_destructor(sexp_data_t *data);
extern void sexp_destructor(sexp_t *x);

extern sexp_data_t *sexp_data_copy(cmsg_bytes_t body, size_t offset, size_t length);
extern sexp_data_t *sexp_data_alias(cmsg_bytes_t body);

extern sexp_t *sexp_cstring(char const *str);
extern sexp_t *sexp_bytes(cmsg_bytes_t bytes);
extern sexp_t *sexp_slice(sexp_data_t *data, size_t offset, size_t length);
extern sexp_t *sexp_display_hint(sexp_t *hint, sexp_t *body);
extern sexp_t *sexp_cons(sexp_t *head, sexp_t *tail);

static inline int sexp_simple_stringp(sexp_t *x) {
  return (x != NULL) && ((x->kind == SEXP_BYTES) || (x->kind == SEXP_SLICE));
}

static inline int sexp_stringp(sexp_t *x) {
  return sexp_simple_stringp(x) || ((x != NULL) && (x->kind == SEXP_DISPLAY_HINT));
}

static inline int sexp_pairp(sexp_t *x) {
  return (x != NULL) && (x->kind == SEXP_PAIR);
}

extern cmsg_bytes_t sexp_data(sexp_t *x);
extern int sexp_cmp(sexp_t *a, sexp_t *b);

static inline sexp_t *sexp_head(sexp_t *x) {
  assert(x->kind == SEXP_PAIR);
  return x->data.pair.head;
}

static inline sexp_t *sexp_tail(sexp_t *x) {
  assert(x->kind == SEXP_PAIR);
  return x->data.pair.tail;
}

static inline sexp_t *sexp_hint(sexp_t *x) {
  assert(x->kind == SEXP_DISPLAY_HINT);
  return x->data.pair.head;
}

static inline sexp_t *sexp_body(sexp_t *x) {
  assert(x->kind == SEXP_DISPLAY_HINT);
  return x->data.pair.tail;
}

#define sexp_setter_(settername,fieldname)			\
  static inline sexp_t *settername(sexp_t *x, sexp_t *y) {	\
    sexp_t *old;						\
    assert(x->kind == SEXP_PAIR);				\
    INCREF(y);							\
    old = x->data.pair.fieldname;				\
    x->data.pair.fieldname = y;					\
    DECREF(old, sexp_destructor);				\
    return x;							\
  }

sexp_setter_(sexp_sethead, head)
sexp_setter_(sexp_settail, tail)

static inline sexp_t *sexp_push(sexp_t *oldstack, sexp_t *val) {
  sexp_t *newstack = INCREF(sexp_cons(val, oldstack));
  DECREF(oldstack, sexp_destructor);
  return newstack;
}

static inline sexp_t *sexp_pop(sexp_t *oldstack, sexp_t **valp) {
  sexp_t *nextstack = INCREF(sexp_tail(oldstack));
  sexp_t *val = INCREF(sexp_head(oldstack));
  DECREF(oldstack, sexp_destructor);
  UNGRAB(val);
  if (valp != NULL) {
    *valp = val;
  }
  return nextstack;
}

static inline int sexp_pseudo_pop(sexp_t **px) {
  *px = sexp_tail(*px);
  return sexp_pairp(*px);
}

static inline sexp_t *sexp_listtail(sexp_t *list, size_t dropcount) {
  while (dropcount) {
    list = sexp_tail(list);
    dropcount--;
  }
  return list;
}

static inline sexp_t *sexp_listref(sexp_t *list, size_t index) {
  return sexp_head(sexp_listtail(list, index));
}

extern sexp_t *sexp_assoc(sexp_t *list, cmsg_bytes_t key);
extern size_t sexp_length(sexp_t *list);

extern sexp_t *sexp_new_queue(void);
extern int sexp_queue_emptyp(sexp_t *q);
extern void sexp_queue_pushback(sexp_t *q, sexp_t *x);
extern void sexp_enqueue(sexp_t *q, sexp_t *x);
extern sexp_t *sexp_dequeue(sexp_t *q);

#endif
