/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <ox/std/array.hpp>
#include <ox/std/bit.hpp>
#include <ox/std/error.hpp>
#include <ox/std/span.hpp>
#include <ox/std/types.hpp>
#include <ox/std/reader.hpp>

#include "err.hpp"

namespace ox {

template<Reader_c Reader>
class FieldBitmapReader {
	protected:
		mutable size_t m_mapBlockIdx = ~size_t{};
		mutable uint64_t m_mapBlock{};
		size_t m_mapStart{};
		Reader &m_reader;

	public:
		explicit constexpr FieldBitmapReader(Reader &reader) noexcept:
			m_mapStart(reader.tellg()),
			m_reader(reader) {
		}

		constexpr Result<bool> get(size_t idx) const noexcept {
			constexpr auto blockBits = sizeof(m_mapBlock);
			auto const blockIdx = idx / blockBits;
			if (m_mapBlockIdx != blockIdx) [[unlikely]] {
				OX_RETURN_ERROR(loadMapBlock(blockIdx));
			}
			idx %= blockBits;
			return (m_mapBlock >> idx) & 1;
		}

	private:
		constexpr Error loadMapBlock(size_t const idx) const noexcept {
			OX_REQUIRE(g, m_reader.tellg());
			OX_RETURN_ERROR(m_reader.seekg(static_cast<int>(m_mapStart + idx), ox::ios_base::beg));
			Array<char, sizeof(m_mapBlock)> mapBlock{};
			OX_RETURN_ERROR(m_reader.read(mapBlock.data(), sizeof(m_mapBlock)));
			// Warning: narrow-conv
			OX_RETURN_ERROR(m_reader.seekg(static_cast<int>(g), ox::ios_base::beg));
			m_mapBlock = 0;
			for (uint64_t i{}; auto b : mapBlock) {
				m_mapBlock |= static_cast<uint64_t>(std::bit_cast<uint8_t>(b)) << i;
				i += 8;
			}
			m_mapBlockIdx = idx;
			return {};
		}

};


class FieldBitmapWriter {
	protected:
		Span<char> m_map;
		size_t m_mapLen{};

	public:
		explicit constexpr FieldBitmapWriter(Span<char> const &map) noexcept:
			m_map(map),
			m_mapLen(m_map.size()) {
		}

		constexpr auto setBuffer(Span<char> const &map) noexcept {
			m_map = map;
			m_mapLen = map.size();
		}

		constexpr Result<bool> get(size_t const i) const noexcept {
			if (i / 8 < m_mapLen) {
				return (std::bit_cast<uint8_t>(m_map[i / 8]) >> (i % 8)) & 1;
			}
			return Error{McPresenceMapOverflow};
		}

		constexpr Error setFields(int const fields) noexcept {
			m_mapLen = static_cast<size_t>((fields / 8 + 1) - (fields % 8 == 0));
			if (m_mapLen > m_map.size()) [[unlikely]] {
				return Error{McPresenceMapOverflow};
			}
			return {};
		}

		constexpr void setMaxLen(int const maxLen) noexcept {
			m_mapLen = static_cast<size_t>(maxLen);
		}

		constexpr int64_t getMaxLen() const noexcept {
			return static_cast<int64_t>(m_mapLen);
		}

		constexpr Error set(size_t const i, bool const on) noexcept {
			if (i / 8 < m_mapLen) {
				char &actual = m_map[i / 8];
				uint8_t v = std::bit_cast<uint8_t>(actual);
				if (on) {
					v |= 1 << (i % 8);
				} else {
					v &= ~static_cast<uint8_t>(1 << (i % 8));
				}
				actual = std::bit_cast<char>(v);
				return {};
			}
			return Error{McPresenceMapOverflow};
		}

};

}
