#pragma once
#include "OS/Handle.h"
#include "OS/FdMap.h"
#include "OS/LinuxIO.h"
#include "Utils/Lock.h"
#if defined(POSIX)
#include <poll.h>
#endif

namespace os {

	class ThreadData;
	class IORequest;

	/**
	 * IO handle. Encapsulates an OS specific handle to some kind of synchronizing object that the
	 * OS will notify when IO requests have been completed.
	 *
	 * TODO: We could use epoll on Linux for better performance when using many file descriptors.
	 */
#if defined(WINDOWS)

	class IOHandle {
	public:
		IOHandle();
		IOHandle(HANDLE h);

		// Get the underlying handle (if any).
		HANDLE v() const;

		// Associate a handle to this IO handle.
		void add(Handle h, const ThreadData *id);

		// Remove association with this IO handle.
		void remove(Handle h, const ThreadData *id);

		// Process all messages for this IO handle.
		void notifyAll(const ThreadData *id) const;

		// Close this handle.
		void close();

		// Attach/detach IO requests.
		void attach();
		void detach();

	private:
		HANDLE handle;

		// # of requests pending.
		mutable size_t pending;
	};

#elif defined(POSIX)

	/**
	 * Note: the IOHandle interface is the same regardless of whether io_uring is available. This
	 * also allows us to use io_uring if available and fall back to the poll-based implementation if
	 * necessary.
	 */
	class IOHandle {
	public:
		IOHandle();
		~IOHandle();

		// Associate a handle to this IOHandle.
		void add(Handle h, const ThreadData *id);

		// Remove association of a handle.
		void remove(Handle h, const ThreadData *id);

		// Attach to this IO handle.
		void attach(Handle h, IORequest *request);

		// Attach to this IO handle, but call 'remove' on handle afterwards.
		void attachAndRemove(Handle h, IORequest *request);

		// Detach from this IO handle.
		void detach(Handle h, IORequest *request);

		// Cancel a request.
		void cancel(IORequest *request);

		// Process all messages for this IO handle.
		void notifyAll(const ThreadData *id);

		// Close this handle.
		void close();

		// Get an array of pollfd:s describing the threads waiting currently.  Note: The first
		// element is empty and available for use by the IOCondition (it puts itself first to wait
		// for the IO condition also).
		struct Desc {
			struct pollfd *fds;
			size_t count;
		};
		Desc desc();

	private:
		// Lock, just in case.
		mutable util::Lock lock;

#ifdef LINUX_IO_URING
		// Linux IO object.
		mutable LinuxIO linuxIO;

		// File descriptor to an eventfd that will get notified by the LinuxIO object. Set to -1 if
		// we failed to initialize io_uring.
		int eventfd;

		// Remember the set of requests that are active currently. We use this to make sure that we
		// don't accidentally mess up the stack of threads if we ever get multiple notifications for
		// the same fd.
		std::unordered_set<IORequest *> activeRequests;
		std::multiset<std::pair<int, IORequest *>> activeFDs;

#endif

		// All handles currently associated with us. If we are using io_uring, this will only ever
		// contain one element (the eventfd). Otherwise, contains all fds we need to watch over.
		typedef FdMap<IORequest, 1> HandleMap;
		HandleMap handles;

		// Implementation of the fallback to poll. This means that the uring implementation is
		// always able to just call the poll implementation as a fallback.
		void pollAdd(Handle h, const ThreadData *id);
		void pollRemove(Handle h, const ThreadData *id);
		void pollAttach(Handle h, IORequest *request);
		void pollAttachAndRemove(Handle h, IORequest *request);
		void pollDetach(Handle h, IORequest *request);
		void pollNotifyAll(const ThreadData *id);
		void pollCancel(IORequest *request);

		// Special cases for initialization and destruction.
		bool uringInit();
		void pollInit();
		void uringDestroy();
	};

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

}
