/*\
 *	DISTRIBUTION: HNMS v2.0
 *	FILE: hnmslib/peer.c
 *
 *	Peer-to-peer communications with other modules.
 *
 *	Jude George
 *	NAS Facility, NASA Ames Research Center
 *
 *	Copyright (c) 1994 Jude George
 *
 *	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; either version 1, or (at your option)
 *	any later version.
 *
 *	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., 675 Mass Ave, Cambridge, MA 02139, USA.
\*/

#include <netdb.h>

#include "stdhnms.h"

static struct peer {
    struct peer			*next;
    int				module_id;
    unsigned int		timestamp_out;
    VarBindList			query;
    VarBindList			subs[MAX_OBJECTS + 1];
    VarBindList			rel_subs[MAX_OBJECTS + 1];
};
typedef struct peer		*Peer;

static Peer			peers = NULL;

static VarBindList		subs_out[MAX_OBJECTS + 1];

/*\
 *  For non-server modules.  Establish a connection to the server,
 *  if I am not already connected.  The server's hostname is expected
 *  to be in the hnmsModuleCurrentServerHost parameter.
\*/
int PEER_connect_server()
{
    Peer		p;
    int			rval;
    struct hostent	*h;
    char		*cp;
    unsigned int	ip_address;
    Message		msg;

    if (PARAM_get_int(oid_hnmsModuleType) == MODULE_SERVER)
	return 0;

    /*
     * If I'm already connected, skip.
     */
    for (p = peers; p; p = p->next)
	if (p->module_id == 0)
	    return 0;

    cp = PARAM_get_str(oid_hnmsModuleCurrentServerHost);
    if (!cp)
	return 0;

    h = gethostbyname(cp);
    if (!h)
	return -1;

    bcopy(h->h_addr_list[0], &ip_address, sizeof(ip_address));

    rval = HNMP_open(ip_address, HNMP_PORT, MODULE_SERVER);
    if (rval < 0)
	return 0;

    msg = Message_create(HNMP_HELLO);
    HNMP_Message_enqueue(msg, 0);
    return 0;
}

/*\
 *  Prepare for subscriptions and queries from another module.
 *  Returns 0 on success, -1 on failure.
\*/
int PEER_open(module_id)
    const int		module_id;
{
    Peer		p;

    for (p = peers; p; p = p->next)
	if (p->module_id == module_id)
	    return -1;
    p = HNMS_alloc(1, sizeof *p);
    p->module_id = module_id;
    p->next = peers;
    peers = p;
    return 0;
}

/*\
 *  Shut down peer-to-peer communications with another module.
 *  Returns 0 on success, -1 on failure.
\*/
int PEER_close(module_id)
    const int		module_id;
{
    Peer		p, prev;

    for (p = peers, prev = NULL; p; prev = p, p = p->next)
	if (p->module_id == module_id) {
	    if (prev)
		prev->next = p->next;
	    else
		peers = p->next;
	    if (p->query)
		VarBindList_free(p->query);
	    free(p);
	    break;
	}

}

/*\
 *  Send a Welcome.
\*/
int PEER_welcome(module_id)
    const int		module_id;
{
    Message		msg;

    msg = Message_create(HNMP_WELCOME);
    msg->to__module__id = module_id;
    HNMP_Message_enqueue(msg, module_id);
    return 0;
}

/*\
 *  Announce one object to another module.
\*/
int PEER_announce_object(hnms_id, module_id)
    const int		hnms_id;
    const int		module_id;
{
    VarBindList		vbl;
    PE			pe;
    Message		msg;
    int			class;
    unsigned int	time;

    vbl = 0;
    pe = OBJ_get(hnms_id, oid_hnmsObjUName, 0, 0, 0, 0, &time);
    VarBindList_set(&vbl, oid_hnmsObjUName, pe, 0, 0, 0, &time);
    msg = Message_create(HNMP_ANNOUNCE);
    Message_set(msg, HNMP_object_id, &hnms_id);
    Message_set(msg, HNMP_variables, vbl);
    HNMP_Message_enqueue(msg, module_id);
    VarBindList_free(vbl);
    return 0;
}

/*\
 *  Generate a microtask to announce one object -- later, when I have time.
\*/
void PEER_microannounce(hnms_id, module_id)
    const int		hnms_id;
    const int		*module_id;
{
    HNMS_create_microtask(PEER_announce_object, hnms_id, *module_id);
}

/*\
 *  Announce all objects to one module.  This can be CPU-intensive
 *  if we have a lot of objects, so we do this with the microtask
 *  queue to avoid eating up a lot of CPU at once with this task.
\*/
int PEER_announce_all_to_one(module_id)
    const int		module_id;
{
    OBJ_iterate(PEER_microannounce, &module_id);
    return 0;
}

/*\
 *  Announce one object to all modules.
\*/
int PEER_announce_one_to_all(hnms_id)
    const int		hnms_id;
{
    VarBindList		vbl;
    PE			pe;
    Message		msg;
    Peer		p;
    unsigned int	time;

    vbl = 0;
    pe = OBJ_get(hnms_id, oid_hnmsObjUName, 0, 0, 0, 0, &time);
    VarBindList_set(&vbl, oid_hnmsObjUName, pe, 0, 0, 0, &time);
    for (p = peers; p; p = p->next) {
	msg = Message_create(HNMP_ANNOUNCE);
	Message_set(msg, HNMP_object_id, &hnms_id);
	Message_set(msg, HNMP_variables, vbl);
	HNMP_Message_enqueue(msg, p->module_id);
    }
    VarBindList_free(vbl);
    return 0;
}

/*\
 *  Send a SendData for one object to one module.
\*/
int PEER_send_data(hnms_id, vbl, module_id)
    const int		hnms_id;
    const VarBindList	vbl;
    const int		module_id;
{
    Message		msg;

    msg = Message_create(HNMP_SEND_DATA);
    Message_set(msg, HNMP_object_id, &hnms_id);
    Message_set(msg, HNMP_variables, vbl);
    HNMP_Message_enqueue(msg, module_id);
}

/*\
 *  Send a SendRelations for one object to one module.
\*/
int PEER_send_relations(hnms_id, vbl, module_id)
    const int		hnms_id;
    const VarBindList	vbl;
    const int		module_id;
{
    Message		msg;

    msg = Message_create(HNMP_SEND_RELATIONS);
    Message_set(msg, HNMP_object_id, &hnms_id);
    Message_set(msg, HNMP_variables, vbl);
    HNMP_Message_enqueue(msg, module_id);
}

/*\
 *  Send a Delete for one object to all modules.
\*/
int PEER_delete_one_to_all(hnms_id)
    const int		hnms_id;
{
    Message		msg;
    Peer		p;

    for (p = peers; p; p = p->next) {
	msg = Message_create(HNMP_DELETE);
	Message_set(msg, HNMP_object_id, &hnms_id);
	HNMP_Message_enqueue(msg, p->module_id);
    }
    return 0;
}

/*\
 *  Send a UnsubData for one object to one module.
\*/
int PEER_unsub_object(hnms_id, module_id)
    const int		hnms_id;
    const int		module_id;
{
    Message		msg;

    msg = Message_create(HNMP_UNSUB_DATA);
    Message_set(msg, HNMP_object_id, &hnms_id);
    HNMP_Message_enqueue(msg, module_id);
    return 0;
}

/*\
 *  Set the given variables to be a module's subscription for a
 *  particluar object.  Returns 0 on success, -1 on failure.
\*/
int PEER_add_sub(module_id, hnms_id, vbl)
    const int		module_id;
    const int		hnms_id;
    const VarBindList	vbl;
{
    Peer		p;

    if (!hnms_id || !vbl)
	return -1;

    for (p = peers; p; p = p->next)
	if (p->module_id == module_id)
	    break;
    if (!p)
	return -1;

    if (p->subs[hnms_id])
	free(p->subs[hnms_id]);
    p->subs[hnms_id] = VarBindList_copylist(vbl);
    return 0;
}

/*\
 *  Drop a module's subscription to the specified object.
 *  Returns 0 on success, -1 on failure.
\*/
int PEER_drop_sub(module_id, hnms_id)
    const int		module_id;
    const int		hnms_id;
{
    Peer		p;

    if (!hnms_id)
	return -1;

    for (p = peers; p; p = p->next)
	if (p->module_id == module_id)
	    break;
    if (!p)
	return -1;

    if (p->subs[hnms_id]) {
	free(p->subs[hnms_id]);
	p->subs[hnms_id] = NULL;
    }
    return 0;
}

/*\
 *  Set a relations sub.
\*/
int PEER_add_rel_sub(module_id, hnms_id, vbl)
    const int		module_id;
    const int		hnms_id;
    const VarBindList	vbl;
{
    Peer		p;

    if (!hnms_id || !vbl)
	return -1;

    for (p = peers; p; p = p->next)
	if (p->module_id == module_id)
	    break;
    if (!p)
	return -1;

    if (p->rel_subs[hnms_id])
	free(p->rel_subs[hnms_id]);
    p->rel_subs[hnms_id] = VarBindList_copylist(vbl);
    return 0;
}

/*\
 *  Drop a relations sub.
\*/
int PEER_drop_rel_sub(module_id, hnms_id)
    const int		module_id;
    const int		hnms_id;
{
    Peer		p;

    if (!hnms_id)
	return -1;

    for (p = peers; p; p = p->next)
	if (p->module_id == module_id)
	    break;
    if (!p)
	return -1;

    if (p->rel_subs[hnms_id]) {
	free(p->rel_subs[hnms_id]);
	p->rel_subs[hnms_id] = NULL;
    }
    return 0;
}

/*\
 *  Get the VarBindList that comprises the peer module's subscription
 *  for the specified HNMS object.  Return the VarBindList if successful,
 *  NULL otherwise.
\*/
const VarBindList PEER_get_sub(module_id, hnms_id)
    const int		module_id;
    const int		hnms_id;
{
    Peer		p;

    for (p = peers; p; p = p->next)
	if (p->module_id == module_id)
	    break;
    if (!p)
	return NULL;

    return p->subs[hnms_id];
}

/*\
 *  Get the VarBindList that comprises this module's outgoing
 *  subscription of variables for the specified object.
\*/
const VarBindList PEER_get_sub_out(hnms_id)
    const int		hnms_id;
{
    return subs_out[hnms_id];
}

/*\
 *  Fill the subscriptions of all peer modules that are subscribed
 *  to variables on my objects.
\*/
int PEER_fill_subs()
{
    Peer		p;
    int			n;
    PE			objv_pe;
    unsigned int	objv_ts;
    VarBindList		v, vbl;

    PEER_fill_rel_subs();

    for (p = peers; p; p = p->next)
	for (n = 1; n <= MAX_OBJECTS; n++) {
	    vbl = NULL;
	    for (v = p->subs[n]; v; v = v->next) {
		objv_pe = OBJ_get(n, v->VarBind->name, 0, 0, 0, 0, &objv_ts);
		if (objv_pe &&
		    (objv_ts > (unsigned int) v->VarBind->timestamp->parm)) {
		    v->VarBind->timestamp->parm = objv_ts;
		    VarBindList_set(&vbl, v->VarBind->name, objv_pe,
				    0, 0, 0, &objv_ts);
		}
	    }
	    if (vbl) {
		PEER_send_data(n, vbl, p->module_id);
		VarBindList_free(vbl);
	    }
	}
    return 0;
}

/*\
 *  Fill relation subscriptions.
\*/
int PEER_fill_rel_subs()
{
    Peer		p;
    int			n;
    PE			objv_pe;
    unsigned int	objv_ts;
    VarBindList		v, vbl;

    for (p = peers; p; p = p->next)
	for (n = 1; n <= MAX_OBJECTS; n++) {
	    vbl = NULL;
	    for (v = p->rel_subs[n]; v; v = v->next) {
		objv_pe = OBJ_get(n, v->VarBind->name, 0, 0, 0, 0, &objv_ts);
		if (objv_pe &&
		    (objv_ts > (unsigned int) v->VarBind->timestamp->parm)) {
		    v->VarBind->timestamp->parm = objv_ts;
		    VarBindList_set(&vbl, v->VarBind->name, objv_pe,
				    0, 0, 0, &objv_ts);
		}
	    }
	    if (vbl) {
		PEER_send_relations(n, vbl, p->module_id);
		VarBindList_free(vbl);
	    }
	}
    return 0;
}

/*\
 *  Set this module's outgoing subscription for an object to be the
 *  given VarBindList.  Assign the subscription to any IO module.
 *  Pass in a VarBindList, which this function will keep (or destroy).
\*/
static void PEER_assign_obj(hnms_id, vbl)
    const int		hnms_id;
    VarBindList		vbl;
{
    int			my_module_id;
    int			owner;
    Peer		p;

    if (!hnms_id)
	return;

    if (subs_out[hnms_id])
	VarBindList_free(subs_out[hnms_id]);
    
    subs_out[hnms_id] = vbl;

    /*
     * Assign it to an IO module.
     */
}

/*\
 *  For the server module.  For each HNMS object, multiplex all
 *  subs into one sub that I will send to the IO module that
 *  owns that object.  For each outgoing subscription variable,
 *  Use the smallest interval of any incoming subscription.
 *  Preserve the original timestamp of each existing outgoing
 *  subscription variable.
\*/
int PEER_merge_subs()
{
    int			n;
    Peer		p;
    static PE		nullpe = NULL;
    VarBindList		v,vbl;
    int			i;
    unsigned int	ts;

    if (!nullpe)
	nullpe = pe_create_null();

    for (n = 1; n < MAX_OBJECTS; n++) {
	vbl = NULL;
	for (p = peers; p; p = p->next)
	    for (v = p->subs[n]; v; v = v->next) {
		if (oid_is_hnms(v->VarBind->name))
		    continue;
		i = 0;
		VarBindList_get(vbl, v->VarBind->name, 0, 0, 0, &i, 0);
		if (i == 0)
		    i = v->VarBind->interval;
		else
		    i = MIN(v->VarBind->interval, i);
		ts = 0;
		VarBindList_get(subs_out[n], v->VarBind->name,
				0, 0, 0, 0, &ts);
		VarBindList_set(&vbl, v->VarBind->name,
				nullpe, 0, 0, &i, &ts);
	    }
	PEER_assign_obj(n, vbl);
    }
    return 0;
}

/*\
 *  Print the subs in and out for a particular object.
\*/
void PEER_print_subs(hnms_id)
    int			hnms_id;
{
    Peer		p;

    for (p = peers; p; p = p->next) {
	printf("Subs in from module %d:\n", p->module_id);
	VarBindList_print(p->subs[hnms_id]);
	printf("Rel Subs in from module %d:\n", p->module_id);
	VarBindList_print(p->rel_subs[hnms_id]);
    }
    printf("Subs out:\n");
    VarBindList_print(subs_out[hnms_id]);
}
