/*
 * Copyright (C) by Argonne National Laboratory
 *     See COPYRIGHT in top-level directory
 */

#include "mpidimpl.h"

static int setupPortFunctions = 1;

#ifndef MPIDI_CH3_HAS_NO_DYNAMIC_PROCESS
static int MPIDI_Open_port(MPIR_Info *, char *);
static int MPIDI_Close_port(const char *);

/* Define the functions that are used to implement the port
 * operations */
static MPIDI_PortFns portFns = { MPIDI_Open_port,
				 MPIDI_Close_port,
				 MPIDI_Comm_accept,
				 MPIDI_Comm_connect };
#else
static MPIDI_PortFns portFns = { 0, 0, 0, 0 };
#endif

/*@
   MPID_Open_port - Open an MPI Port

   Input Arguments:
.  MPI_Info info - info

   Output Arguments:
.  char *port_name - port name

   Notes:

.N Errors
.N MPI_SUCCESS
.N MPI_ERR_OTHER
@*/
int MPID_Open_port(MPIR_Info *info_ptr, char *port_name)
{
    int mpi_errno=MPI_SUCCESS;

    MPIR_FUNC_ENTER;

    /* Check to see if we need to setup channel-specific functions
       for handling the port operations */
    if (setupPortFunctions) {
	MPIDI_CH3_PortFnsInit( &portFns );
	setupPortFunctions = 0;
    }

    /* The default for this function is MPIDI_Open_port.
       A channel may define its own function and set it in the 
       init check above; such a function may be named MPIDI_CH3_Open_port.
       In addition, not all channels can implement this operation, so
       those channels will set the function pointer to NULL */
    if (portFns.OpenPort) {
	mpi_errno = portFns.OpenPort( info_ptr, port_name );
	MPIR_ERR_CHECK(mpi_errno);
    }
    else {
	MPIR_ERR_SET(mpi_errno, MPI_ERR_OTHER, "**notimpl" );
    }

 fn_fail:
    MPIR_FUNC_EXIT;
    return mpi_errno;
}

/*@
   MPID_Close_port - Close port

Input Parameters:
.  port_name - Name of MPI port to close

   Notes:

.N Errors
.N MPI_SUCCESS
.N MPI_ERR_OTHER

@*/
int MPID_Close_port(const char *port_name)
{
    int mpi_errno=MPI_SUCCESS;

    MPIR_FUNC_ENTER;

    /* Check to see if we need to setup channel-specific functions
       for handling the port operations */
    if (setupPortFunctions) {
	MPIDI_CH3_PortFnsInit( &portFns );
	setupPortFunctions = 0;
    }

    /* The default for this function is 0 (no function).
       A channel may define its own function and set it in the 
       init check above; such a function may be named MPIDI_CH3_Close_port */
    if (portFns.ClosePort) {
	mpi_errno = portFns.ClosePort( port_name );
	MPIR_ERR_CHECK(mpi_errno);
    }
    else {
	MPIR_ERR_SET(mpi_errno, MPI_ERR_OTHER, "**notimpl" );
    }

 fn_fail:	
    MPIR_FUNC_EXIT;
    return mpi_errno;
}

int MPID_Comm_accept(const char * port_name, MPIR_Info * info, int root,
		     MPIR_Comm * comm, MPIR_Comm ** newcomm_ptr)
{
    int mpi_errno = MPI_SUCCESS;

    MPIR_FUNC_ENTER;

    /* Check to see if we need to setup channel-specific functions
       for handling the port operations */
    if (setupPortFunctions) {
	MPIDI_CH3_PortFnsInit( &portFns );
	setupPortFunctions = 0;
    }

    /* A channel may define its own function and set it in the 
       init check above; such a function may be named MPIDI_CH3_Comm_accept.
       If the function is null, we signal a not-implemented error */
    if (portFns.CommAccept) {
	mpi_errno = portFns.CommAccept( port_name, info, root, comm, 
					newcomm_ptr );
	MPIR_ERR_CHECK(mpi_errno);
    }
    else {
	MPIR_ERR_SET(mpi_errno, MPI_ERR_OTHER, "**notimpl" );
    }

 fn_fail:
    MPIR_FUNC_EXIT;
    return mpi_errno;
}

int MPID_Comm_connect(const char * port_name, MPIR_Info * info, int root,
		      MPIR_Comm * comm, MPIR_Comm ** newcomm_ptr)
{
    int mpi_errno=MPI_SUCCESS;

    MPIR_FUNC_ENTER;

    /* Check to see if we need to setup channel-specific functions
       for handling the port operations */
    if (setupPortFunctions) {
	MPIDI_CH3_PortFnsInit( &portFns );
	setupPortFunctions = 0;
    }

    /* A channel may define its own function and set it in the 
       init check above; such a function may be named MPIDI_CH3_Comm_connect.
       If the function is null, we signal a not-implemented error */
    if (portFns.CommConnect) {
	mpi_errno = portFns.CommConnect( port_name, info, root, comm, 
					 newcomm_ptr );
	MPIR_ERR_CHECK(mpi_errno);
    }
    else {
	MPIR_ERR_SET(mpi_errno, MPI_ERR_OTHER, "**notimpl" );
    }

 fn_fail:
    MPIR_FUNC_EXIT;
    return mpi_errno;
}

/* ------------------------------------------------------------------------- */
#ifndef MPIDI_CH3_HAS_NO_DYNAMIC_PROCESS

/*
 * Here are the routines that provide some of the default implementations
 * for the Port routines.
 *
 * MPIDI_Open_port - creates a port "name" that includes a tag value that
 * is used to separate different MPI Port values.  That tag value is
 * extracted with MPIDI_GetTagFromPort
 * MPIDI_GetTagFromPort - Routine to return the tag associated with a port.
 *
 * The port_name_tag is used in the connect and accept messages that 
 * are used in the connect/accept protocol that is implemented in the
 * ch3u_port.c file.
 */

#define MPIDI_CH3I_PORT_NAME_TAG_KEY "tag"

/* Though the port_name_tag_mask itself is an int, we can only have as
 * many tags as the context_id space can support. */
static int port_name_tag_mask[MPIR_MAX_CONTEXT_MASK] = { 0 };

static int get_port_name_tag(int * port_name_tag)
{
    int i, j;
    int mpi_errno = MPI_SUCCESS;

    for (i = 0; i < MPIR_MAX_CONTEXT_MASK; i++)
	if (port_name_tag_mask[i] != ~0)
	    break;

    if (i < MPIR_MAX_CONTEXT_MASK) {
	/* Found a free tag. port_name_tag_mask[i] is not fully used
	 * up. */

	/* OR the mask value with powers of two. If the OR value is
	 * the same as the original value, then it means that the
	 * OR'ed bit was originally 1 (used); otherwise, it was
	 * originally 0 (free). */
	for (j = 0; j < (8 * sizeof(int)); j++) {
	    if ((port_name_tag_mask[i] | (1U << ((8 * sizeof(int)) - j - 1))) !=
		port_name_tag_mask[i]) {
		/* Mark the appropriate bit as used and return that */
		port_name_tag_mask[i] |= (1U << ((8 * sizeof(int)) - j - 1));
		*port_name_tag = ((i * 8 * (int)sizeof(int)) + j);
		goto fn_exit;
	    }
	}
    }
    else {
	goto fn_fail;
    }

fn_exit:
    return mpi_errno;

fn_fail:
    /* Everything is used up */
    *port_name_tag = -1;
    mpi_errno = MPI_ERR_OTHER;
    goto fn_exit;
}

static void free_port_name_tag(int tag)
{
    int idx, rem_tag;

    idx = tag / ((int)sizeof(int) * 8);
    rem_tag = tag - (int)(idx * sizeof(int) * 8);

    port_name_tag_mask[idx] &= ~(1U << ((8 * sizeof(int)) - 1 - rem_tag));
}

/*
 * MPIDI_Open_port()
 */
static int MPIDI_Open_port(MPIR_Info *info_ptr, char *port_name)
{
    int mpi_errno = MPI_SUCCESS;
    int str_errno = MPL_SUCCESS;
    int len;
    int port_name_tag = 0; /* this tag is added to the business card,
                              which is then returned as the port name */
    int myRank = MPIR_Process.rank;

    MPIR_FUNC_ENTER;

    mpi_errno = get_port_name_tag(&port_name_tag);
    MPIR_ERR_CHKANDJUMP(mpi_errno,mpi_errno,MPI_ERR_OTHER,"**argstr_port_name_tag");

    len = MPI_MAX_PORT_NAME;
    str_errno = MPL_str_add_int_arg(&port_name, &len,
                                     MPIDI_CH3I_PORT_NAME_TAG_KEY, port_name_tag);
    MPIR_ERR_CHKANDJUMP(str_errno, mpi_errno, MPI_ERR_OTHER, "**argstr_port_name_tag");

    /* This works because Get_business_card uses the same MPL_str_xxx
       functions as above to add the business card to the input string */
    /* FIXME: We should instead ask the mpid_pg routines to give us
       a connection string. There may need to be a separate step to 
       restrict us to a connection information that is only valid for
       connections between processes that are started separately (e.g.,
       may not use shared memory).  We may need a channel-specific 
       function to create an exportable connection string.  */
    mpi_errno = MPIDI_CH3_Get_business_card(myRank, port_name, len);
    MPL_DBG_MSG_FMT(MPIDI_CH3_DBG_OTHER, VERBOSE, (MPL_DBG_FDEST, "port_name = %s", port_name));

    mpi_errno = MPIDI_CH3I_Port_init(port_name_tag);

fn_exit:
    MPIR_FUNC_EXIT;
    return mpi_errno;
fn_fail:
    goto fn_exit;
}

/*
 * MPIDI_Close_port()
 */
static int MPIDI_Close_port(const char *port_name)
{
    int mpi_errno = MPI_SUCCESS;
    int port_name_tag;

    MPIR_FUNC_ENTER;

    mpi_errno = MPIDI_GetTagFromPort(port_name, &port_name_tag);
    MPIR_ERR_CHKANDJUMP(mpi_errno, mpi_errno, MPI_ERR_OTHER,"**argstr_port_name_tag");

    free_port_name_tag(port_name_tag);

    mpi_errno = MPIDI_CH3I_Port_destroy(port_name_tag);

fn_exit:
    MPIR_FUNC_EXIT;
    return mpi_errno;
fn_fail:
    goto fn_exit;
}

/*
 * The connect and accept routines use this routine to get the port tag
 * from the port name.
 */
int MPIDI_GetTagFromPort( const char *port_name, int *port_name_tag )
{
    int mpi_errno = MPI_SUCCESS;
    int str_errno = MPL_SUCCESS;

    str_errno = MPL_str_get_int_arg(port_name, MPIDI_CH3I_PORT_NAME_TAG_KEY,
                                     port_name_tag);
    MPIR_ERR_CHKANDJUMP(str_errno, mpi_errno, MPI_ERR_OTHER, "**argstr_no_port_name_tag");

 fn_exit:
    return mpi_errno;
 fn_fail:
    goto fn_exit;
}

#endif
