/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
/*
 * Copyright (c) 2011-2012 Los Alamos National Security, LLC. All rights
 *                         reserved.
 *               2014      Mellanox Technologies, Inc.
 *                         All rights reserved.
 * Copyright (c) 2015 Intel, Inc.  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 *
 */

#include "orte_config.h"
#include "orte/types.h"
#include "opal/types.h"

#include "orte/mca/errmgr/errmgr.h"
#include "orte/runtime/orte_globals.h"
#include "orte/util/name_fns.h"
#include "orte/util/show_help.h"

#include "math.h"

#include "oob_ud_component.h"

#define min(a,b) ((a) < (b) ? (a) : (b))

/* Caller MUST hold the matching lock before calling */
static inline int mca_oob_ud_find_recv (opal_list_t *list, const orte_process_name_t name,
                                        const int tag, mca_oob_ud_req_t **req)
{
    opal_list_item_t *item;
    int rc = ORTE_ERR_NOT_FOUND;

    *req = NULL;

    OPAL_THREAD_LOCK(&mca_oob_ud_component.ud_match_lock);

    for (item = opal_list_get_first (list) ; item != opal_list_get_end (list) ;
         item = opal_list_get_next (item)) {
        mca_oob_ud_req_t *recv_req = (mca_oob_ud_req_t *) item;

        opal_output_verbose(15, orte_oob_base_framework.framework_output,
                             "%s oob:ud:find_recv matching against "
                             "peer: %s, tag: %d", ORTE_NAME_PRINT(ORTE_PROC_MY_NAME),
                             ORTE_NAME_PRINT(&recv_req->req_origin), recv_req->req_tag);

        if (OPAL_EQUAL == opal_dss.compare (&name, &recv_req->req_origin, ORTE_NAME) &&
            tag == recv_req->req_tag) {
            *req = recv_req;
            rc = ORTE_SUCCESS;
            break;
        }
    }

    opal_output_verbose(15, orte_oob_base_framework.framework_output,
                         "%s oob:ud:find_recv %sfound",
                         ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), ORTE_SUCCESS != rc ? "not " : "");


    OPAL_THREAD_UNLOCK(&mca_oob_ud_component.ud_match_lock);

    if (ORTE_SUCCESS == rc) {
        mca_oob_ud_req_append_to_list (*req, NULL);
    }

    return rc;
}

int mca_oob_ud_get_recv_req (const orte_process_name_t name, const int tag,
                             mca_oob_ud_req_t **reqp, bool iovec_used) {
    mca_oob_ud_req_t *req;

    opal_output_verbose(15, orte_oob_base_framework.framework_output,
                         "%s oob:ud:get_recv_req create receive request against: %s, tag: %d",
                         ORTE_NAME_PRINT(ORTE_PROC_MY_NAME),
                         ORTE_NAME_PRINT(&name), tag);

    *reqp = req = OBJ_NEW(mca_oob_ud_req_t);

    req->req_origin       = name;
    req->req_tag          = tag;
    req->req_channel      = ORTE_RML_INVALID_CHANNEL_NUM;
    req->req_seq_num      = 0;
    /* this receive was not expected */
    req->type             = MCA_OOB_UD_REQ_RECV;

    /* let mca_oob_ud_recv_alloc alloc memory for the receive */
    if (iovec_used) {
        req->req_data.iov.uiov   = calloc (1, sizeof (struct iovec));
        req->req_data_type = MCA_OOB_UD_REQ_IOV;
    } else {
        req->req_data_type = MCA_OOB_UD_REQ_BUF;
    }
    req->req_data.iov.count  = 1;

    return ORTE_SUCCESS;
}

static inline int mca_oob_ud_find_active_recv (const orte_process_name_t name, const int tag,
                                               mca_oob_ud_req_t **req) {
    opal_output_verbose(15, orte_oob_base_framework.framework_output,
                         "%s oob:ud:recv_match active receive request "
                         "against: %s, tag: %d", ORTE_NAME_PRINT(ORTE_PROC_MY_NAME),
                         ORTE_NAME_PRINT(&name), tag);

    return mca_oob_ud_find_recv (&mca_oob_ud_component.ud_active_recvs, name, tag, req);
}

static void mca_oob_ud_recv_try_to (int fd, short event, void *data)
{
    (void) mca_oob_ud_recv_try ((mca_oob_ud_req_t *) data);
}

int mca_oob_ud_recv_try (mca_oob_ud_req_t *recv_req)
{
    int rc, data_len;
    int wr_count, sge_count, wr_index, sge_index, iov_index;
    unsigned int iov_left, iov_offset, packet_size;
    const unsigned int mtu = recv_req->req_mtu;
    struct timeval aquire_timeout = {0, 500000};
    mca_oob_ud_msg_t *rep_msg = NULL;

    opal_output_verbose(10, orte_oob_base_framework.framework_output,
                         "%s oob:ud:recv_try receiving from %s. recv_req = %p. rem ctx = %p",
                         ORTE_NAME_PRINT(ORTE_PROC_MY_NAME),
                         ORTE_NAME_PRINT(&recv_req->req_peer->peer_name),
                         (void *)recv_req, (void *)recv_req->req_rem_ctx);

    do {
        if (NULL == recv_req->req_qp) {
            rc = mca_oob_ud_qp_data_aquire (recv_req->req_port, &recv_req->req_qp);
            if (ORTE_SUCCESS != rc) {
                break;
            }
        }

        (void) mca_oob_ud_qp_purge (recv_req->req_qp);

        rc = mca_oob_ud_msg_get (recv_req->req_port, recv_req, &recv_req->req_port->listen_qp,
                                 recv_req->req_peer, NULL, &rep_msg);
        if (ORTE_SUCCESS != rc) {
            break;
        }

        if (MCA_OOB_UD_REQ_IOV == recv_req->req_data_type) {
            if (NULL == recv_req->req_data.iov.mr) {
                /* allocate space for memory registers */
                recv_req->req_data.iov.mr = (struct ibv_mr **) calloc (recv_req->req_data.iov.count, sizeof (struct ibv_mr *));
                if (NULL == recv_req->req_data.iov.mr) {
                    ORTE_ERROR_LOG(ORTE_ERR_OUT_OF_RESOURCE);
                    rc = ORTE_ERR_OUT_OF_RESOURCE;
                    break;
                }
            }

            rc = mca_oob_ud_register_iov (recv_req->req_data.iov.uiov, recv_req->req_data.iov.count,
                                          recv_req->req_data.iov.mr, recv_req->req_port->device->ib_pd,
                                          mtu, &sge_count, &wr_count, &data_len);

            if (ORTE_SUCCESS != rc) {
                break;
            }
        } else {
            data_len = recv_req->req_data.buf.size;
            rc = mca_oob_ud_register_buf (recv_req->req_data.buf.p, recv_req->req_data.buf.size,
                                          &recv_req->req_data.buf.mr, recv_req->req_port->device->ib_pd,
                                          mtu, &sge_count, &wr_count);

            if (ORTE_SUCCESS != rc) {
                break;
            }
        }

        data_len = min(data_len, recv_req->req_rem_data_len);
        if (data_len < recv_req->req_rem_data_len) {
            opal_output_verbose(5, orte_oob_base_framework.framework_output,
                     "%s oob:ud:recv_try receive buffers are not big. this is probably an error condition."
                     "data_len = %d, recv_req->req_rem_data_len = %d.",
                     ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), data_len, recv_req->req_rem_data_len);
            rc = ORTE_ERR_BAD_PARAM;
            break;
        }

        wr_count = (data_len + mtu - 1) / mtu;
        sge_count += wr_count;

        opal_output_verbose(5, orte_oob_base_framework.framework_output,
                             "%s oob:ud:recv_try receiving %d bytes in %d "
                             "work requests, %d sges", ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), data_len,
                             wr_count, sge_count);

        recv_req->req_packet_count = wr_count;

        if (NULL == recv_req->req_wr.recv) {
            /* allocate work requests */
            recv_req->req_wr.recv  = (struct ibv_recv_wr *) calloc (wr_count, sizeof (struct ibv_recv_wr));
            if (NULL == recv_req->req_wr.recv) {
                ORTE_ERROR_LOG(ORTE_ERR_OUT_OF_RESOURCE);
                rc = ORTE_ERR_OUT_OF_RESOURCE;
                break;
            }
        }

        if (NULL == recv_req->req_sge) {
            /* allocate scatter-gather lists. we need more to hold the grh */
            recv_req->req_sge = (struct ibv_sge *) calloc (sge_count, sizeof (struct ibv_sge));
            if (NULL == recv_req->req_sge) {
                ORTE_ERROR_LOG(ORTE_ERR_OUT_OF_RESOURCE);
                rc = ORTE_ERR_OUT_OF_RESOURCE;
                break;
            }
        }

        if (NULL == recv_req->req_grh) {
            /* allocate grh buffers */
            recv_req->req_grh = (struct ibv_grh *) calloc (wr_count, sizeof (struct ibv_grh));
            if (NULL == recv_req->req_grh) {
                ORTE_ERROR_LOG(ORTE_ERR_OUT_OF_RESOURCE);
                rc = ORTE_ERR_OUT_OF_RESOURCE;
                break;
            }
        }

        if (NULL == recv_req->req_grh_mr) {
            /* register grh buffers */
            recv_req->req_grh_mr = ibv_reg_mr (recv_req->req_port->device->ib_pd, recv_req->req_grh,
                                               wr_count * sizeof (struct ibv_grh),
                                               IBV_ACCESS_LOCAL_WRITE);
            if (NULL == recv_req->req_grh_mr) {
                orte_show_help("help-oob-ud.txt", "reg-mr-failed", true,
                       orte_process_info.nodename, recv_req->req_grh,
                       wr_count * sizeof (struct ibv_grh), strerror(errno));
                /* could not register memory */
                rc = ORTE_ERR_OUT_OF_RESOURCE;
                break;
            }
        }

        rc = ORTE_SUCCESS;

        if (MCA_OOB_UD_REQ_IOV == recv_req->req_data_type) {
            iov_left   = recv_req->req_data.iov.uiov[0].iov_len;
            iov_offset = 0;
            iov_index  = 0;

            opal_output_verbose(5, orte_oob_base_framework.framework_output,
                                 "%s oob:ud: recv_req->req_data.iov.uiov[0].iov_len = %d",
                                 ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), (int)recv_req->req_data.iov.uiov[0].iov_len);

            for (wr_index = 0, sge_index = 0 ; wr_index < wr_count ; ++wr_index) {
                int sge_first = sge_index;

                packet_size = 0;

                /* grh */
                mca_oob_ud_fill_sge(recv_req->req_sge + sge_index++,
                                    recv_req->req_grh + wr_index,
                                    sizeof (struct ibv_grh),
                                    recv_req->req_grh_mr->lkey);

                do {
                    int to_recv = min (iov_left, mtu - packet_size);

                    mca_oob_ud_fill_sge(recv_req->req_sge + sge_index++,
                                        (char *)recv_req->req_data.iov.uiov[iov_index].iov_base + iov_offset,
                                        to_recv, recv_req->req_data.iov.mr[iov_index]->lkey);

                    iov_offset += to_recv;
                    iov_left   -= to_recv;
                    packet_size += to_recv;

                    if (0 == iov_left) {
                        iov_index++;
                        iov_offset = 0;

                        if (iov_index < recv_req->req_data.iov.count) {
                            iov_left = recv_req->req_data.iov.uiov[iov_index].iov_len;
                        }
                    }
                } while ((packet_size < mtu) && (iov_left > 0));

                mca_oob_ud_fill_recv_wr(recv_req->req_wr.recv + wr_index,
                        recv_req->req_sge + sge_first,
                        sge_index - sge_first);

                if (wr_index + 1 < wr_count) {
                    recv_req->req_wr.recv[wr_index].next = recv_req->req_wr.recv + wr_index + 1;
                }
            }
        } else {
            unsigned int buffer_left   = recv_req->req_data.buf.size;
            unsigned int buffer_offset = 0;

            opal_output_verbose(5, orte_oob_base_framework.framework_output,
                                 "%s oob:ud:recv_try recv_req->req_data.buf.size = %d",
                                 ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), recv_req->req_data.buf.size);

            for (wr_index = 0, sge_index = 0 ; wr_index < wr_count ; ++wr_index) {
                int sge_first = sge_index;

                packet_size = 0;

                /* grh */
                mca_oob_ud_fill_sge(recv_req->req_sge + sge_index++,
                                    recv_req->req_grh + wr_index,
                                    sizeof (struct ibv_grh),
                                    recv_req->req_grh_mr->lkey);

                do {
                    int to_recv = min (buffer_left, mtu - packet_size);

                    mca_oob_ud_fill_sge(recv_req->req_sge + sge_index++,
                                        (char *)recv_req->req_data.buf.p + buffer_offset,
                                        to_recv, recv_req->req_data.buf.mr->lkey);

                    buffer_offset += to_recv;
                    buffer_left   -= to_recv;
                    packet_size += to_recv;
                } while ((packet_size < mtu) && (buffer_left > 0));

                mca_oob_ud_fill_recv_wr(recv_req->req_wr.recv + wr_index,
                    recv_req->req_sge + sge_first,
                    sge_index - sge_first);

                if (wr_index + 1 < wr_count) {
                    recv_req->req_wr.recv[wr_index].next = recv_req->req_wr.recv + wr_index + 1;
                }
            }
        }

        rc = mca_oob_ud_qp_post_recv (recv_req->req_qp, recv_req->req_wr.recv);
        if (ORTE_SUCCESS != rc) {
            break;
        }

        opal_output_verbose(5, orte_oob_base_framework.framework_output,
                             "%s oob:ud:recv_try posting reply message",
                             ORTE_NAME_PRINT(ORTE_PROC_MY_NAME));

        /* ok, we have a data queue pair */
        rep_msg->hdr->msg_type = MCA_OOB_UD_MSG_REPLY;
        rep_msg->hdr->msg_lcl_ctx = recv_req->req_rem_ctx;
        rep_msg->hdr->msg_rem_ctx = recv_req;

        rep_msg->hdr->msg_data.rep.qpn = recv_req->req_qp->ib_qp->qp_num;
        rep_msg->hdr->msg_data.rep.data_len = data_len;
        rep_msg->hdr->msg_data.rep.mtu = mtu;

        rc = mca_oob_ud_msg_post_send (rep_msg);

        /* post send already returned the message */
        rep_msg = NULL;
    } while (0);

    if (ORTE_ERR_TEMP_OUT_OF_RESOURCE == rc) {
        mca_oob_ud_req_timer_set (recv_req, &aquire_timeout, 1, mca_oob_ud_recv_try_to);
        rc = ORTE_SUCCESS;
    }

    if (ORTE_SUCCESS != rc) {
        /* bad stuff happened */
        mca_oob_ud_req_complete (recv_req, rc);

        if (mca_oob_ud_req_is_in_list(recv_req, &mca_oob_ud_component.ud_active_recvs)) {
            opal_list_remove_item (&mca_oob_ud_component.ud_active_recvs, (opal_list_item_t *) recv_req);
        }
        OBJ_RELEASE(recv_req);
        return rc;
    }

    recv_req->state = MCA_OOB_UD_REQ_ACTIVE;

    return rc;
}

int mca_oob_ud_recv_complete (mca_oob_ud_req_t *recv_req)
{
    mca_oob_ud_msg_t *dataok;
    int i, j, rc = ORTE_SUCCESS;
    uint32_t expected;
    bool error = false, out_of_order = false;
#if defined(HAVE_VALGRIND)
    int iov_index;
#endif

    opal_output_verbose(5, orte_oob_base_framework.framework_output,
                         "%s oob:ud:recv_complete req = %p",
                         ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), (void *) recv_req);

    if (false == recv_req->req_is_eager) {
        for (i = 0, expected = 0 ; i < recv_req->req_packet_count ; ) {
            struct ibv_wc wc[10];

            rc = ibv_poll_cq (recv_req->req_qp->ib_recv_cq, 10, wc);
            for (j = 0 ; j < rc ; ++j) {
                if (wc[j].imm_data != expected) {
                    out_of_order = true;
                }
                if (IBV_WC_SUCCESS != wc[j].status) {
                    error = true;
                }

                opal_output_verbose(5, orte_oob_base_framework.framework_output,
                                     "%s oob:ud:recv_complete wc status = %d. imm data = %u. len = %d",
                                     ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), wc[j].status, wc[j].imm_data,
                                     wc[j].byte_len);

                expected++;
            }

            if (rc <= 0) {
                break;
            }

            i += rc;
        }

        if (i != recv_req->req_packet_count || error || out_of_order) {
            /* retry */
            recv_req->state = MCA_OOB_UD_REQ_PENDING;

            opal_output_verbose(5, orte_oob_base_framework.framework_output,
                                 "%s oob:ud:recv_complete receive incomplete. error: %d, "
                                 "out_of_order: %d packets: %d/%d. rc = %d, errno = %d.",
                                 ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), error, out_of_order, i,
                                 recv_req->req_packet_count, rc, errno);
            mca_oob_ud_recv_try (recv_req);

            return ORTE_SUCCESS;
        }

        opal_output_verbose(10, orte_oob_base_framework.framework_output,
                             "%s oob:ud:recv_complete data received ok!",
                             ORTE_NAME_PRINT(ORTE_PROC_MY_NAME));

        /* send data ok and wait for ack */
        rc = mca_oob_ud_msg_get (recv_req->req_port, recv_req, &recv_req->req_port->listen_qp,
                                 recv_req->req_peer, false, &dataok);
        if (ORTE_SUCCESS != rc) {
            return rc;
        }

        dataok->hdr->msg_type = MCA_OOB_UD_MSG_DATA_OK;
        dataok->hdr->msg_lcl_ctx = recv_req->req_rem_ctx;

        rc = mca_oob_ud_msg_post_send (dataok);
        if (ORTE_SUCCESS != rc) {
            return rc;
        }
    }

#if defined(HAVE_VALGRIND)
    for (iov_index = 0 ; iov_index < recv_req->req_count ; ++iov_index) {
        VALGRIND_MAKE_MEM_DEFINED(recv_req->req_uiov[iov_index].iov_base,
                                  recv_req->req_uiov[iov_index].iov_len);
    }
#endif

    mca_oob_ud_req_complete (recv_req, rc);

    return ORTE_SUCCESS;
}

int mca_oob_ud_recv_match_send (mca_oob_ud_port_t *port, mca_oob_ud_peer_t *peer, mca_oob_ud_msg_hdr_t *msg_hdr,
                                mca_oob_ud_req_t **reqp)
{
    char *data = (msg_hdr->msg_data.req.data_follows ? (char *)(msg_hdr + 1) : NULL);
    mca_oob_ud_req_t *req;
    int rc, i;

    *reqp = NULL;

    opal_output_verbose(10, orte_oob_base_framework.framework_output,
                         "%s oob:ud:recv_incoming_send matching incoming "
                         "send from peer %s with tag %d (data_follows = %d, data = %p, iovec_use = %d)", ORTE_NAME_PRINT(ORTE_PROC_MY_NAME),
                         ORTE_NAME_PRINT(&msg_hdr->msg_origin), msg_hdr->msg_data.req.tag,
                         msg_hdr->msg_data.req.data_follows, (void *)data, msg_hdr->msg_data.req.data_iovec_used);

    rc = mca_oob_ud_get_recv_req (msg_hdr->msg_origin, msg_hdr->msg_data.req.tag, &req,  msg_hdr->msg_data.req.data_iovec_used);
    if (ORTE_SUCCESS != rc) {
        ORTE_ERROR_LOG(rc);
        return rc;
    }

    req->req_rem_ctx      = msg_hdr->msg_rem_ctx;
    req->req_port         = port;
    req->req_mtu          = min(port->mtu, msg_hdr->msg_data.req.mtu);
    req->req_origin       = msg_hdr->msg_origin;
    req->req_target       = msg_hdr->msg_target;
    req->req_rem_data_len = msg_hdr->msg_data.req.data_len;
    req->req_channel      = msg_hdr->msg_channel;
    req->req_seq_num      = msg_hdr->msg_seq_num;

    do {
        rc = mca_oob_ud_recv_alloc (req);
        if (ORTE_SUCCESS != rc) {
            ORTE_ERROR_LOG(rc);
            free (req->req_data.iov.uiov);
            OBJ_RELEASE(req);
            req = NULL;
            break;
        }
        req->req_peer = peer;
        OBJ_RETAIN(req->req_peer);

        if (NULL == data) {
            req->state = MCA_OOB_UD_REQ_ACTIVE;
            opal_output_verbose(10, orte_oob_base_framework.framework_output,
                                "%s oob:ud:recv_incoming_send request still active",
                                ORTE_NAME_PRINT(ORTE_PROC_MY_NAME));
            break;
        }

       opal_output_verbose(10, orte_oob_base_framework.framework_output,
                             "%s oob:ud:recv_incoming_send send was eager",
                             ORTE_NAME_PRINT(ORTE_PROC_MY_NAME));

        req->req_is_eager = true;

        if (msg_hdr->msg_data.req.data_iovec_used) {
            for (i = 0 ; i < req->req_data.iov.count; ++i) {
                memcpy (req->req_data.iov.uiov[i].iov_base, data, req->req_data.iov.uiov[i].iov_len);
                data += req->req_data.iov.uiov[i].iov_len;
            }
        } else {
            memcpy(req->req_data.buf.p, data, msg_hdr->msg_data.req.data_len);
        }

        req->state = MCA_OOB_UD_REQ_COMPLETE;
    } while (0);

    *reqp = req;

    return rc;
}
