/*
* Copyright (c) 2015-2016 Intel Corporation, Inc.  All rights reserved.
*
* This software is available to you under a choice of one of two
* licenses.  You may choose to be licensed under the terms of the GNU
* General Public License (GPL) Version 2, available from the file
* COPYING in the main directory of this source tree, or the
* BSD license below:
*
*     Redistribution and use in source and binary forms, with or
*     without modification, are permitted provided that the following
*     conditions are met:
*
*      - Redistributions of source code must retain the above
*        copyright notice, this list of conditions and the following
*        disclaimer.
*
*      - Redistributions in binary form must reproduce the above
*        copyright notice, this list of conditions and the following
*        disclaimer in the documentation and/or other materials
*        provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

#ifdef _WIN32

#include "netdir.h"
#include "netdir_ov.h"
#include "netdir_cq.h"
#include "netdir_log.h"
#include "netdir_iface.h"
#include "netdir_unexp.h"

#include "rdma/fabric.h"
#include "rdma/fi_endpoint.h"

#include "ofi.h"
#include "ofi_util.h"

static ssize_t ofi_nd_srx_recv(struct fid_ep *ep, void *buf, size_t len,
			      void *desc, fi_addr_t src_addr, void *context);
static ssize_t ofi_nd_srx_recvmsg(struct fid_ep *ep_fid, const struct fi_msg *msg,
				 uint64_t flags);
static ssize_t ofi_nd_srx_recvv(struct fid_ep *ep_fid, const struct iovec *iov, void **desc,
			       size_t count, fi_addr_t src_addr, void *context);
static ssize_t ofi_nd_no_send(struct fid_ep *ep, const void *buf, size_t len,
			      void *desc, fi_addr_t src_addr, void *context);
static ssize_t ofi_nd_no_sendmsg(struct fid_ep *ep_fid, const struct fi_msg *msg,
				 uint64_t flags);
static ssize_t ofi_nd_no_sendv(struct fid_ep *ep_fid, const struct iovec *iov, void **desc,
			       size_t count, fi_addr_t dest_addr, void *context);
static ssize_t ofi_nd_no_inject(struct fid_ep *ep_fid, const void *buf, size_t len,
				fi_addr_t dest_addr);
static ssize_t ofi_nd_no_senddata(struct fid_ep *ep, const void *buf, size_t len, void *desc,
				  uint64_t data, fi_addr_t dest_addr, void *context);
static ssize_t ofi_nd_no_injectdata(struct fid_ep *ep, const void *buf, size_t len,
				    uint64_t data, fi_addr_t dest_addr);
static int ofi_nd_srx_close(struct fid *fid);
static ssize_t ofi_nd_srx_cancel(fid_t fid, void *context);
extern int ofi_nd_ep_getopt(struct fid* ep, int level, int optname,
	void* optval, size_t* optlen);

struct fi_ops_msg ofi_nd_srx_msg = {
	.size = sizeof(ofi_nd_srx_msg),
	.recv = ofi_nd_srx_recv,
	.recvv = ofi_nd_srx_recvv,
	.recvmsg = ofi_nd_srx_recvmsg,
	.send = ofi_nd_no_send,
	.sendv = ofi_nd_no_sendv,
	.sendmsg = ofi_nd_no_sendmsg,
	.inject = ofi_nd_no_inject,
	.senddata = ofi_nd_no_senddata,
	.injectdata = ofi_nd_no_injectdata
};

static struct fi_ops ofi_nd_fi_ops = {
	.size = sizeof(ofi_nd_fi_ops),
	.close = ofi_nd_srx_close,
	.bind = fi_no_bind,
	.control = fi_no_control,
	.ops_open = fi_no_ops_open
};

static struct fid ofi_nd_fid = {
	.fclass = FI_CLASS_SRX_CTX,
	.context = NULL,
	.ops = &ofi_nd_fi_ops
};

static struct fi_ops_ep ofi_nd_ep_ops = {
	.size = sizeof(ofi_nd_ep_ops),
	.cancel = ofi_nd_srx_cancel,
	.getopt = ofi_nd_ep_getopt,
	.setopt = fi_no_setopt,
	.tx_ctx = fi_no_tx_ctx,
	.rx_ctx = fi_no_rx_ctx,
	.rx_size_left = fi_no_rx_size_left,
	.tx_size_left = fi_no_tx_size_left
};

int ofi_nd_srx_ctx(struct fid_domain *pdomain,
		   struct fi_rx_attr *attr, struct fid_ep **rx_ep,
		   void *context)
{
	OFI_UNUSED(attr);
	struct nd_domain *domain = container_of(pdomain, struct nd_domain, fid);
	struct nd_srx *srx = (struct nd_srx*) calloc(1, sizeof(*srx));
	if (!srx)
		return -FI_ENOMEM;

	struct nd_srx def = {
		.fid = {
			.fid = {
				.fclass = FI_CLASS_SRX_CTX,
				.context = context,
				.ops = &ofi_nd_fi_ops
			},
			.ops = &ofi_nd_ep_ops,
			.msg = &ofi_nd_srx_msg
		},
		.domain = domain,
		.attr = {
			.caps = FI_MSG | FI_RECV,
			.mode = 0,
			.op_flags = 0,
			.comp_order = FI_ORDER_STRICT,
			.total_buffered_recv = 0,
			.size = (size_t)gl_data.inline_thr,
			.iov_limit = (size_t)min(domain->ainfo.MaxReceiveSge, ND_MSG_IOV_LIMIT) - 1
		}
	};

	*srx = def;
	/* TODO */
	dlist_init(&srx->received);

	*rx_ep = &srx->fid;

	InitializeCriticalSection(&srx->prepost_lock);

	return FI_SUCCESS;
}

static ssize_t ofi_nd_srx_recvmsg(struct fid_ep *pep, const struct fi_msg *msg,
				  uint64_t flags)
{
	assert(pep->fid.fclass == FI_CLASS_SRX_CTX);
	assert(msg);

	if (pep->fid.fclass != FI_CLASS_SRX_CTX)
		return -FI_EINVAL;

	size_t i;
	size_t len = 0;

	struct nd_srx *srx = container_of(pep, struct nd_srx, fid);

	if (msg->iov_count > min(srx->domain->ainfo.MaxReceiveSge, ND_MSG_IOV_LIMIT) - 1)
		return -FI_EINVAL;

	for (i = 0; i < msg->iov_count; i++) {
		if (msg->msg_iov[i].iov_len && !msg->msg_iov[i].iov_base)
			return -FI_EINVAL;
		len += msg->msg_iov[i].iov_len;
	}

	struct nd_cq_entry *entry = ofi_nd_buf_alloc_nd_cq_entry();
	if (!entry)
		return -FI_ENOMEM;
	memset(entry, 0, sizeof(*entry));

	entry->buf = (msg->iov_count == 1) ? msg->msg_iov[0].iov_base : 0;
	entry->len = len;
	entry->data = msg->data;
	entry->flags = flags | FI_MSG | FI_RECV;
	entry->domain = srx->domain;
	entry->context = msg->context;
	entry->iov_cnt = msg->iov_count;
	entry->seq = InterlockedAdd64(&srx->domain->msg_cnt, 1);

	for (i = 0; i < msg->iov_count; i++) {
		entry->iov[i] = msg->msg_iov[i];
	}

	/* store allocated entry in 1st pointer of internal data of context */
	if (msg->context)
		ND_FI_CONTEXT(msg->context) = entry;

	ofi_nd_queue_push(&srx->prepost, &entry->queue_item);

	ofi_nd_srx_match(srx);

	return FI_SUCCESS;
}

static ssize_t ofi_nd_srx_recvv(struct fid_ep *pep, const struct iovec *iov,
			       void **desc,
			       size_t count, fi_addr_t src_addr, void *context)
{
	struct fi_msg msg = {
		.msg_iov = iov,
		.desc = desc,
		.iov_count = count,
		.addr = src_addr,
		.context = context,
		.data = 0
	};

	assert(pep->fid.fclass == FI_CLASS_SRX_CTX);

	if (pep->fid.fclass != FI_CLASS_SRX_CTX)
		return -FI_EINVAL;

	struct nd_srx *rx_ctx  = container_of(pep, struct nd_srx, fid);

	return ofi_nd_srx_recvmsg(pep, &msg, rx_ctx->attr.op_flags);
}

static ssize_t ofi_nd_srx_recv(struct fid_ep *pep, void *buf, size_t len,
			       void *desc, fi_addr_t src_addr, void *context)
{
	struct iovec iov = {
		.iov_base = buf,
		.iov_len = len
	};

	return ofi_nd_srx_recvv(pep, &iov, &desc, 1, src_addr, context);
}

static int ofi_nd_srx_close(struct fid *fid)
{
	assert(fid);
	assert(fid->fclass == FI_CLASS_SRX_CTX);

	if (fid->fclass != FI_CLASS_SRX_CTX)
		return -FI_EINVAL;

	struct nd_srx *srx = container_of(fid, struct nd_srx, fid.fid);

	DeleteCriticalSection(&srx->prepost_lock);
	if (srx->srx)
		srx->srx->lpVtbl->Release(srx->srx);
	free(srx);

	return FI_SUCCESS;
}

static ssize_t ofi_nd_no_send(struct fid_ep *ep, const void *buf, size_t len,
			      void *desc, fi_addr_t src_addr, void *context)
{
	OFI_UNUSED(ep);
	OFI_UNUSED(buf);
	OFI_UNUSED(len);
	OFI_UNUSED(desc);
	OFI_UNUSED(src_addr);
	OFI_UNUSED(context);
	return -FI_ENOSYS;
}

static ssize_t ofi_nd_no_sendmsg(struct fid_ep *ep_fid, const struct fi_msg *msg,
				 uint64_t flags)
{
	OFI_UNUSED(ep_fid);
	OFI_UNUSED(msg);
	OFI_UNUSED(flags);
	return -FI_ENOSYS;
}

static ssize_t ofi_nd_no_sendv(struct fid_ep *ep_fid, const struct iovec *iov, void **desc,
			       size_t count, fi_addr_t dest_addr, void *context)
{
	OFI_UNUSED(ep_fid);
	OFI_UNUSED(iov);
	OFI_UNUSED(desc);
	OFI_UNUSED(count);
	OFI_UNUSED(dest_addr);
	OFI_UNUSED(context);
	return -FI_ENOSYS;
}
static ssize_t ofi_nd_no_inject(struct fid_ep *ep_fid, const void *buf, size_t len,
				fi_addr_t dest_addr)
{
	OFI_UNUSED(ep_fid);
	OFI_UNUSED(buf);
	OFI_UNUSED(len);
	OFI_UNUSED(dest_addr);
	return -FI_ENOSYS;
}

static ssize_t ofi_nd_no_senddata(struct fid_ep *ep, const void *buf, size_t len, void *desc,
				  uint64_t data, fi_addr_t dest_addr, void *context)
{
	OFI_UNUSED(ep);
	OFI_UNUSED(buf);
	OFI_UNUSED(len);
	OFI_UNUSED(desc);
	OFI_UNUSED(data);
	OFI_UNUSED(dest_addr);
	OFI_UNUSED(context);
	return -FI_ENOSYS;
}

static ssize_t ofi_nd_no_injectdata(struct fid_ep *ep, const void *buf, size_t len,
			     uint64_t data, fi_addr_t dest_addr)
{
	OFI_UNUSED(ep);
	OFI_UNUSED(buf);
	OFI_UNUSED(len);
	OFI_UNUSED(data);
	OFI_UNUSED(dest_addr);
	return -FI_ENOSYS;
}

static ssize_t ofi_nd_srx_cancel(fid_t fid, void *context)
{
	assert(fid);
	assert(fid->fclass == FI_CLASS_SRX_CTX);
	assert(context);

	if (!context) {
		ND_LOG_WARN(FI_LOG_EP_DATA, "Context is NULL \n");
		return -FI_EINVAL;
	}

	return ofi_nd_cq_cancel(fid, context);
}

#endif /* _WIN32 */

