#pragma once
#include "Sync.h"
#include "Handle.h"
#include "Thread.h"
#include "UThread.h"
#include "LinuxIO.h"

#if defined(POSIX)
#include <sys/socket.h>
#endif

namespace os {

	class IORequest;
	class IOHandle;

	/**
	 * Sleep structure for IORequests that have a timeout.
	 */
	class IOTimeoutSleep : public UThreadState::SleepData {
	public:
		IOTimeoutSleep() : SleepData(0), request(null) {}

		IORequest *request;

		virtual void signal();
	};


	/**
	 * IO request. These are posted to an IOHandle when the requests are completed.
	 */
#if defined(WINDOWS)

	class IORequest : public OVERLAPPED {
	public:
		// Note the thread that the request is associated with.
		IORequest(Handle handle, const Thread &thread, nat timeout = 0);
		~IORequest();

		// Sema used for notifying when the request is complete.
		Sema wake;

		// Handle we are working with. So that we can cancel IO.
		Handle handle;

		// Number of bytes read.
		nat bytes;

		// Error code (if any).
		int error;

		// Called on completion.
		void complete(nat bytes);

		// Called on failure.
		void failed(nat bytes, int error);

	private:
		// No copy.
		IORequest(const IORequest &);
		IORequest &operator =(const IORequest &);

		// Owning thread.
		Thread thread;

		// Sleep structure.
		IOTimeoutSleep sleep;
	};

#elif defined(POSIX)

#ifdef LINUX_IO_URING
#define DEF_IO_OP(name) name = IORING_OP_ ## name
#else
#define DEF_IO_OP(name) name
#endif

	/**
	 * IORequest on POSIX systems. This version is designed after how io_uring works. This approach
	 * allows us to use io_uring where it is available and fall back on poll (or others) if not. To
	 * allow portability, care has been taken to not assume that the io_uring headers are available.
	 */
	class IORequest {
	public:
		// IO operations. These have the same ID as the corresponding IORING_OP_ enum values.
		enum Operation {
			DEF_IO_OP(READ),
			DEF_IO_OP(WRITE),
			DEF_IO_OP(OPENAT),
			DEF_IO_OP(CLOSE),
			DEF_IO_OP(STATX),
			DEF_IO_OP(UNLINKAT),
			DEF_IO_OP(MKDIRAT),
			DEF_IO_OP(FSYNC),
			DEF_IO_OP(ACCEPT),
			DEF_IO_OP(CONNECT),
		};

		// Create.
		IORequest(Operation op, Handle handle, const Thread &thread);
		~IORequest();

		// Set additional data based on the operation:
		void bufferParam(void *addr, size_t len) {
			request.addr = reinterpret_cast<size_t>(addr);
			request.len = len;
		}
		void bufferParam(const void *addr, size_t len) {
			request.addr = reinterpret_cast<size_t>(addr);
			request.len = len;
		}
		void strParam(const char *str) {
			request.addr = reinterpret_cast<size_t>(str);
		}
		void offsetParam(off_t offset) {
			request.off = offset;
		}
		void useFilePointer() {
			request.off = -1; // Uses and advances the file pointer as usual.
		}
		void openParams(int flags) {
			request.open_flags = flags;
		}
		void modeParam(int mode) {
			request.len = mode;
		}
		void statxParams(struct statx *out, int flags, int mask) {
			request.statx_flags = flags;
			request.len = mask;
			request.off = reinterpret_cast<size_t>(out);
		}
		void acceptParams(struct sockaddr *addr, socklen_t *addrSize) {
			request.addr = reinterpret_cast<size_t>(addr);
			request.addr2 = reinterpret_cast<size_t>(addrSize);
		}
		void connectParams(struct sockaddr *addr, socklen_t addrSize) {
			request.addr = reinterpret_cast<size_t>(addr);
			request.off = static_cast<size_t>(addrSize);
		}
		void unlinkParams(int flags) {
			request.unlink_flags = flags;
		}
		void fsyncDatasync() {
#ifdef LINUX_IO_URING
			request.fsync_flags = IORING_FSYNC_DATASYNC;
#else
			request.fsync_flags = 1;
#endif
		}

		// Result from the IO operation. Note: ECANCELED either means a timeout or that the file was closed.
		int result;

		// Was the timeout reached.
		bool timeout() const {
			return result == -ECANCELED && timeoutSent;
		}

		// Was the file closed?
		bool closed() const {
			return result == -ECANCELED && !timeoutSent;
		}

		// Submit the request. Waits until completion. Returns 'result' for convenience.
		int submit(nat timeout = 0);

		// Submit the request and ask to detach the handle at the same time.
		int submitAndDetach(nat timeout = 0);

		// Submit the request, ask to detach, and deallocate the IO request once finished.
		void submitDetachDelete(nat timeout = 0);

		// Acces the IO handle.
		IOHandle &io();

	private:
		// To allow it to inspect what we are representing.
		friend class IOHandle;
		friend class IOTimeoutSleep;

		// No copy.
		IORequest(const IORequest &);
		IORequest &operator =(const IORequest &);

		// Did the timeout elapse? Note: 'result' may indicate a success even though timeout is
		// true. This may happen if the operation succeeded while the operation was being
		// cancelled. As such, a successful result trumps a timeout.
		bool timeoutSent;

		// Deallocate (using delete) once completed?
		bool deallocate;

		// Event signalled when the request is completed.
		Event wake;

		// Owning thread.
		Thread thread;

		// Sleep structure.
		IOTimeoutSleep sleep;

		// Data about the operation to execute.
#ifdef LINUX_IO_URING
		struct io_uring_sqe request;
#else
		struct Request {
			// Inspired by io_uring_sqe, but not identical. As long as we follow the rule that we
			// read from the same data members we write to (as one should do with unions
			// regardless), it is fine anyway.
			byte opcode;
			byte flags;
			int fd;
			size_t addr;
			size_t len;
			union {
				size_t off;
				size_t addr2;
			};
			union {
				int open_flags;
				int statx_flags;
				int unlink_flags;
				int fsync_flags;
			};
		};
		Request request;
#endif

		// Called by the IOHandle once the request is finished. If "result" is not null, update
		// 'result' accordingly.
		void onFinish(int *result);

		enum DoResult {
			doDone, // Done, no need for async operation.
			doWaitRead, // Need to wait until ready for reading.
			doWaitWrite, // Need to wait until ready for writing.
		};

		// Called by the IOHandle to attempt to perform the request now (e.g. if using poll). Should
		// return any of the values in DoResult to indicate how to proceed.
		DoResult doRequest();
	};


#undef DEF_IO_OP

#else
#error "Implement IORequest for your platform!"
#endif

}
