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

#include <array>
#include <cstdint>
#include <memory>
#include <string>

#include <substrate/internal/types>
#include <substrate/utility>

// NOLINTNEXTLINE(modernize-concat-nested-namespaces)
namespace substrate
{
	namespace advanced
	{
		// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
		struct SUBSTRATE_INTERFACE readable_t
		{
			SUBSTRATE_NO_DISCARD(virtual ssize_t read(void *bufferPtr,
				size_t bufferLen, std::nullptr_t) const noexcept) = 0;

			SUBSTRATE_NO_DISCARD(bool read(void *const value, const size_t valueLen, size_t &resultLen) const noexcept)
			{
				const ssize_t result{read(value, valueLen, nullptr)};
				if (result < 0)
					return false;
				resultLen = size_t(result);
				return resultLen == valueLen;
			}

			SUBSTRATE_NO_DISCARD(bool read(void *const value, const size_t valueLen) const noexcept)
			{
				size_t resultLen{};
				return read(value, valueLen, resultLen);
			}

			template<typename T> SUBSTRATE_NO_DISCARD(bool read(T &value) const noexcept)
				{ return read(&value, sizeof(T)); }
			template<typename T> SUBSTRATE_NO_DISCARD(bool read(std::unique_ptr<T> &value) const noexcept)
				{ return read(value.get(), sizeof(T)); }
			template<typename T> SUBSTRATE_NO_DISCARD(bool read(const std::unique_ptr<T> &value) const noexcept)
				{ return read(value.get(), sizeof(T)); }
			// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
			template<typename T> SUBSTRATE_NO_DISCARD(bool read(const std::unique_ptr<T []> &value, const size_t valueCount) const noexcept)
				{ return read(value.get(), sizeof(T) * valueCount); }
			template<typename T, size_t N> SUBSTRATE_NO_DISCARD(bool read(std::array<T, N> &value) const noexcept)
				{ return read(value.data(), sizeof(T) * N); }

#ifdef SUBSTRATE_MANAGED_PTR
			template<typename T> SUBSTRATE_NO_DISCARD(enable_if_t<!std::is_array<T>::value, bool>
				read(const managedPtr_t<T> &value) const noexcept) { return read(value.get(), sizeof(T)); }
			template<typename T> SUBSTRATE_NO_DISCARD(bool read(const managedPtr_t<T> &value, const size_t valueLen) const noexcept)
				{ return read(value.get(), valueLen); }
#endif

#ifdef SUBSTRATE_FIXED_VECTOR
			template<typename T> SUBSTRATE_NO_DISCARD(bool read(const fixedVector_t<T> &value) const noexcept)
				{ return read(value.data(), sizeof(T) * value.size()); }
#endif

			template<size_t length, typename T, size_t N> SUBSTRATE_NO_DISCARD(bool read(std::array<T, N> &value) const noexcept)
			{
				static_assert(length <= N, "Can't request to read more than the std::array<> length");
				return read(value.data(), sizeof(T) * length);
			}

			SUBSTRATE_NO_DISCARD(bool readLE(uint16_t &value) const noexcept)
			{
				std::array<uint8_t, 2> data{};
				const bool result{read(data)};
				value = uint16_t((uint16_t(data[1]) << 8U) | data[0]);
				return result;
			}

			SUBSTRATE_NO_DISCARD(bool readLE(uint32_t &value) const noexcept)
			{
				std::array<uint8_t, 4> data{};
				const bool result{read(data)};
				value = (uint32_t(data[3]) << 24U) | (uint32_t(data[2]) << 16U) |
					(uint32_t(data[1]) << 8U) | data[0];
				return result;
			}

			SUBSTRATE_NO_DISCARD(bool readLE(uint64_t &value) const noexcept)
			{
				std::array<uint8_t, 8> data{};
				const bool result{read(data)};
				value = (uint64_t(data[7]) << 56U) | (uint64_t(data[6]) << 48U) |
					(uint64_t(data[5]) << 40U) | (uint64_t(data[4]) << 32U) |
					(uint64_t(data[3]) << 24U) | (uint64_t(data[2]) << 16U) |
					(uint64_t(data[1]) << 8U) | data[0];
				return result;
			}

			template<typename T, typename = typename std::enable_if<
				std::is_integral<T>::value && !std::is_same<T, bool>::value &&
				std::is_signed<T>::value && sizeof(T) >= 2>::type
			>
			SUBSTRATE_NO_DISCARD(bool readLE(T &value) const noexcept)
			{
				substrate::make_unsigned_t<T> data{};
				const auto result{readLE(data)};
				value = static_cast<T>(data);
				return result;
			}

			SUBSTRATE_NO_DISCARD(bool readBE(uint16_t &value) const noexcept)
			{
				std::array<uint8_t, 2> data{};
				const bool result{read(data)};
				value = uint16_t((uint16_t(data[0]) << 8U) | data[1]);
				return result;
			}

			SUBSTRATE_NO_DISCARD(bool readBE(uint32_t &value) const noexcept)
			{
				std::array<uint8_t, 4> data{};
				const bool result{read(data)};
				value = (uint32_t(data[0]) << 24U) | (uint32_t(data[1]) << 16U) |
					(uint32_t(data[2]) << 8U) | data[3];
				return result;
			}

			SUBSTRATE_NO_DISCARD(bool readBE(uint64_t &value) const noexcept)
			{
				std::array<uint8_t, 8> data{};
				const bool result{read(data)};
				value = (uint64_t(data[0]) << 56U) | (uint64_t(data[1]) << 48U) |
					(uint64_t(data[2]) << 40U) | (uint64_t(data[3]) << 32U) |
					(uint64_t(data[4]) << 24U) | (uint64_t(data[5]) << 16U) |
					(uint64_t(data[6]) << 8U) | data[7];
				return result;
			}

			template<typename T, typename = typename std::enable_if<
				std::is_integral<T>::value && !std::is_same<T, bool>::value &&
				std::is_signed<T>::value && sizeof(T) >= 2>::type
			>
			SUBSTRATE_NO_DISCARD(bool readBE(T &value) const noexcept)
			{
				substrate::make_unsigned_t<T> data{};
				const auto result{readBE(data)};
				value = static_cast<T>(data);
				return result;
			}
		};

		// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
		struct SUBSTRATE_INTERFACE writeable_t
		{
		public:
			SUBSTRATE_NO_DISCARD(virtual ssize_t write(const void *bufferPtr,
				size_t bufferLen, std::nullptr_t) const noexcept) = 0;

			SUBSTRATE_NO_DISCARD(bool write(const void *const value, const size_t valueLen) const noexcept)
			{
				const auto result{write(value, valueLen, nullptr)};
				if (result < 0)
					return false;
				return size_t(result) == valueLen;
			}

			template<typename T> SUBSTRATE_NO_DISCARD(bool write(const T &value) const noexcept)
				{ return write(&value, sizeof(T)); }
			template<typename T> SUBSTRATE_NO_DISCARD(bool write(const std::unique_ptr<T> &value) const noexcept)
				{ return write(value.get(), sizeof(T)); }
			// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
			template<typename T> SUBSTRATE_NO_DISCARD(bool write(const std::unique_ptr<T []> &value, const size_t valueCount) const noexcept)
				{ return write(value.get(), sizeof(T) * valueCount); }
			template<typename T, size_t N> SUBSTRATE_NO_DISCARD(bool write(const std::array<T, N> &value) const noexcept)
				{ return write(value.data(), sizeof(T) * N); }

#ifdef SUBSTRATE_MANAGED_PTR
			template<typename T> SUBSTRATE_NO_DISCARD(enable_if_t<!std::is_array<T>::value, bool>
				write(const managedPtr_t<T> &value) const noexcept) { return write(value.get(), sizeof(T)); }
			template<typename T> SUBSTRATE_NO_DISCARD(bool write(const managedPtr_t<T> &value, const size_t valueLen) const noexcept)
				{ return write(value.get(), valueLen); }
#endif

#ifdef SUBSTRATE_FIXED_VECTOR
			template<typename T> SUBSTRATE_NO_DISCARD(bool write(const fixedVector_t<T> &value) const noexcept)
				{ return write(value.data(), sizeof(T) * value.size()); }
#endif

			SUBSTRATE_NO_DISCARD(bool write(const std::string &value) const noexcept)
				{ return write(value.data(), value.size()); }
#if __cplusplus >= 201703L
			SUBSTRATE_NO_DISCARD(bool write(const std::string_view &value) const noexcept)
				{ return write(value.data(), value.size()); }
#endif

			template<size_t length, typename T, size_t N> SUBSTRATE_NO_DISCARD(bool write(const std::array<T, N> &value) const noexcept)
			{
				static_assert(length <= N, "Can't request to write more than the std::array<> length");
				return write(value.data(), sizeof(T) * length);
			}

			SUBSTRATE_NO_DISCARD(bool writeLE(const uint16_t value) const noexcept)
			{
				const std::array<uint8_t, 2> data
				{{
					uint8_t(value),
					uint8_t(value >> 8U)
				}};
				return write(data);
			}

			SUBSTRATE_NO_DISCARD(bool writeLE(const uint32_t value) const noexcept)
			{
				const std::array<uint8_t, 4> data
				{{
					uint8_t(value),
					uint8_t(value >> 8U),
					uint8_t(value >> 16U),
					uint8_t(value >> 24U)
				}};
				return write(data);
			}

			SUBSTRATE_NO_DISCARD(bool writeLE(const uint64_t value) const noexcept)
			{
				const std::array<uint8_t, 8> data
				{{
					uint8_t(value),
					uint8_t(value >> 8U),
					uint8_t(value >> 16U),
					uint8_t(value >> 24U),
					uint8_t(value >> 32U),
					uint8_t(value >> 40U),
					uint8_t(value >> 48U),
					uint8_t(value >> 56U)
				}};
				return write(data);
			}

			template<typename T, typename = enable_if_t<
				std::is_integral<T>::value && !std::is_same<T, bool>::value &&
				std::is_signed<T>::value && sizeof(T) >= 2>
			>
			SUBSTRATE_NO_DISCARD(bool writeLE(const T value) const noexcept)
			{
				auto data{static_cast<typename std::make_unsigned<T>::type>(value)};
				return writeLE(data);
			}

			SUBSTRATE_NO_DISCARD(bool writeBE(const uint16_t value) const noexcept)
			{
				const std::array<uint8_t, 2> data
				{{
					uint8_t(value >> 8U),
					uint8_t(value)
				}};
				return write(data);
			}

			SUBSTRATE_NO_DISCARD(bool writeBE(const uint32_t value) const noexcept)
			{
				const std::array<uint8_t, 4> data
				{{
					uint8_t(value >> 24U),
					uint8_t(value >> 16U),
					uint8_t(value >> 8U),
					uint8_t(value)
				}};
				return write(data);
			}

			SUBSTRATE_NO_DISCARD(bool writeBE(const uint64_t value) const noexcept)
			{
				const std::array<uint8_t, 8> data
				{{
					uint8_t(value >> 56U),
					uint8_t(value >> 48U),
					uint8_t(value >> 40U),
					uint8_t(value >> 32U),
					uint8_t(value >> 24U),
					uint8_t(value >> 16U),
					uint8_t(value >> 8U),
					uint8_t(value)
				}};
				return write(data);
			}

			template<typename T, typename = enable_if_t<
				std::is_integral<T>::value && !std::is_same<T, bool>::value &&
				std::is_signed<T>::value && sizeof(T) >= 2>
			>
			SUBSTRATE_NO_DISCARD(bool writeBE(const T value) const noexcept)
			{
				auto data{static_cast<typename std::make_unsigned<T>::type>(value)};
				return writeBE(data);
			}
		};

		// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
		struct SUBSTRATE_INTERFACE seekable_t
		{
		public:
			SUBSTRATE_NO_DISCARD(virtual off_t seek(off_t offset, int32_t whence) const noexcept) = 0;
			SUBSTRATE_NO_DISCARD(virtual off_t tell() const noexcept) = 0;
			SUBSTRATE_NO_DISCARD(virtual off_t length() const noexcept) = 0;

			SUBSTRATE_NO_DISCARD(bool head() const noexcept) { return seek(0, SEEK_SET) == 0; }
			SUBSTRATE_NO_DISCARD(bool tail() const noexcept)
			{
				const auto offset{length()};
				if (offset < 0)
					return false;
				return seek(offset, SEEK_SET) == offset;
			}

			SUBSTRATE_NO_DISCARD(bool seekRel(const off_t offset) const noexcept)
			{
				const auto currentPos{tell()};
				if (currentPos == -1 || currentPos + offset < 0)
					return false;
				return seek(offset, SEEK_CUR) == currentPos + offset;
			}
		};

		// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
		struct SUBSTRATE_INTERFACE io_t : public readable_t, public writeable_t
		{
			SUBSTRATE_NO_DISCARD(virtual bool valid() const noexcept) = 0;
		};

		// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
		struct SUBSTRATE_INTERFACE seekableIO_t : public io_t, public seekable_t { };
	}
}

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