/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil ; -*- */
/*
 *  (C) 2001 by Argonne National Laboratory.
 *      See COPYRIGHT in top-level directory.
 *
 * Portions of this code were written by Microsoft. Those portions are
 * Copyright (c) 2007 Microsoft Corporation. Microsoft grants
 * permission to use, reproduce, prepare derivative works, and to
 * redistribute to others. The code is licensed "as is." The User
 * bears the risk of using it. Microsoft gives no express warranties,
 * guarantees or conditions. To the extent permitted by law, Microsoft
 * excludes the implied warranties of merchantability, fitness for a
 * particular purpose and non-infringement.
 */

#include "mpiimpl.h"
#include "attr.h"
/*
 * Keyvals.  These are handled just like the other opaque objects in MPICH
 * The predefined keyvals (and their associated attributes) are handled 
 * separately, without using the keyval 
 * storage
 */

#ifndef MPID_KEYVAL_PREALLOC 
#define MPID_KEYVAL_PREALLOC 16
#endif

/* Preallocated keyval objects */
MPID_Keyval MPID_Keyval_direct[MPID_KEYVAL_PREALLOC] = { {0} };
MPIU_Object_alloc_t MPID_Keyval_mem = { 0, 0, 0, 0, MPID_KEYVAL, 
					    sizeof(MPID_Keyval), 
					    MPID_Keyval_direct,
					    MPID_KEYVAL_PREALLOC, };

#ifndef MPID_ATTR_PREALLOC 
#define MPID_ATTR_PREALLOC 32
#endif

/* Preallocated keyval objects */
MPID_Attribute MPID_Attr_direct[MPID_ATTR_PREALLOC] = { {0} };
MPIU_Object_alloc_t MPID_Attr_mem = { 0, 0, 0, 0, MPID_ATTR, 
					    sizeof(MPID_Attribute), 
					    MPID_Attr_direct,
					    MPID_ATTR_PREALLOC, };

/* Provides a way to trap all attribute allocations when debugging leaks. */
MPID_Attribute *MPID_Attr_alloc(void)
{
    MPID_Attribute *attr = (MPID_Attribute *)MPIU_Handle_obj_alloc(&MPID_Attr_mem);
    /* attributes don't have refcount semantics, but let's keep valgrind and
     * the debug logging pacified */
    MPIU_Assert(attr != NULL);
    MPIU_Object_set_ref(attr, 0);
    return attr;
}

void MPID_Attr_free(MPID_Attribute *attr_ptr)
{
    MPIU_Handle_obj_free(&MPID_Attr_mem, attr_ptr);
}

#undef FUNCNAME
#define FUNCNAME MPIR_Call_attr_delete
#undef FCNAME
#define FCNAME MPL_QUOTE(FUNCNAME)
/*
  This function deletes a single attribute.
  It is called by both the function to delete a list and attribute set/put 
  val.  Return the return code from the delete function; 0 if there is no
  delete function.

  Even though there are separate keyvals for communicators, types, and files,
  we can use the same function because the handle for these is always an int
  in MPICH.  

  Note that this simply invokes the attribute delete function.  It does not
  remove the attribute from the list of attributes.
*/
int MPIR_Call_attr_delete( int handle, MPID_Attribute *attr_p )
{
    int rc;
    int mpi_errno = MPI_SUCCESS;
    MPID_Keyval* kv = attr_p->keyval;

    if(kv->delfn.user_function == NULL)
        goto fn_exit;

    rc = kv->delfn.proxy(
                kv->delfn.user_function,
                handle,
                attr_p->keyval->handle,
                attr_p->attrType,
                (void *)(MPIU_Pint)attr_p->value,
                attr_p->keyval->extra_state
                );
    /* --BEGIN ERROR HANDLING-- */
    if(rc != 0){
#if MPICH_ERROR_MSG_LEVEL < MPICH_ERROR_MSG_ALL
	/* If rc is a valid error class, then return that.  
	   Note that it may be a dynamic error class */
	/* AMBIGUOUS: This is an ambiguity in the MPI standard: What is the
	   error value returned from the user-provided routine?  Particularly
	   with the MPI-2 feature of user-defined error codes, the 
	   user expectation is probably that the user-provided error code
	   is returned. */
	mpi_errno = rc;
#else
        mpi_errno = MPIR_Err_create_code(MPI_SUCCESS, MPIR_ERR_RECOVERABLE, FCNAME, __LINE__, MPI_ERR_OTHER, "**user", "**userdel %d", rc);
#endif
        goto fn_fail;
    }
    /* --END ERROR HANDLING-- */

  fn_exit:
    return mpi_errno;
  fn_fail:
    goto fn_exit;
}

/*
  This function copies a single attribute.
  It is called by the function to copy a list of attribute
  Return the return code from the copy function; MPI_SUCCESS if there is
  no copy function.

  Even though there are separate keyvals for communicators, types, and files,
  we can use the same function because the handle for these is always an int
  in MPICH.

  Note that this simply invokes the attribute copy function.
*/
#undef FUNCNAME
#define FUNCNAME MPIR_Call_attr_copy
#undef FCNAME
#define FCNAME MPL_QUOTE(FUNCNAME)
int MPIR_Call_attr_copy( int handle, MPID_Attribute *attr_p, void** value_copy, int* flag)
{
    int mpi_errno = MPI_SUCCESS;
    int rc;
    MPID_Keyval* kv = attr_p->keyval;

    if(kv->copyfn.user_function == NULL)
        goto fn_exit;

    rc = kv->copyfn.proxy(
                kv->copyfn.user_function,
                handle,
                attr_p->keyval->handle,
                attr_p->keyval->extra_state,
                attr_p->attrType,
                (void *)(MPIU_Pint)attr_p->value,
                value_copy,
                flag
                );

    /* --BEGIN ERROR HANDLING-- */
    if(rc != 0){
#if MPICH_ERROR_MSG_LEVEL < MPICH_ERROR_MSG_ALL
	mpi_errno = rc;
#else
        mpi_errno = MPIR_Err_create_code(MPI_SUCCESS, MPIR_ERR_RECOVERABLE, FCNAME, __LINE__, MPI_ERR_OTHER, "**user", "**usercopy %d", rc);
#endif
        goto fn_fail;
    }
    /* --END ERROR HANDLING-- */
fn_exit:
    return mpi_errno;
fn_fail:
    goto fn_exit;
}

#undef FUNCNAME
#define FUNCNAME MPIR_Attr_dup_list
#undef FCNAME
#define FCNAME MPL_QUOTE(FUNCNAME)
/* Routine to duplicate an attribute list */
int MPIR_Attr_dup_list( int handle, MPID_Attribute *old_attrs, 
			MPID_Attribute **new_attr )
{
    MPID_Attribute *p, *new_p, **next_new_attr_ptr=new_attr;
    void* new_value = NULL;
    int mpi_errno = MPI_SUCCESS;

    for(p = old_attrs; p != NULL; p = p->next)
    {
        /* call the attribute copy function (if any) */
        int flag = 0;
        mpi_errno = MPIR_Call_attr_copy(
                        handle,
                        p,
                        &new_value,
                        &flag
                        );

        if(mpi_errno != MPI_SUCCESS)
            goto fn_fail;

        if(!flag)
            continue;
        /* If flag was returned as true, then insert this attribute into the 
	   new list (new_attr) */

        /* duplicate the attribute by creating new storage, copying the
        attribute value, and invoking the copy function */
        new_p = MPID_Attr_alloc();
        /* --BEGIN ERROR HANDLING-- */
        if (!new_p){
            mpi_errno = MPIR_Err_create_code( MPI_SUCCESS, MPIR_ERR_RECOVERABLE, FCNAME, __LINE__, MPI_ERR_OTHER, "**nomem", 0 );
            goto fn_fail;
        }
        /* --END ERROR HANDLING-- */
        if(!*new_attr){
            *new_attr = new_p;
        }
        *(next_new_attr_ptr) = new_p;

        new_p->keyval = p->keyval;
        /* Remember that we need this keyval */
        MPIR_Keyval_add_ref(p->keyval);

        new_p->attrType         = p->attrType;
        new_p->pre_sentinal     = 0;
	/* FIXME: This is not correct in some cases (size(MPI_Aint)>
	 sizeof(MPIU_Pint)) */
        new_p->value            = (MPID_AttrVal_t)(MPIU_Pint)new_value;
        new_p->post_sentinal    = 0;
        new_p->next             = 0;

        next_new_attr_ptr = &(new_p->next);
    } /* for(;;) */

  fn_exit:
    return mpi_errno;
  fn_fail:
    goto fn_exit;
}

#undef FUNCNAME
#define FUNCNAME MPIR_Attr_delete_list
#undef FCNAME
#define FCNAME MPL_QUOTE(FUNCNAME)
/* Routine to delete an attribute list */
int MPIR_Attr_delete_list( int handle, MPID_Attribute **attr )
{
    MPID_Attribute *p, *new_p;
    int mpi_errno = MPI_SUCCESS;

    p = *attr;
    while (p) {
	/* delete the attribute by first executing the delete routine, if any,
	   determine the the next attribute, and recover the attributes 
	   storage */
	new_p = p->next;
	
	/* Check the sentinals first */
	/* --BEGIN ERROR HANDLING-- */
	if (p->pre_sentinal != 0 || p->post_sentinal != 0) {
	    MPIR_ERR_SET(mpi_errno,MPI_ERR_OTHER,"**attrsentinal");
	    /* We could keep trying to free the attributes, but for now
	       we'll just bag it */
	    return mpi_errno;
	}
	/* --END ERROR HANDLING-- */
	/* For this attribute, find the delete function for the 
	   corresponding keyval */
	/* Still to do: capture any error returns but continue to 
	   process attributes */
	mpi_errno = MPIR_Call_attr_delete( handle, p );

	/* We must also remove the keyval reference.  If the keyval
	   was freed earlier (reducing the refcount), the actual 
	   release and free will happen here.  We must free the keyval
	   even if the attr delete failed, as we then remove the 
	   attribute.
	*/
	{
	    int in_use;
	    /* Decrement the use of the keyval */
	    MPIR_Keyval_release_ref( p->keyval, &in_use);
	    if (!in_use) {
		MPIU_Handle_obj_free( &MPID_Keyval_mem, p->keyval );
	    }
	}
	
	MPIU_Handle_obj_free( &MPID_Attr_mem, p );
	
	p = new_p;
    }

    /* We must zero out the attribute list pointer or we could attempt to use it
       later.  This normally can't happen because the communicator usually
       disappears after a call to MPI_Comm_free.  But if the attribute keyval
       has an associated delete function that returns an error then we don't
       actually free the communicator despite having freed all the attributes
       associated with the communicator.

       This function is also used for Win and Type objects, but the idea is the
       same in those cases as well. */
    *attr = NULL;
    return mpi_errno;
}

int
MPIR_Attr_copy_c_proxy(
    MPI_Comm_copy_attr_function* user_function,
    int handle,
    int keyval,
    void* extra_state,
    MPIR_AttrType attrib_type,
    void* attrib,
    void** attrib_copy,
    int* flag
    )
{
    void *attrib_val = NULL;
    int ret;

    /* Make sure that the attribute value is delieverd as a pointer */
    if (MPIR_ATTR_KIND(attrib_type) == MPIR_ATTR_KIND(MPIR_ATTR_INT)){
        attrib_val = &attrib;
    }
    else{
        attrib_val = attrib;
    }

    /* user functions might call other MPI functions, so we need to
     * release the lock here. This is safe to do as GLOBAL is not at
     * all recursive in our implementation. */
    MPID_THREAD_CS_EXIT(GLOBAL, MPIR_THREAD_GLOBAL_ALLFUNC_MUTEX);
    ret = user_function(handle, keyval, extra_state, attrib_val, attrib_copy, flag);
    MPID_THREAD_CS_ENTER(GLOBAL, MPIR_THREAD_GLOBAL_ALLFUNC_MUTEX);

    return ret;
}


int
MPIR_Attr_delete_c_proxy(
    MPI_Comm_delete_attr_function* user_function,
    int handle,
    int keyval,
    MPIR_AttrType attrib_type,
    void* attrib,
    void* extra_state
    )
{
    void *attrib_val = NULL;
    int ret;

    /* Make sure that the attribute value is delieverd as a pointer */
    if (MPIR_ATTR_KIND(attrib_type) == MPIR_ATTR_KIND(MPIR_ATTR_INT))
        attrib_val = &attrib;
    else
        attrib_val = attrib;

    /* user functions might call other MPI functions, so we need to
     * release the lock here. This is safe to do as GLOBAL is not at
     * all recursive in our implementation. */
    MPID_THREAD_CS_EXIT(GLOBAL, MPIR_THREAD_GLOBAL_ALLFUNC_MUTEX);
    ret = user_function(handle, keyval, attrib_val, extra_state);
    MPID_THREAD_CS_ENTER(GLOBAL, MPIR_THREAD_GLOBAL_ALLFUNC_MUTEX);

    return ret;
}

/* FIXME: Missing routine description */
void
MPIR_Keyval_set_proxy(
    int keyval,
    MPID_Attr_copy_proxy copy_proxy,
    MPID_Attr_delete_proxy delete_proxy
    )
{
    MPID_Keyval*  keyval_ptr;
    MPID_Keyval_get_ptr( keyval, keyval_ptr );
    if(keyval_ptr == NULL)
        return;

    keyval_ptr->copyfn.proxy = copy_proxy;
    keyval_ptr->delfn.proxy = delete_proxy;
}
