/*
 * 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/assert.hpp>
#include <ox/std/bit.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/math.hpp>
#include <ox/std/memops.hpp>
#include <ox/std/reader.hpp>

namespace ox::mc {

template<Integer_c T>
static constexpr auto Bits = sizeof(T) << 3;

/**
 * Returns highest bit other than possible signed bit.
 * Bit numbering starts at 0.
 */
template<Integer_c I>
[[nodiscard]]
constexpr size_t highestBit(I const val) noexcept {
	unsigned shiftStart = sizeof(I) * 8 - 1;
	// find the most significant non-sign indicator bit
	size_t highestBit = 0;
	// start at one bit lower if signed
	if constexpr(is_signed_v<I>) {
		--shiftStart;
	}
	for (auto i = shiftStart; i > 0; --i) {
		auto const bitValue = (val >> i) & 1;
		if (bitValue) {
			highestBit = i;
			break;
		}
	}
	return highestBit;
}

static_assert(highestBit(static_cast<int8_t>(0b10000000)) == 0);
static_assert(highestBit(~static_cast<int8_t>(-1)) == 0);
static_assert(highestBit(~static_cast<int8_t>(-2)) == 0);
static_assert(highestBit(~static_cast<int8_t>(-3)) == 1);
static_assert(highestBit(1) == 0);
static_assert(highestBit(2) == 1);
static_assert(highestBit(4) == 2);
static_assert(highestBit(8) == 3);
static_assert(highestBit(static_cast<uint64_t>(1) << 31) == 31);
static_assert(highestBit(static_cast<uint64_t>(1) << 63) == 63);

struct McInt {
	Array<uint8_t, 9> data{};
	// length of integer in bytes
	size_t length = 0;
};

template<Integer_c I>
[[nodiscard]]
constexpr McInt encodeInteger(I const pInput) noexcept {
	auto const input = ResizedInt_t<I, 64>{pInput};
	McInt out;
	auto const inputNegative = is_signed_v<I> && input < 0;
	// move input to uint64_t to allow consistent bit manipulation and to avoid
	// overflow concerns
	auto const val = std::bit_cast<uint64_t>(input);
	if (val) {
		// bits needed to represent number factoring in space possibly
		// needed for signed bit
		auto const highBit = inputNegative ? highestBit(~val) : highestBit(val);
		auto const bits = highBit + 1 + (is_signed_v<I> ? 1 : 0);
		// bytes needed to store value
		size_t bytes = bits / 8 + (bits % 8 != 0);
		auto const bitsAvailable = bytes * 8; // bits available to integer value
		auto const bitsNeeded = bits + bytes;
		// factor in bits needed for bytesIndicator (does not affect bytesIndicator)
		// bits for integer + bits needed to represent bytes > bits available
		if (bitsNeeded > bitsAvailable && bytes != 9) {
			++bytes;
		}
		auto const bytesIndicator = onMask<uint8_t>(bytes - 1);
		// ensure we are copying from little endian representation
		LittleEndian<uint64_t> leVal = val;
		if (inputNegative) {
			leVal |= static_cast<uint64_t>(1 << (bitsNeeded - 1));
		}
		if (bytes == 9) {
			out.data[0] = bytesIndicator;
			OX_ALLOW_UNSAFE_BUFFERS_BEGIN
			ox::memcpy(&out.data[1], &leVal, 8);
			OX_ALLOW_UNSAFE_BUFFERS_END
			if (inputNegative) {
				out.data[1] |= 0b1000'0000;
			}
		} else {
			auto const valBits = bytes * 8;
			uint64_t const negBit = inputNegative ? 1 : 0;
			auto const intermediate =
				static_cast<uint64_t>(leVal.raw() | (negBit << (valBits - 1))) << bytes |
				static_cast<uint64_t>(bytesIndicator);
			OX_ALLOW_UNSAFE_BUFFERS_BEGIN
			ox::memcpy(&out.data[0], &intermediate, sizeof(intermediate));
			OX_ALLOW_UNSAFE_BUFFERS_END
		}
		out.length = bytes;
	}
	return out;
}

/**
 * Returns the number of bytes indicated by the bytes indicator of a variable
 * length integer.
 */
[[nodiscard]]
constexpr size_t countBytes(unsigned const b) noexcept {
	size_t i = 0;
	while ((b >> i) & 1) ++i;
	return i + 1;
}

static_assert(countBytes(0b0000'0000) == 1);
static_assert(countBytes(0b0000'0001) == 2);
static_assert(countBytes(0b0000'0011) == 3);
static_assert(countBytes(0b0000'0111) == 4);
static_assert(countBytes(0b0000'1111) == 5);
static_assert(countBytes(0b0001'1111) == 6);
static_assert(countBytes(0b0011'1111) == 7);
static_assert(countBytes(0b0111'1111) == 8);
static_assert(countBytes(0b1111'1111) == 9);

template<Integer_c I>
constexpr Result<I> decodeInteger(Reader_c auto &rdr, size_t &bytesRead) noexcept {
	uint8_t firstByte = 0;
	OX_RETURN_ERROR(rdr.read(&firstByte, 1));
	OX_RETURN_ERROR(rdr.seekg(-1, ox::ios_base::cur));
	auto const bytes = countBytes(firstByte);
	if (bytes == 9) {
		bytesRead = bytes;
		I out = 0;
		OX_RETURN_ERROR(rdr.seekg(1, ox::ios_base::cur));
		OX_RETURN_ERROR(rdr.read(&out, sizeof(I)));
		return fromLittleEndian<I>(out);
	}
	bytesRead = bytes;
	uint64_t decoded = 0;
	OX_RETURN_ERROR(rdr.read(&decoded, bytes));
	decoded >>= bytes;
	// move sign bit
	if constexpr(is_signed_v<I>) {
		auto const negBit = bytes * 8 - bytes - 1;
		// move sign
		auto const negative = (decoded >> negBit) == 1;
		if (negative) {
			// fill in all bits between encoded sign and real sign with 1s
			// split it up because the 32-bit ARM can't shift more than 32 bits
			Array<uint32_t, 2> d = {};
			//d[0] = decoded & 0xffff'ffff;
			//d[1] = decoded >> 32;
		 	OX_ALLOW_UNSAFE_BUFFERS_BEGIN
			ox::memcpy(&d[0], &decoded, sizeof(decoded));
		 	OX_ALLOW_UNSAFE_BUFFERS_END
			auto bit = negBit;
			for (; bit < ox::min<size_t>(Bits<I>, 32); ++bit) {
				d[0] |= 1 << bit;
			}
			bit -= 32;
			for (; bit < Bits<I>; ++bit) {
				d[1] |= 1 << bit;
			}
			I out = 0;
			if constexpr(ox::defines::BigEndian) {
				auto const d0Tmp = d[0];
				d[0] = d[1];
				d[1] = d0Tmp;
			}
		 	OX_ALLOW_UNSAFE_BUFFERS_BEGIN
			ox::memcpy(&out, &d[0], sizeof(out));
			OX_ALLOW_UNSAFE_BUFFERS_END
			return out;
		}
	}
	return static_cast<I>(decoded);
}

template<Integer_c I>
Result<I> decodeInteger(McInt const &m) noexcept {
	size_t bytesRead{};
	BufferReader br({reinterpret_cast<const char*>(m.data.data()), 9});
	return decodeInteger<I>(br, bytesRead);
}

}
