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

#include <assert.h>

#include <ucontext.h>

#include "cmsg_private.h"
#include "ref.h"
#include "sexp.h"
#include "harness.h"
#include "sexpio.h"
#include "hashtable.h"
#include "node.h"
#include "meta.h"
#include "messages.h"

static cmsg_bytes_t _container_name;
static hashtable_t node_class_table;
static hashtable_t directory;

static void *node_incref(void *arg) {
  return INCREF((node_t *) arg);
}

static void node_decref(void *arg) {
  DECREF((node_t *) arg, node_destructor);
}

void init_node(cmsg_bytes_t container_name) {
  if (container_name.len == 0) {
    unsigned char buf[CMSG_UUID_BUF_SIZE];
    gen_uuid(buf);
    _container_name = cmsg_bytes_malloc_dup(CMSG_BYTES(CMSG_UUID_BUF_SIZE, buf));
  } else {
    _container_name = cmsg_bytes_malloc_dup(container_name);
  }
  info("Local container name is <<%.*s>>\n", _container_name.len, _container_name.bytes);

  init_hashtable(&node_class_table,
		 31,
		 NULL,
		 NULL);
  init_hashtable(&directory,
		 10007,
		 node_incref,
		 node_decref);
}

cmsg_bytes_t local_container_name(void) {
  return _container_name;
}

void register_node_class(node_class_t *nc) {
  cmsg_bytes_t key = cmsg_cstring_bytes(nc->name);
  if (hashtable_contains(&node_class_table, key)) {
    die("Duplicate node class name %s\n", nc->name);
  }
  hashtable_put(&node_class_table, key, nc);
}

node_class_t *lookup_node_class(cmsg_bytes_t name) {
  node_class_t *nc = NULL;
  hashtable_get(&node_class_table, name, (void **) &nc);
  return nc;
}

static void init_node_names(node_t *n) {
  init_hashtable(&n->names, 5, NULL, NULL);
}

node_t *new_node(node_class_t *nc, sexp_t *args, sexp_t **error_out) {
  node_t *n = malloc(sizeof(*n));
  n->refcount = ZERO_REFCOUNT();
  n->node_class = nc;
  n->extension = NULL;
  init_node_names(n);
  if (nc->extend != NULL) {
    sexp_t *error = nc->extend(n, args);
    if (error != NULL) {
      node_destructor(n);
      if (error_out != NULL) {
	*error_out = error;
      } else {
	warn("Creating node of class %s failed with ", nc->name);
	sexp_writeln(stderr_h, error);
      }
      return NULL;
    }
  }
  return n;
}

void node_destructor(node_t *n) {
  if (n->node_class->destroy != NULL) {
    n->node_class->destroy(n);
  }
  unbind_all_names_for_node(n);
  destroy_hashtable(&n->names);
  free(n);
}

node_t *lookup_node(cmsg_bytes_t name) {
  node_t *n = NULL;
  hashtable_get(&directory, name, (void **) &n);
  return n;
}

static void announce_binding(cmsg_bytes_t name, int onoff) {
  sexp_t *filter = sexp_bytes(name);
  INCREF(filter);
  announce_subscription(sexp_empty_bytes, filter, sexp_empty_bytes, sexp_empty_bytes, onoff);
  DECREF(filter, sexp_destructor);
}

int bind_node(cmsg_bytes_t name, node_t *n) {
  if (name.len == 0) {
    warn("Binding to empty name forbidden\n");
    return 0;
  }
  if (hashtable_contains(&directory, name)) {
    return 0;
  }
  hashtable_put(&directory, name, n);
  hashtable_put(&n->names, name, NULL);
  info("Binding node <<%.*s>> of class %s\n", name.len, name.bytes, n->node_class->name);
  announce_binding(name, 1);
  return 1;
}

int unbind_node(cmsg_bytes_t name) {
  node_t *n = NULL;
  hashtable_get(&directory, name, (void **) &n);
  if (n == NULL) {
    return 0;
  } else {
    info("Unbinding node <<%.*s>> of class %s\n", name.len, name.bytes, n->node_class->name);
    hashtable_erase(&n->names, name);
    hashtable_erase(&directory, name);
    announce_binding(name, 0);
    return 1;
  }
}

static void unbind_on_destroy(void *context, cmsg_bytes_t key, void *value) {
  unbind_node(key);
}

void unbind_all_names_for_node(node_t *n) {
  hashtable_t names = n->names;
  init_node_names(n);
  hashtable_foreach(&names, unbind_on_destroy, NULL);
  destroy_hashtable(&names);
}

int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body, sexp_t *token) {
  return send_node_release(node, message_post(sexp_bytes(name), body, token));
}

int send_node(cmsg_bytes_t node, sexp_t *message) {
  node_t *n = lookup_node(node);
  if (n == NULL) {
    return 0;
  }
  n->node_class->handle_message(n, message);
  return 1;
}

int send_node_release(cmsg_bytes_t node, sexp_t *message) {
  int result;
  INCREF(message);
  result = send_node(node, message);
  DECREF(message, sexp_destructor);
  return result;
}
