// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright © 2020-2021 Florian Fischer
#include <fcntl.h>
#include <sys/eventfd.h>	// for eventfd
#include <sys/types.h>		// for ssize_t

#include <array>
#include <cassert>	// for assert
#include <cerrno>		// for EBADF, ECANCELED
#include <cstdint>	// for uint64_t, int32_t
#include <cstdlib>	// for exit, EXIT_SUCCESS

#include "Common.hpp"	 // for DIE_MSG_ERRNO, DIE_MSG
#include "io.hpp"
#include "io/Future.hpp"	// for ReadFuture, CloseFuture, WriteFuture

using emper::io::CloseFuture;
using emper::io::ReadFuture;
using emper::io::WriteFuture;

static void successChain() {
	int efd = eventfd(0, 0);
	if (efd == -1) {
		DIE_MSG_ERRNO("eventfd failed");
	}

	uint64_t write_buf = 42;
	WriteFuture writeFuture(efd, &write_buf, sizeof(write_buf), 0);

	uint64_t read_buf;
	ReadFuture readFuture(efd, &read_buf, sizeof(read_buf), 0);
	readFuture.setDependency(writeFuture);

	CloseFuture closeFuture(efd);
	closeFuture.setDependency(readFuture);
	ssize_t res = closeFuture.submitAndWait();

	if (res == -1) {
		DIE_MSG_ERRNO("linked requests chain failed");
	}

	if (read_buf != 42) {
		DIE_MSG("dependent read value differs from written value");
	}
}

static void successLoop() {
	int efd = eventfd(0, 0);
	if (efd == -1) {
		DIE_MSG_ERRNO("eventfd failed");
	}

	const unsigned ITERATIONS = 10000;
	for (unsigned i = 0; i < ITERATIONS; ++i) {
		uint64_t write_buf = 42;
		WriteFuture writeFuture(efd, &write_buf, sizeof(write_buf), 0);

		uint64_t read_buf;
		ReadFuture readFuture(efd, &read_buf, sizeof(read_buf), 0);
		readFuture.setDependency(writeFuture);

		ssize_t res = readFuture.submitAndWait();

		if (res == -1) {
			DIE_MSG_ERRNO("linked requests chain failed");
		}

		if (read_buf != 42) {
			DIE_MSG("dependent read value differs from written value");
		}
	}

	CloseFuture closeFuture(efd);
	ssize_t res = closeFuture.submitAndWait();

	if (res == -1) {
		DIE_MSG_ERRNO("linked requests chain failed");
	}
}

static void failureChainInvCor() {
	std::array<char, 32> buf;
	ReadFuture invalidReadFuture(-1, buf.data(), buf.size(), 0);

	ReadFuture readFuture(0, buf.data(), buf.size(), 0);
	readFuture.setDependency(invalidReadFuture);

	int32_t res = readFuture.submitAndWait();
	assert(res == -ECANCELED);

	res = invalidReadFuture.wait();
	assert(res == -EBADF);
}

static void failureChainCorInvCor() {
	std::array<char, 32> buf;

	int fd = emper::io::openAndWait("/dev/null", O_WRONLY);
	// int fd = open("/dev/null", O_WRONLY, 0);
	assert(fd != -1);

	WriteFuture correctFuture1(fd, buf.data(), buf.size(), 0);

	ReadFuture invalidFuture(-1, buf.data(), buf.size(), 0);
	invalidFuture.setDependency(correctFuture1);

	WriteFuture correctFuture2(fd, buf.data(), buf.size(), 0);
	correctFuture2.setDependency(invalidFuture);

	int32_t res = correctFuture2.submitAndWait();
	assert(res == -ECANCELED);

	res = invalidFuture.wait();
	assert(res == -EBADF);

	res = correctFuture1.wait();
	assert(res == (int32_t)buf.size());

	emper::io::closeAndWait(fd);
}

void emperTest() {
	// This leaves the io_uring in a weird state because the successChain() afterwards
	// fails because the write future is already completed with -ECANCELED but
	// there is successful cqe in the CQ for this future
	failureChainCorInvCor();

	successChain();
	successLoop();
	failureChainInvCor();

	// failureChainInvCor left the io_uring in a weird state because
	// the io_uring_submit of the openAndWait() never returns.
	// failureChainCorInvCor();

	exit(EXIT_SUCCESS);
}