/*
 * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
 *                         University Research and Technology
 *                         Corporation.  All rights reserved.
 * Copyright (c) 2004-2016 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart,
 *                         University of Stuttgart.  All rights reserved.
 * Copyright (c) 2004-2005 The Regents of the University of California.
 *                         All rights reserved.
 * Copyright (c) 2006-2021 Cisco Systems, Inc.  All rights reserved
 * Copyright (c) 2009      Sun Microsystems, Inc.  All rights reserved.
 * Copyright (c) 2021      IBM Corporation.  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include "ompi_config.h"
#include "ompi/communicator/communicator.h"
#include "ompi/request/grequest.h"
#include "ompi/mpi/fortran/base/fint_2_int.h"


/*
 * See the comment in the grequest destructor for the weird semantics
 * here.  If the request has been marked complete via a call to
 * MPI_GREQUEST_COMPLETE, actually release the object.  OTherwise,
 * just mark this object as "freed" so that a later call to
 * MPI_GREQUEST_COMPLETE will release it (!).
 *
 * Note that TEST* and WAIT* will call this function when a request
 * has been completed.
 */
static int ompi_grequest_free(ompi_request_t** req)
{
    OBJ_RELEASE(*req);
    *req = MPI_REQUEST_NULL;
    return OMPI_SUCCESS;
}

static int ompi_grequest_cancel(ompi_request_t* req, int flag)
{
    int rc = OMPI_SUCCESS;
    MPI_Fint ierr;
    ompi_fortran_logical_t fflag;
    ompi_grequest_t* greq = (ompi_grequest_t*)req;

    if (greq->greq_cancel.c_cancel != NULL) {
        if (greq->greq_funcs_are_c) {
            rc = greq->greq_cancel.c_cancel(greq->greq_state,
                                            REQUEST_COMPLETE(&greq->greq_base));
        } else {
            fflag = (ompi_fortran_logical_t) REQUEST_COMPLETE(&greq->greq_base);
            greq->greq_cancel.f_cancel((MPI_Aint*)greq->greq_state, &fflag, &ierr);
            rc = OMPI_FINT_2_INT(ierr);
        }
    }
    return rc;
}

static void ompi_grequest_construct(ompi_grequest_t* greq)
{
    greq->greq_base.req_free     = ompi_grequest_free;
    greq->greq_base.req_cancel   = ompi_grequest_cancel;
    greq->greq_base.req_type = OMPI_REQUEST_GEN;
    greq->greq_base.req_mpi_object.comm = &(ompi_mpi_comm_world.comm);
    /* Set the function pointers to C here; the F77 MPI API will
       override this value if the gen request was created from
       Fortran */
    greq->greq_funcs_are_c = true;
}

/*
 * MPI has some weird semantics with respect to generalized requests
 * -- different than all other MPI object types.  So we move some
 * cleanup stuff here to the destructor rather than in
 * greqeust_request_free -- mainly because the cleanup may be required
 * in two different places.
 *
 * Specifically, generalized requests can be completed (and therefore
 * released) the following ways:
 *
 * 1. Call to MPI_GREQUEST_COMPLETE and then a corresponding call to
 * some flavor of MPI_TEST* or MPI_WAIT*.  This will both complete the
 * requests and destroy the corresponding MPI generalized request
 * object.
 *
 * 2. Call MPI_REQUEST_FREE and then (!) -- with some other
 * still-valid copy of the handler -- call MPI_GREQUEST_COMPLETE.
 *
 * 3. Reverse the order of #2 -- call MPI_GREQUEST_COMPLETE and then
 * MPI_REQUEST_FREE.
 *
 * So any one of these functions may actually be the one that
 * de-allocates the back-end request object.  Hence, this is perfect
 * for our reference counting system -- so the call to the gen request
 * free_fn() is back here in the destructor, whenever the object is
 * actually freed.
 *
 * Hence, the following must occur before a grequest is freed:
 *
 * - ompi_grequest_complete() (i.e., GREQUEST_COMPLETE) is invoked
 * - ompi_grequest_free() is invoked
 *
 * Remember that ompi_grequest_free() is invoked by MPI_TEST* and
 * MPI_WAIT* when the request was previously marked as complete and
 * TEST* / WAIT* notified the user as such, and this function is also
 * invoked by REQUEST_FREE).  Hence, these two functions will *always*
 * be invoked, but the order in which they are invoked is up to the
 * user.  So this is a perfect opportunity for the OBJ_* reference
 * count system.  When we create an ompi_grequest_t in
 * ompi_grequest_start(), we both OBJ_NEW and OBJ_RETAIN it so that
 * its reference count goes to 0.  Then in ompi_grequest_complete()
 * and ompi_grequest_free(), we OBJ_RELEASE it.  Hence, when both of
 * them have RELEASEd -- regardless of the order in which the
 * functions were invoked, then the destructor is invoked and
 * everything is cleaned up (and we invoked the grequest free_fn).
 */
static void ompi_grequest_destruct(ompi_grequest_t* greq)
{
    if (greq->greq_free.c_free != NULL) {
        /* We were already putting query_fn()'s return value into
         * status.MPI_ERROR but for MPI_{Wait,Test}*.  If there's a
         * free callback to invoke, the standard says to use the
         * return value from free_fn() callback, too.
         */
        if (greq->greq_funcs_are_c) {
            greq->greq_base.req_status.MPI_ERROR =
                greq->greq_free.c_free(greq->greq_state);
        } else {
            MPI_Fint ierr;
            greq->greq_free.f_free((MPI_Aint*)greq->greq_state, &ierr);
            greq->greq_base.req_status.MPI_ERROR =
                OMPI_FINT_2_INT(ierr);
        }
    }

    OMPI_REQUEST_FINI(&greq->greq_base);
}


OBJ_CLASS_INSTANCE(
    ompi_grequest_t,
    ompi_request_t,
    ompi_grequest_construct,
    ompi_grequest_destruct);


int ompi_grequest_start(
    MPI_Grequest_query_function *gquery_fn,
    MPI_Grequest_free_function *gfree_fn,
    MPI_Grequest_cancel_function *gcancel_fn,
    void* gstate,
    ompi_request_t** request)
{
    ompi_grequest_t *greq = OBJ_NEW(ompi_grequest_t);
    if(greq == NULL) {
        return OMPI_ERR_OUT_OF_RESOURCE;
    }
    /* We call RETAIN here specifically to increase the refcount to 2.
       See comment before the destructor for an explanation. */
    OBJ_RETAIN(greq);

    greq->greq_base.req_state = OMPI_REQUEST_ACTIVE;
    greq->greq_state = gstate;
    greq->greq_query.c_query = gquery_fn;
    greq->greq_free.c_free = gfree_fn;
    greq->greq_cancel.c_cancel = gcancel_fn;
    greq->greq_base.req_status = ompi_status_empty;
    greq->greq_base.req_complete = REQUEST_PENDING;

    *request = &greq->greq_base;
    return OMPI_SUCCESS;
}


/*
 * Beware the odd semantics listed in MPI-2:8.2...  See the comment in
 * the grequest destructor.
 *
 * First do the normal stuff to complete the request (i.e., call
 * ompi_request_complete()).  Then, if this request object was
 * previously freed via MPI_REQUEST_FREE, release it.
 */
int ompi_grequest_complete(ompi_request_t *req)
{
    int rc;

    rc = ompi_request_complete(req, true);
    OBJ_RELEASE(req);
    return rc;
}


/*
 * Grequest queries are invoked in two places:
 *
 * 1. MPI_TEST* / MPI_WAIT*, when requests have completed.
 *
 * 2. MPI_REQUEST_GET_STATUS, when requests may or may not have
 * completed.
 *
 */
int ompi_grequest_invoke_query(ompi_request_t *request,
                               ompi_status_public_t *status)
{
    int rc = OMPI_SUCCESS;
    ompi_grequest_t *g = (ompi_grequest_t*) request;

    /* MPI-3 mandates that the return value from the query function
     * (i.e., the int return value from the C function or the ierr
     * argument from the Fortran function) must be returned to the
     * user. Thus, if the return of the query function is not MPI_SUCCESS
     * we will update the MPI_ERROR field. Otherwise, the MPI_ERROR
     * field is untouched (or left to the discretion of the query function).
     */
    if (NULL != g->greq_query.c_query) {
        if (g->greq_funcs_are_c) {
            rc = g->greq_query.c_query(g->greq_state, status);
        } else {
            /* request->req_status.MPI_ERROR was initialized to success
             * and it's meant to be unmodified in the case of callback
             * success, and set when callbacks return a failure.  But
             * if we leave fstatus uninitialized this sets
             * req_status.MPI_ERROR to whatever happened to be on the
             * stack at fstatus (f_query isn't supposed to directly set
             * its status.MPI_ERROR, according to the standard)
             *
             * So the Status_c2f below only really cares about transferring
             * the MPI_ERROR setting into fstatus so that when it's transferred
             * back in the f2c call, it has the starting value.
             */
            MPI_Fint ierr;
            MPI_Fint fstatus[sizeof(MPI_Status) / sizeof(int)];
            MPI_Status_c2f(status, fstatus);
            g->greq_query.f_query((MPI_Aint*)g->greq_state, fstatus, &ierr);
            MPI_Status_f2c(fstatus, status);
            rc = OMPI_FINT_2_INT(ierr);
        }
    }
    if( MPI_SUCCESS != rc ) {
        status->MPI_ERROR = rc;
    }
    return rc;
}

