// SPDX-License-Identifier: BSD-3-Clause
#ifndef SUBSTRATE_FD
#define SUBSTRATE_FD

#include <string>
#include <utility>
#if __cplusplus >= 201703L
#include <string_view>
#endif

#include <fcntl.h>

#include <substrate/internal/defs>
#include <substrate/internal/fd_compat>
#ifdef SUBSTRATE_MANAGED_PTR
#	include <substrate/utility>
#endif
#include <substrate/mmap>
#include <substrate/advanced/io>

#if defined(SUBSTRATE_ALLOW_STD_FILESYSTEM_PATH)
#include <filesystem>
#endif

namespace substrate
{
	/*!
	* While this is supposed to be a very thin, RAII-only layer,
	* between a file descriptor and the code that uses it, due to the need to know
	* EOF outside of just read() calls, this also holds a flag for that express purpose.
	*/
	struct fd_t final: public substrate::advanced::seekableIO_t
	{
	private:
		int32_t fd{-1};
		mutable bool eof{false};
		mutable off_t _length{-1};

	public:
		constexpr fd_t() noexcept = default;
		constexpr fd_t(const int32_t fd_) noexcept : fd{fd_} { }
		fd_t(const char *const fileName, const int flags, const mode_t mode = 0) noexcept :
			fd{internal::fdopen(fileName, flags, mode)} { }
		fd_t(const std::string &fileName, const int flags, const mode_t mode = 0) noexcept :
			fd{internal::fdopen(fileName.c_str(), flags, mode)} { }
#if __cplusplus >= 201703L
		fd_t(const std::string_view &fileName, const int flags, const mode_t mode = 0) noexcept :
			fd{internal::fdopen(fileName.data(), flags, mode)} { }
#endif
#if defined(SUBSTRATE_ALLOW_STD_FILESYSTEM_PATH)
		fd_t(const std::filesystem::path &fileName, const int flags, const mode_t mode = 0) noexcept :
			fd{internal::fdopen(fileName.c_str(), flags, mode)} { }
#endif
		fd_t(fd_t &&other) noexcept : fd_t{}
			{ *this = std::move(other); }

		~fd_t() noexcept
		{
			if (fd != -1)
				internal::fdclose(fd);
		}

		fd_t &operator =(fd_t &&desc) noexcept
		{
			std::swap(fd, desc.fd);
			std::swap(eof, desc.eof);
			std::swap(_length, desc._length);
			return *this;
		}

		SUBSTRATE_NO_DISCARD(operator int32_t() const noexcept) { return fd; }
		SUBSTRATE_NO_DISCARD(bool operator ==(const int32_t desc) const noexcept) { return fd == desc; }
		SUBSTRATE_NO_DISCARD(bool valid() const noexcept final) { return fd != -1; }
		SUBSTRATE_NO_DISCARD(bool isEOF() const noexcept) { return eof; }
		void invalidate() noexcept { fd = -1; }

		using substrate::advanced::seekableIO_t::read;
		using substrate::advanced::seekableIO_t::write;
		using substrate::advanced::seekableIO_t::seek;

		SUBSTRATE_NO_DISCARD(ssize_t read(void *const bufferPtr, const size_t bufferLen, std::nullptr_t) const noexcept final)
		{
			const auto result = internal::fdread(fd, bufferPtr, bufferLen);
			if (!result && bufferLen)
				eof = true;
			return result;
		}

		SUBSTRATE_NO_DISCARD(off_t seek(const off_t offset, const int32_t whence) const noexcept final)
		{
			const auto result = internal::fdseek(fd, offset, whence);
			eof = result == length();
			return result;
		}

		SUBSTRATE_NO_DISCARD(ssize_t write(const void *const bufferPtr, const size_t bufferLen, std::nullptr_t) const noexcept final)
			{ return internal::fdwrite(fd, bufferPtr, bufferLen); }
		SUBSTRATE_NO_DISCARD(off_t tell() const noexcept final) { return internal::fdtell(fd); }

		SUBSTRATE_NO_DISCARD(fd_t dup() const noexcept) { return internal::fddup(fd); }

		SUBSTRATE_NO_DISCARD(internal::stat_t stat() const noexcept)
		{
			internal::stat_t fileStat{};
			if (!internal::fstat(fd, &fileStat))
				return fileStat;
			return {};
		}

		SUBSTRATE_NO_DISCARD(off_t length() const noexcept final)
		{
			if (_length != -1)
				return _length;
			internal::stat_t fileStat{};
			const int result = internal::fstat(fd, &fileStat);
			_length = result ? -1 : fileStat.st_size;
			return _length;
		}

		SUBSTRATE_NO_DISCARD(bool resize(const off_t newSize) const noexcept)
		{
			if (internal::fdtruncate(fd, newSize) != 0)
				return false;

			_length = newSize;
			return true;
		}

		SUBSTRATE_NO_DISCARD(mmap_t map(const int32_t prot, const int flags = MAP_SHARED) noexcept)
		{
			const auto len{length()};
			if (len <= 0)
				return {};
			return map(prot, static_cast<std::size_t>(len), flags);
		}

		SUBSTRATE_NO_DISCARD(mmap_t map(const int32_t prot, const std::size_t len, const int flags, void* map_addr = nullptr) noexcept)
		{
			if (!valid())
				return {};
			const int32_t file = fd;
			invalidate();
			return {file, len, prot, flags, map_addr};
		}

		fd_t(const fd_t &) = delete;
		fd_t &operator =(const fd_t &) = delete;
	};

#ifndef _WIN32
	// NOLINTNEXTLINE(hicpp-signed-bitwise)
	SUBSTRATE_NOWARN_UNUSED(constexpr static mode_t normalMode) = S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH;
#else
	// NOLINTNEXTLINE(hicpp-signed-bitwise)
	SUBSTRATE_NOWARN_UNUSED(constexpr static mode_t normalMode) = _S_IWRITE | _S_IREAD;
#endif
} // namespace substrate

#endif /* SUBSTRATE_FD */
/* vim: set ft=cpp ts=4 sw=4 noexpandtab: */
