/**
 * Copyright (C) Mellanox Technologies Ltd. 2001-2017.  ALL RIGHTS RESERVED.
 *
 * See file LICENSE for terms.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "wireup.h"

#include <ucp/core/ucp_proxy_ep.h>


/* Context for packing short data into bcopy */
typedef struct {
    uint64_t     header;
    const void   *payload;
    unsigned     length;
} ucp_signaling_ep_pack_ctx_t;


static size_t ucp_signaling_ep_pack_short(void *dest, void *arg)
{
    ucp_signaling_ep_pack_ctx_t *ctx = arg;

    *(uint64_t*)dest = ctx->header;
    memcpy(UCS_PTR_BYTE_OFFSET(dest, sizeof(uint64_t)), ctx->payload, ctx->length);
    return sizeof(uint64_t) + ctx->length;
}

static size_t ucp_signaling_ep_pack_tag_short(void *dest, void *arg)
{
    ucp_signaling_ep_pack_ctx_t *ctx = arg;

    memcpy(dest, ctx->payload, ctx->length);
    return ctx->length;
}

static ucs_status_t
ucp_signaling_ep_am_short(uct_ep_h ep, uint8_t id, uint64_t header,
                          const void *payload, unsigned length)
{
    ucp_proxy_ep_t *proxy_ep = ucs_derived_of(ep, ucp_proxy_ep_t);
    ucp_signaling_ep_pack_ctx_t ctx;
    ssize_t packed_size;

    ctx.header  = header;
    ctx.payload = payload;
    ctx.length  = length;

    ucp_assert_memtype(proxy_ep->ucp_ep->worker->context, ctx.payload,
                       ctx.length, UCS_MEMORY_TYPE_HOST);

    packed_size = uct_ep_am_bcopy(proxy_ep->uct_ep, id,
                                  ucp_signaling_ep_pack_short, &ctx,
                                  UCT_SEND_FLAG_SIGNALED);
    if (packed_size < 0) {
        return (ucs_status_t)packed_size;
    }

    ucp_proxy_ep_replace(proxy_ep);
    return UCS_OK;
}

static ssize_t
ucp_signaling_ep_am_bcopy(uct_ep_h ep, uint8_t id, uct_pack_callback_t pack_cb,
                          void *arg, unsigned flags)
{
    ucp_proxy_ep_t *proxy_ep = ucs_derived_of(ep, ucp_proxy_ep_t);
    ssize_t packed_size;

    packed_size = uct_ep_am_bcopy(proxy_ep->uct_ep, id, pack_cb, arg,
                                  flags | UCT_SEND_FLAG_SIGNALED);
    if (packed_size >= 0) {
        ucp_proxy_ep_replace(proxy_ep);
    }
    return packed_size;
}

static ucs_status_t
ucp_signaling_ep_am_zcopy(uct_ep_h ep, uint8_t id, const void *header,
                          unsigned header_length, const uct_iov_t *iov,
                          size_t iovcnt, unsigned flags, uct_completion_t *comp)
{
    ucp_proxy_ep_t *proxy_ep = ucs_derived_of(ep, ucp_proxy_ep_t);
    ucs_status_t status;

    status = uct_ep_am_zcopy(proxy_ep->uct_ep, id, header, header_length, iov,
                             iovcnt, flags | UCT_SEND_FLAG_SIGNALED, comp);
    if ((status == UCS_OK) || (status == UCS_INPROGRESS)) {
        ucp_proxy_ep_replace(proxy_ep);
    }
    return status;
}

static ucs_status_t
ucp_signaling_ep_tag_eager_short(uct_ep_h ep, uct_tag_t tag, const void *data,
                                 size_t length)
{
    ucp_proxy_ep_t *proxy_ep = ucs_derived_of(ep, ucp_proxy_ep_t);
    ucp_signaling_ep_pack_ctx_t ctx;
    ssize_t packed_size;

    ctx.payload = data;
    ctx.length  = length;

    ucp_assert_memtype(proxy_ep->ucp_ep->worker->context, ctx.payload,
                       ctx.length, UCS_MEMORY_TYPE_HOST);

    packed_size = uct_ep_tag_eager_bcopy(proxy_ep->uct_ep, tag, 0,
                                         ucp_signaling_ep_pack_tag_short, &ctx,
                                         UCT_SEND_FLAG_SIGNALED);
    if (packed_size < 0) {
        return (ucs_status_t)packed_size;
    }

    ucp_proxy_ep_replace(proxy_ep);
    return UCS_OK;
}

static ssize_t
ucp_signaling_ep_tag_eager_bcopy(uct_ep_h ep, uct_tag_t tag, uint64_t imm,
                                 uct_pack_callback_t pack_cb, void *arg,
                                 unsigned flags)
{
    ucp_proxy_ep_t *proxy_ep = ucs_derived_of(ep, ucp_proxy_ep_t);
    ssize_t packed_size;

    packed_size = uct_ep_tag_eager_bcopy(proxy_ep->uct_ep, tag, imm, pack_cb,
                                         arg, flags | UCT_SEND_FLAG_SIGNALED);
    if (packed_size >= 0) {
        ucp_proxy_ep_replace(proxy_ep);
    }
    return packed_size;
}

static ucs_status_t
ucp_signaling_ep_tag_eager_zcopy(uct_ep_h ep, uct_tag_t tag, uint64_t imm,
                                 const uct_iov_t *iov, size_t iovcnt,
                                 unsigned flags, uct_completion_t *comp)
{
    ucp_proxy_ep_t *proxy_ep = ucs_derived_of(ep, ucp_proxy_ep_t);
    ucs_status_t status;

    status = uct_ep_tag_eager_zcopy(proxy_ep->uct_ep, tag, imm, iov, iovcnt,
                                    flags | UCT_SEND_FLAG_SIGNALED, comp);

    if (!UCS_STATUS_IS_ERR(status)) {
        ucp_proxy_ep_replace(proxy_ep);
    }
    return status;
}

static ucs_status_ptr_t
ucp_signaling_ep_tag_rndv_zcopy(uct_ep_h ep, uct_tag_t tag, const void *header,
                                unsigned header_length, const uct_iov_t *iov,
                                size_t iovcnt, unsigned flags,
                                uct_completion_t *comp)
{
    ucp_proxy_ep_t *proxy_ep = ucs_derived_of(ep, ucp_proxy_ep_t);
    ucs_status_ptr_t status;

    status = uct_ep_tag_rndv_zcopy(proxy_ep->uct_ep, tag, header, header_length,
                                   iov, iovcnt, flags | UCT_SEND_FLAG_SIGNALED,
                                   comp);
    if (!UCS_PTR_IS_ERR(status)) {
        ucp_proxy_ep_replace(proxy_ep);
    }
    return status;
}

static ucs_status_t
ucp_signaling_ep_tag_rndv_request(uct_ep_h ep, uct_tag_t tag,
                                  const void* header, unsigned header_length,
                                  unsigned flags)
{
    ucp_proxy_ep_t *proxy_ep = ucs_derived_of(ep, ucp_proxy_ep_t);
    ucs_status_t status;

    status = uct_ep_tag_rndv_request(proxy_ep->uct_ep, tag, header,
                                     header_length,
                                     flags | UCT_SEND_FLAG_SIGNALED);
    if (status == UCS_OK) {
        ucp_proxy_ep_replace(proxy_ep);
    }
    return status;
}

ucs_status_t ucp_signaling_ep_create(ucp_ep_h ucp_ep, uct_ep_h uct_ep,
                                     int is_owner, uct_ep_h *signaling_ep)
{
    static uct_iface_ops_t signaling_ep_ops = {
        .ep_am_short         = ucp_signaling_ep_am_short,
        .ep_am_bcopy         = ucp_signaling_ep_am_bcopy,
        .ep_am_zcopy         = ucp_signaling_ep_am_zcopy,
        .ep_tag_eager_short  = ucp_signaling_ep_tag_eager_short,
        .ep_tag_eager_bcopy  = ucp_signaling_ep_tag_eager_bcopy,
        .ep_tag_eager_zcopy  = ucp_signaling_ep_tag_eager_zcopy,
        .ep_tag_rndv_zcopy   = ucp_signaling_ep_tag_rndv_zcopy,
        .ep_tag_rndv_request = ucp_signaling_ep_tag_rndv_request
    };

    return UCS_CLASS_NEW(ucp_proxy_ep_t, signaling_ep, &signaling_ep_ops,
                         ucp_ep, uct_ep, is_owner);
}
