/*
 *
 * Copyright 1998-1999, University of Notre Dame.
 * Authors: Jeffrey M. Squyres, Kinis L. Meyer with M. D. McNally 
 *          and Andrew Lumsdaine
 *
 * This file is part of the Notre Dame LAM implementation of MPI.
 *
 * You should have received a copy of the License Agreement for the
 * Notre Dame LAM implementation of MPI along with the software; see
 * the file LICENSE.  If not, contact Office of Research, University
 * of Notre Dame, Notre Dame, IN 46556.
 *
 * Permission to modify the code and to distribute modified code is
 * granted, provided the text of this NOTICE is retained, a notice that
 * the code was modified is included with the above COPYRIGHT NOTICE and
 * with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
 * file is distributed with the modified code.
 *
 * LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
 * By way of example, but not limitation, Licensor MAKES NO
 * REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
 * PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
 * OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
 * OR OTHER RIGHTS.  
 *
 * Additional copyrights may follow.
 *
 *	Ohio Trollius
 *	Copyright 1997 The Ohio State University
 *	RBD/NJN
 *
 *	$Id: c2cbuf.c,v 6.6 1999/07/28 00:31:59 jsquyres Exp $
 *
 *	Function:	- client-to-client message buffering
 */

#include <lam_config.h>

#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>

#if LAM_NEED_SYS_SELECT_H
#include <sys/select.h>
#endif

#include <all_hash.h>
#include <mpi.h>
#include <mpisys.h>
#include <portable.h>
#include <rpisys.h>
#include <t_types.h>
#include <terror.h>
#include <typical.h>

/*
 * global functions
 */
int			_cbuf_init();
void			_cbuf_end();
void			_cbuf_delete();
void			*_cbuf_append();
struct cbuf_msg		*_cbuf_find();

/*
 * external functions
 */
extern int4		next_prime();

/*
 * local functions
 */
static MPI_Comm		cid_2_comm();
static void		inc_ger_ctr();

/*
 * external variables
 */
extern fd_set		_tcp_disabled;		/* sockets disabled by GER */

/*
 * local variables
 */
static HASH		*buftbl = 0;		/* buffer hash table */

/*
 *	_cbuf_init
 *
 *	Function:	- initialize c2c buffers
 *	Returns:	- 0 or LAMERROR
 */
int
_cbuf_init()

{
	_cbuf_end();

	buftbl = ah_init(INITHASH, sizeof(struct cbuf_cid), INT4_MAX, 0);
	if (buftbl == 0) return(LAMERROR);

	return(0);
}

/*
 *	_cbuf_end
 *
 *	Function:	- destroy c2c buffers
 */
void
_cbuf_end()

{
	struct cbuf_cid	*p;			/* favourite pointer */
	
	if (buftbl == 0) return;
/*
 * Loop over all context IDs.
 */
	for (p = (struct cbuf_cid *) ah_top(buftbl); p;
			p = (struct cbuf_cid *) ah_next(buftbl, p)) {

		if (p->cb_envs) {
			al_free(p->cb_envs);
		}
	}

	ah_free(buftbl);
}

/*
 *	_cbuf_find
 *
 *	Function:	- find a matching message
 *	Accepts:	- ptr request envlope
 *	Returns:	- ptr message or NULL
 */
struct cbuf_msg *
_cbuf_find(rqenv)

struct c2c_envl	*rqenv;

{
	struct cbuf_cid	*pcid;			/* ptr CID entry */
	struct cbuf_msg	*pmsg = 0;		/* ptr message */
/*
 * Don't even bother hashing if the table is empty.
 */
	if (ah_count(buftbl) <= 0) {
		return(0);
	}

	pcid = (struct cbuf_cid *) ah_find(buftbl, rqenv->ce_cid);
	if (pcid && pcid->cb_envs) {
/*
 * Loop over list of messages finding first match.
 */
		pmsg = al_top(pcid->cb_envs);

		while (pmsg) {
			if (! _c2c_envl_cmp(&(pmsg->cm_env), rqenv)) {
				return(pmsg);
			}
			pmsg = al_next(pcid->cb_envs, pmsg);
		}
	}
	
	return(0);
}

/*
 *	_cbuf_delete
 *
 *	Function:	- remove a message
 *			- context ID entries are never deleted
 *	Accepts:	- message
 */
void
_cbuf_delete(msg)

struct cbuf_msg		*msg;

{
	struct cbuf_cid	*pcid;			/* ptr CID entry */

	pcid = (struct cbuf_cid *) ah_find(buftbl, msg->cm_env.ce_cid);
	if (pcid == 0 || pcid->cb_envs == 0) return;

	if (lam_ger) {
		inc_ger_ctr(pcid->cb_comm, msg->cm_env.ce_rank, -1);
	}

	/* Don't delete the buffer if it was a c2c ssend to ourselves,
           becuase in this special case, we just re-used the user's
           buffer */
	if (msg->cm_buf && 
	    (msg->cm_req == NULL || 
	     msg->cm_req->rq_rpi.c2c.cq_state != C2CSENDSELF)) {
		free(msg->cm_buf);
	}

	al_delete(pcid->cb_envs, msg);
}

/*
 *	_cbuf_append
 *
 *	Function:	- add new message to end of list
 *			- expand hash table if needed
 *	Accepts:	- message
 *	Returns:	- buffered message or NULL
 */
void *
_cbuf_append(msg)

struct cbuf_msg		*msg;

{
	struct cbuf_cid	*pcid;			/* ptr CID entry */
	struct cbuf_cid	cident;			/* CID entry */
	int		newsize;		/* expanded table size */

	pcid = (struct cbuf_cid *) ah_find(buftbl, msg->cm_env.ce_cid);
	if (pcid == 0) {
/*
 * Expand hash table if no more free entries.
 */
		if (ah_count(buftbl) == (newsize = ah_size(buftbl))) {
			newsize = next_prime(newsize + newsize);
			if (ah_expand(buftbl, newsize)) return(0);
		}
/*
 * Create new CID entry.
 */
		LAM_ZERO_ME(cident);
		cident.cb_cid = msg->cm_env.ce_cid;
		cident.cb_envs = 0;
		if (lam_ger) {
			cident.cb_comm = cid_2_comm(cident.cb_cid);
		}
		if (ah_insert(buftbl, (void *) &cident)) return(0);

		pcid = (struct cbuf_cid *)
			ah_find(buftbl, msg->cm_env.ce_cid);
		
		if (pcid == 0) {
			errno = EIMPOSSIBLE;
			return(0);
		}
	}
/*
 * Create message list if necessary.
 */
	if (!pcid->cb_envs) {

		pcid->cb_envs = al_init(sizeof(struct cbuf_msg), 0);
		if (!pcid->cb_envs) return(0);
	}
/*
 * Link message to end of list.
 */
	if (lam_ger) {
		inc_ger_ctr(pcid->cb_comm, msg->cm_env.ce_rank, 1);
	}

	return(al_append(pcid->cb_envs, msg));
}

/*
 *	cid_2_comm
 *
 *	Function:	- finds communicator from context ID
 *	Accepts:	- context ID
 *	Returns:	- communicator
 */
static MPI_Comm
cid_2_comm(cid)

int			cid;

{
	struct _comm	celem;
	MPI_Comm	comm;
	MPI_Comm	*pcomm;

	celem.c_contextid = cid;
	comm = &celem;
	
	pcomm = al_find(lam_comms, &comm);
	
	return((pcomm) ? *pcomm : MPI_COMM_NULL);
}

/*
 *	inc_ger_ctr
 *
 *	Function:	- increment GER counter
 *	Accepts:	- context ID
 *			- rank of process in communicator
 *			- increment
 */
static void
inc_ger_ctr(comm, rank, inc)

MPI_Comm		comm;
int			rank;
int			inc;

{
	struct c2c_proc	*ps;			/* c2c process */

	if (LAM_IS_INTER(comm)) {
		ps = &comm->c_rgroup->g_procs[rank]->p_rpi.c2c;
	} else {
		ps = &comm->c_group->g_procs[rank]->p_rpi.c2c;
	}

	ps->cp_nbfde += inc;
}
