/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
/*
 * Copyright (c) 2011-2016 Los Alamos National Security, LLC. All rights
 *                         reserved.
 * Copyright (c) 2011      UT-Battelle, LLC. All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#ifndef MCA_BTL_UGNI_ENDPOINT_H
#define MCA_BTL_UGNI_ENDPOINT_H

#include "btl_ugni.h"

enum mca_btl_ugni_endpoint_state_t {
    MCA_BTL_UGNI_EP_STATE_INIT = 0,
    MCA_BTL_UGNI_EP_STATE_START,
    MCA_BTL_UGNI_EP_STATE_RDMA,
    MCA_BTL_UGNI_EP_STATE_CONNECTING,
    MCA_BTL_UGNI_EP_STATE_CONNECTED
};
typedef enum mca_btl_ugni_endpoint_state_t mca_btl_ugni_endpoint_state_t;

struct mca_btl_ugni_smsg_mbox_t;

typedef struct mca_btl_base_endpoint_t {
    opal_list_item_t super;

    opal_proc_t *peer_proc;

    /** may need to lock recursively as the modex lookup could call opal_progress
     * and hence our progress function. if this changes modify this mutex to not
     * be recursive. also need to update the constructor function. */
    opal_recursive_mutex_t lock;
    mca_btl_ugni_endpoint_state_t state;

    opal_common_ugni_endpoint_t *common;

    mca_btl_ugni_module_t *btl;

    gni_ep_handle_t smsg_ep_handle;
    gni_ep_handle_t rdma_ep_handle;

    mca_btl_ugni_endpoint_attr_t remote_attr; /* TODO: UGH, remove this */

    struct mca_btl_ugni_smsg_mbox_t *mailbox;
    gni_mem_handle_t  rmt_irq_mem_hndl;


    opal_list_t frag_wait_list;
    bool wait_listed;
    /** protect against race on connection */
    bool dg_posted;

    int32_t smsg_progressing;

    int index;
} mca_btl_base_endpoint_t;

typedef mca_btl_base_endpoint_t  mca_btl_ugni_endpoint_t;
OBJ_CLASS_DECLARATION(mca_btl_ugni_endpoint_t);

int mca_btl_ugni_ep_connect_progress (mca_btl_ugni_endpoint_t *ep);
int mca_btl_ugni_ep_disconnect (mca_btl_ugni_endpoint_t *ep, bool send_disconnect);

static inline int mca_btl_ugni_init_ep (mca_btl_ugni_module_t *ugni_module,
                                        mca_btl_ugni_endpoint_t **ep,
                                        mca_btl_ugni_module_t *btl,
                                        opal_proc_t *peer_proc) {
    mca_btl_ugni_endpoint_t *endpoint;

    endpoint = OBJ_NEW(mca_btl_ugni_endpoint_t);
    assert (endpoint != NULL);

    endpoint->smsg_progressing = 0;
    endpoint->state = MCA_BTL_UGNI_EP_STATE_INIT;

    endpoint->btl = btl;
    endpoint->peer_proc = peer_proc;
    endpoint->index = opal_pointer_array_add (&ugni_module->endpoints, endpoint);

    *ep = endpoint;

    return OPAL_SUCCESS;
}

static inline void mca_btl_ugni_release_ep (mca_btl_ugni_endpoint_t *ep) {
    int rc;

    if (ep->common) {
        opal_mutex_lock (&ep->lock);

        rc = mca_btl_ugni_ep_disconnect (ep, false);
        if (OPAL_UNLIKELY(OPAL_SUCCESS != rc)) {
            BTL_VERBOSE(("btl/ugni error disconnecting endpoint"));
        }

        /* TODO -- Clear space at the end of the endpoint array */
        opal_pointer_array_set_item (&ep->btl->endpoints, ep->index, NULL);

        opal_mutex_unlock (&ep->lock);

        opal_common_ugni_endpoint_return (ep->common);
    }

    OBJ_RELEASE(ep);
}

static inline int mca_btl_ugni_check_endpoint_state (mca_btl_ugni_endpoint_t *ep) {
    int rc;

    if (OPAL_LIKELY(MCA_BTL_UGNI_EP_STATE_CONNECTED == ep->state)) {
        return OPAL_SUCCESS;
    }

    opal_mutex_lock (&ep->lock);

    switch (ep->state) {
    case MCA_BTL_UGNI_EP_STATE_INIT:
    case MCA_BTL_UGNI_EP_STATE_RDMA:
    case MCA_BTL_UGNI_EP_STATE_START:
        rc = mca_btl_ugni_ep_connect_progress (ep);
        if (OPAL_SUCCESS != rc) {
            break;
        }
    case MCA_BTL_UGNI_EP_STATE_CONNECTING:
        rc = OPAL_ERR_RESOURCE_BUSY;
        break;
    default:
        rc = OPAL_SUCCESS;
    }

    opal_mutex_unlock (&ep->lock);

    return rc;
}

static inline int mca_btl_ugni_ep_connect_rdma (mca_btl_base_endpoint_t *ep) {
    int rc;

    if (ep->state >= MCA_BTL_UGNI_EP_STATE_RDMA) {
        return OPAL_SUCCESS;
    }

    /* protect against re-entry from opal_progress */
    if (OPAL_UNLIKELY(MCA_BTL_UGNI_EP_STATE_START == ep->state)) {
        return OPAL_ERR_RESOURCE_BUSY;
    }

    ep->state = MCA_BTL_UGNI_EP_STATE_START;

    /* get the modex info for this endpoint and setup a ugni endpoint. this call may lead
     * to re-entry through opal_progress(). */
    rc = opal_common_ugni_endpoint_for_proc (ep->btl->device, ep->peer_proc, &ep->common);
    if (OPAL_SUCCESS != rc) {
        assert (0);
        return rc;
    }

    /* bind endpoint to remote address */
    rc = opal_common_ugni_ep_create (ep->common, ep->btl->rdma_local_cq, &ep->rdma_ep_handle);
    if (OPAL_UNLIKELY(OPAL_SUCCESS != rc)) {
        return rc;
    }

    ep->state = MCA_BTL_UGNI_EP_STATE_RDMA;

    return OPAL_SUCCESS;
}

static inline int mca_btl_ugni_check_endpoint_state_rdma (mca_btl_base_endpoint_t *ep) {
    int rc;
    if (OPAL_LIKELY(MCA_BTL_UGNI_EP_STATE_INIT < ep->state)) {
        return OPAL_SUCCESS;
    }

    opal_mutex_lock (&ep->lock);
    rc = mca_btl_ugni_ep_connect_rdma (ep);
    opal_mutex_unlock (&ep->lock);
    return rc;
}

static inline int mca_btl_ugni_wildcard_ep_post (mca_btl_ugni_module_t *ugni_module) {
    gni_return_t rc;

    BTL_VERBOSE(("posting wildcard datagram"));

    memset (&ugni_module->wc_local_attr, 0, sizeof (ugni_module->wc_local_attr));
    memset (&ugni_module->wc_remote_attr, 0, sizeof (ugni_module->wc_remote_attr));
    rc = GNI_EpPostDataWId (ugni_module->wildcard_ep, &ugni_module->wc_local_attr,
                            sizeof (ugni_module->wc_local_attr), &ugni_module->wc_remote_attr,
                            sizeof (ugni_module->wc_remote_attr), MCA_BTL_UGNI_CONNECT_WILDCARD_ID);

    return opal_common_rc_ugni_to_opal (rc);
}

#endif /* MCA_BTL_UGNI_ENDPOINT_H */
