/* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.

   This program 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; version 2 of the License.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#include <stdlib.h>
#include <assert.h>

#include "x_platform.h"

#include "xcom_common.h"
#include "xcom_vp.h"
#include "node_list.h"
#include "simset.h"
#include "task.h"
#include "server_struct.h"
#include "xcom_detector.h"
#include "site_struct.h"
#include "xcom_base.h"
#include "bitset.h"
#include "xcom_memory.h"
#include "synode_no.h"
#include "node_no.h"
#include "task_debug.h"
#include "xcom_transport.h"
#include "site_def.h"
#include "node_set.h"

typedef site_def *site_def_ptr;

struct site_def_ptr_array {
	u_int count;
	u_int site_def_ptr_array_len;
	site_def_ptr *site_def_ptr_array_val;
};
typedef struct site_def_ptr_array site_def_ptr_array;

define_xdr_funcs(site_def_ptr)

/* FIFO of site definitions */
static site_def_ptr_array site_defs;
static site_def *incoming = 0;
static inline node_no _get_maxnodes(site_def const *site);

/* purecov: begin deadcode */
/* Save incoming site def, but do not make it available yet */
site_def *begin_site_def(site_def *s)
{
	assert(!incoming);
	incoming = s;
	assert(s->global_node_set.node_set_len == _get_maxnodes(s));
	return incoming;
}


/* Push saved site def, making it active from synode start */
site_def *end_site_def(synode_no start)
{
	assert(incoming);
	incoming->start = start;
	return push_site_def(incoming);
}
/* purecov: end */

/* Rteurn pointer to array of site defs */
void	get_all_site_defs(site_def ***s, uint32_t *n)
{
	*s = site_defs.site_def_ptr_array_val;
	*n = site_defs.site_def_ptr_array_len;
}


/* Module initialization */
void	init_site_vars()
{
	init_site_def_ptr_array(&site_defs);
	site_defs.count = 0;
	incoming = 0;
}


/* Recursively free a complete site_def.  Only free the site_def, not
 the servers that it points to, since servers are shared by multiple
 site_defs, and will eventually be deallocated by garbage_collect_servers
*/
void	free_site_def(site_def *s)
{
	if (s) {
		invalidate_detector_sites(s);
		xdr_free((xdrproc_t) xdr_node_list, (char *)(&s->nodes));
		free_node_set(&s->global_node_set);
		free_node_set(&s->local_node_set);
		free(s);
	}
}


/* Free all resources in this module */
void	free_site_defs()
{
	u_int	i;
	for(i = 0; i < site_defs.count; i++) {
		free_site_def(site_defs.site_def_ptr_array_val[i]);
	}
	free_site_def_ptr_array(&site_defs);
	site_defs.count = 0;
	free_site_def(incoming);
}


/* Add a new site definition to the list */
site_def *push_site_def(site_def *s)
{
	uint32_t	i;
	set_site_def_ptr(&site_defs, 0, site_defs.count);
	DBGOUT(FN;
	NDBG(site_defs.count,u);
	PTREXP(s);
	if (s) {
		SYCEXP(s->start);
		SYCEXP(s->boot_key);
	}
	);
	for (i = site_defs.count; i > 0; i--) {
		DBGOUT(NDBG(i-1,d);
		PTREXP(site_defs.site_def_ptr_array_val[i-1]);
		if (site_defs.site_def_ptr_array_val[i-1]) {
			SYCEXP(site_defs.site_def_ptr_array_val[i-1]->start);
			SYCEXP(site_defs.site_def_ptr_array_val[i-1]->boot_key);
		}
		);
		site_defs.site_def_ptr_array_val[i] = site_defs.site_def_ptr_array_val[i-1];
	}
	set_site_def_ptr(&site_defs, s, 0);
	if(s){
		s->x_proto =  set_latest_common_proto(common_xcom_version(s));
		G_DEBUG("latest common protocol is now %d",s->x_proto);
	}
	site_defs.count++;
	assert(!s  || (s->global_node_set.node_set_len == _get_maxnodes(s)));
	return s;
}

/* Return first site def */
static inline site_def const *_get_site_def()
{
	assert(site_defs.count == 0 || !site_defs.site_def_ptr_array_val[0] || site_defs.site_def_ptr_array_val[0]->global_node_set.node_set_len ==
	    _get_maxnodes(site_defs.site_def_ptr_array_val[0]));
	if (site_defs.count > 0)
		return site_defs.site_def_ptr_array_val[0];
	else
		return 0;
}


/* Return first site def */
site_def *get_site_def_rw()
{
	if (site_defs.count > 0)
		return site_defs.site_def_ptr_array_val[0];
	else
		return 0;
}

/* purecov: begin deadcode */
/* Return previous site def */
static inline site_def const *_get_prev_site_def()
{
	assert(site_defs.count == 0 || !site_defs.site_def_ptr_array_val[1] || site_defs.site_def_ptr_array_val[1]->global_node_set.node_set_len ==
	    _get_maxnodes(site_defs.site_def_ptr_array_val[1]));
	if (site_defs.count > 0)
		return site_defs.site_def_ptr_array_val[1];
	else
		return 0;
}
/* purecov: end */



/* Return first site def */
site_def const *get_site_def()
{
	return _get_site_def();
}

/* purecov: begin deadcode */
/* Return previous site def */
site_def const *get_prev_site_def()
{
	return _get_prev_site_def();
}
/* purecov: end */

static inline int match_def(site_def const *site, synode_no synode)
{
	return site && (synode.group_id == 0 || synode.group_id == site->start.group_id)
	&& !synode_lt(synode, site->start);
}

/* Return first site def which has start less than or equal to synode */
site_def const *find_site_def(synode_no synode)
{
	site_def const * retval = 0;
	u_int i;

	for(i = 0; i < site_defs.count; i++)
		if (match_def(site_defs.site_def_ptr_array_val[i], synode)){
		retval =  site_defs.site_def_ptr_array_val[i];
		break;
	}
	assert(!retval || retval->global_node_set.node_set_len == _get_maxnodes(retval));
	return retval;
}

/* As find_site_def, but return pointer to non-const object */
site_def *find_site_def_rw(synode_no synode)
{
	site_def * retval = 0;
	u_int i;

	for(i = 0; i < site_defs.count; i++)
		if (match_def(site_defs.site_def_ptr_array_val[i], synode)){
		retval =  site_defs.site_def_ptr_array_val[i];
		break;
	}
	assert(!retval || retval->global_node_set.node_set_len == _get_maxnodes(retval));
	return retval;
}


void garbage_collect_site_defs(synode_no x)
{
	u_int	i;
	u_int s_max = site_defs.count;

	DBGOUT(FN;
	    NDBG(site_defs.count, u);
	    SYCEXP(x);
	    );
	for (i = 3; i < s_max; i++) {
		if (match_def(site_defs.site_def_ptr_array_val[i], x)) {
			break;
		}
	}
	i++;
	for (; i < s_max; i++) {
		site_def * site = site_defs.site_def_ptr_array_val[i];
		DBGOUT(NDBG(i, d);
		    PTREXP(site_defs.site_def_ptr_array_val[i]); );
		if (site) {
			DBGOUT(SYCEXP(site->start);
			    SYCEXP(site->boot_key); );
			free_site_def(site);
			site_defs.site_def_ptr_array_val[i] = 0;
		}
		site_defs.count--;
	}
}

/* purecov: begin deadcode */
char *dbg_site_def(site_def const *site)
{
	assert(site->global_node_set.node_set_len == _get_maxnodes(site));
	return dbg_list(&site->nodes);
}
/* purecov: end */

/* Create a new empty site_def */
site_def *new_site_def()
{
	site_def * retval =  (site_def * ) calloc(1, sizeof(site_def));
	retval->nodeno = VOID_NODE_NO;
	return retval;
}


/* {{{ Clone a site definition */

site_def *clone_site_def(site_def const *site)
{
	site_def * retval = new_site_def();
	assert(site->global_node_set.node_set_len == _get_maxnodes(site));
	*retval = *site;
	init_node_list(site->nodes.node_list_len, site->nodes.node_list_val, &retval->nodes);
	retval->global_node_set = clone_node_set(site->global_node_set);
	retval->local_node_set = clone_node_set(site->local_node_set);
	assert(retval->global_node_set.node_set_len == _get_maxnodes(retval));
	DBGOUT(FN; PTREXP(site); PTREXP(retval));
	return retval;
}


/* }}} */

/* {{{ Initialize a site definition from array of string pointers */

void	init_site_def(u_int n, node_address *names, site_def *site)
{
	site->start = null_synode;
	site->boot_key = null_synode;
	site->nodeno = VOID_NODE_NO;
	init_detector(site->detected);
	init_node_list(n, names, &site->nodes);
	site->global_node_count = 0;
	alloc_node_set(&site->global_node_set, NSERVERS);
	site->global_node_set.node_set_len = site->nodes.node_list_len;
	set_node_set(&site->global_node_set); /* Assume everyone is there */
	assert(site->global_node_set.node_set_len == _get_maxnodes(site));
	alloc_node_set(&site->local_node_set, NSERVERS);
	site->local_node_set.node_set_len = site->nodes.node_list_len;
	set_node_set(&site->local_node_set); /* Assume everyone is there */
	assert(site->local_node_set.node_set_len == _get_maxnodes(site));
	site->detector_updated = 0;
	site->x_proto = my_xcom_version;
}


/* }}} */


/* Add nodes to site definition, avoid duplicates */
void	add_site_def(u_int n, node_address *names, site_def *site)
{
	if (n > 0) {
		add_node_list(n, names, &site->nodes);
	}
	realloc_node_set(&site->global_node_set, _get_maxnodes(site));
	realloc_node_set(&site->local_node_set, _get_maxnodes(site));
}

/* Remove nodes from site definition, ignore missing nodes */
void	remove_site_def(u_int n, node_address *names, site_def *site)
{
	if (n > 0) {
		remove_node_list(n, names, &site->nodes);
	}
	init_detector(site->detected); /* Zero all unused timestamps */
	realloc_node_set(&site->global_node_set, _get_maxnodes(site));
	realloc_node_set(&site->local_node_set, _get_maxnodes(site));
}

/* purecov: begin deadcode */
/* Return boot_key of first site def */
synode_no get_boot_key()
{
	assert(!_get_site_def() || _get_site_def()->global_node_set.node_set_len == _get_maxnodes(_get_site_def()));
	if (get_site_def()) {
		return get_site_def()->boot_key;
	} else {
		return null_synode;
	}
}


/* Set boot_key of first site def */
void	set_boot_key(synode_no const x)
{
	assert(_get_site_def());
	assert(_get_site_def()->global_node_set.node_set_len == _get_maxnodes(_get_site_def()));
	if (site_defs.site_def_ptr_array_val[0]) {
		site_defs.site_def_ptr_array_val[0]->boot_key = x;
	}
}
/* purecov: end */

/* Return group id of site */
uint32_t get_group_id(site_def const *site)
{
	if (site) {
		uint32_t	group_id = site->start.group_id;
		assert(site->global_node_set.node_set_len == _get_maxnodes(site));
		MAY_DBG(FN; NDBG(group_id, lx); );
		return group_id;
	} else {
		return null_id;
	}
}


#if 0
void	set_group_id(site_def *site, uint32_t id)
{
	MAY_DBG(FN; STRLIT("changing group id from ");
	    NDBG(get_group_id(site), lx);
	    STRLIT("to ");
	    NDBG(id, lu);
	    );
	site->group_id = id;
}


#endif

static inline node_no _get_maxnodes(site_def const *site)
{
	if (site) {
		return site->nodes.node_list_len;
	} else
		return 0;
}


/* Return maxnodes of site */
node_no get_maxnodes(site_def const *site)
{
	return _get_maxnodes(site);
}

/* purecov: begin deadcode */
node_no get_prev_maxnodes()
{
	return _get_maxnodes(_get_prev_site_def());
}
/* purecov: end */

/* Return nodeno of site */
static inline node_no _get_nodeno(site_def const *site)
{
	if (site) {
		assert(site->global_node_set.node_set_len == _get_maxnodes(site));
		return site->nodeno;
	} else
		return VOID_NODE_NO;
}

/* purecov: begin deadcode */
/* Return nodeno of site */
node_no get_nodeno(site_def const *site)
{
	return _get_nodeno(site);
}
/* purecov: end */

node_no get_prev_nodeno()
{
	return _get_nodeno(_get_prev_site_def());
}


void import_config(gcs_snapshot *gcs_snap)
{
	int	i;
	DBGOUT(FN; SYCEXP(gcs_snap->log_start));
	for (i = (int)gcs_snap->cfg.configs_len-1; i >= 0; i--) {
		config_ptr cp = gcs_snap->cfg.configs_val[i];
		if (cp) {
			site_def * site = new_site_def();
			DBGOUT(FN; SYCEXP(cp->start); SYCEXP(cp->boot_key));
			init_site_def(cp->nodes.node_list_len,
			    cp->nodes.node_list_val, site);
			site->start = cp->start;
			site->boot_key = cp->boot_key;
			site_install_action(site);
		}
	}
}

extern synode_no executed_msg;

gcs_snapshot *export_config()
{
	u_int	i;
	gcs_snapshot *gcs_snap = calloc(1, sizeof(gcs_snapshot));
	gcs_snap->cfg.configs_val = calloc(site_defs.count, sizeof(config_ptr));
	gcs_snap->cfg.configs_len = site_defs.count;

	for (i = 0; i < site_defs.count; i++) {
		site_def *site = site_defs.site_def_ptr_array_val[i];
		if(site) {
			config_ptr cp = calloc(1, sizeof(config));
			init_node_list(site->nodes.node_list_len,
			    site->nodes.node_list_val, &cp->nodes);
			cp->start = site->start;
			cp->boot_key = site->boot_key;
			DBGOUT(FN; SYCEXP(cp->start); SYCEXP(cp->boot_key));
			gcs_snap->cfg.configs_val[i] = cp;
		}
	}
	gcs_snap->log_start = get_delivered_msg();
	return gcs_snap;
}


/* Return the global minimum delivered message number, based on incoming gossip */
synode_no get_min_delivered_msg(site_def const *s)
{
	u_int i;
	synode_no retval = null_synode;
	int	init = 1;

	for (i = 0; i < s->nodes.node_list_len; i++) {
		if (s->servers[i]->detected + DETECTOR_LIVE_TIMEOUT > task_now()) {
			if (init) {
				init = 0;
				retval = s->delivered_msg[i];
			} else {
				if (synode_lt(s->delivered_msg[i], retval)) {
					retval = s->delivered_msg[i];
				}
			}
		}
	}
	DBGOUT(FN; SYCEXP(retval));
	return retval;
}


/* Track the minimum delivered message numbers based on incoming messages */
void update_delivered(site_def *s, node_no node, synode_no msgno)
{
	if(node < s->nodes.node_list_len){
		s->delivered_msg[node] = msgno;
		DBGOUT(FN; SYCEXP(s->delivered_msg[node]); NDBG(node,u));
	}
}
