// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

#ifndef FENNEC_MEMORY_BYTES_H
#define FENNEC_MEMORY_BYTES_H

#include <fennec/lang/assert.h>
#include <fennec/lang/types.h>
#include <fennec/lang/hashing.h>

namespace fennec
{

/// \brief byte type
using byte_t = uint8_t;

///
/// \brief container for handling byte arrays
struct byte_array {
public:

	///
	/// \brief Default Constructor
	constexpr byte_array()
		: _carr(nullptr)
		, _size(0)
		, _const(true) {
	}

	///
	/// \brief Buffer Constructor
	/// \param arr the buffer to wrap
	/// \param n the size of the buffer in bytes
	constexpr byte_array(void* arr, size_t n)
		: _arr(static_cast<byte_t*>(arr))
		, _size(n)
		, _const(false) {
	}

	///
	/// \brief Const Buffer Constructor
	/// \param arr the buffer to wrap
	/// \param n the size of the buffer in bytes
	constexpr byte_array(const void* arr, size_t n)
		: _carr(static_cast<const byte_t*>(arr))
		, _size(n)
		, _const(true) {
	}

	///
	/// \brief Buffer Constructor
	/// \param arr the buffer to wrap
	/// \tparam n the size of the buffer in elements
	template<typename T, size_t n>
	constexpr byte_array(T (&arr)[n])
		: byte_array(arr, n * sizeof(T)) {
	}

	///
	/// \brief Const Buffer Constructor
	/// \param arr the buffer to wrap
	/// \tparam n the size of the buffer in elements
	template<typename T, size_t n>
	constexpr byte_array(const T (&arr)[n])
		: byte_array(arr, n * sizeof(T)) {
	}

	///
	/// \returns The size of the array in bytes
	constexpr size_t size() const {
		return _size;
	}

	///
	/// \brief Array Access Operator
	/// \param i the index to access
	/// \returns a reference to the byte at \emph{i}
	constexpr byte_t& operator[](int i) {
		assertd(not _const, "Attempted to Access Const-Qualified Memory as Non-Const");
		assertd(i >= 0 && size_t(i) < _size, "Array Out of Bounds");
		return _arr[i];
	}

	///
	/// \brief Const Array Access Operator
	/// \param i the index to access
	/// \returns a copy of the byte at \emph{i}
	constexpr byte_t operator[](int i) const {
		assertd(not _const, "Attempted to Access Const-Qualified Memory as Non-Const");
		assertd(i >= 0 && size_t(i) < _size, "Array Out of Bounds");
		return _carr[i];
	}

	///
	/// \brief Cast Function
	/// \tparam T type to cast to
	/// \returns a pointer to the underlying buffer interpreted as an array of \emph{T}
	template<typename T>
	constexpr T* cast() {
		void* temp = _arr;
		return static_cast<T*>(temp);
	}

	///
	/// \brief Const Cast Function
	/// \tparam T type to cast to
	/// \returns a pointer to the underlying buffer interpreted as an array of \emph{T}
	template<typename T>
	constexpr const T* cast() const {
		const void* temp = _carr;
		return static_cast<const T*>(temp);
	}

private:
	union { // hack to allow both const qualified and non-const strings
		byte_t*        _arr;
		const byte_t* _carr;
	};
	size_t _size;
	bool  _const;
};

///
/// \brief Byte Array Hash Function
template<>
struct hash<byte_array> {
	///
	/// \brief hash operator
	/// \param bytes the byte_array to hash
	/// \returns the murmur2 hash of the byte array
	size_t operator()(const byte_array& bytes) const {

		// Murmur2
		static constexpr uint64_t m = 0xc6a4a7935bd1e995;
		static constexpr uint64_t s = 0xff51afd7ed558ccd;
		static constexpr uint64_t r = 47;
		const uint64_t n = bytes.size();

		uint64_t h = s ^ (n * m);
		const uint64_t* x = bytes.cast<uint64_t>();
		const uint64_t* y = x + (n / sizeof(uint64_t));

		while (x != y) {
			uint64_t k = *x++;

			k *= m;
			k ^= k >> r;
			k *= m;

			h ^= k;
			h *= m;
		}

		const uint8_t* b = reinterpret_cast<const uint8_t*>(x);
		switch (n & 0x7) {
			case 7: h ^= uint64_t(b[6]) << 48; __attribute__((fallthrough));
			case 6: h ^= uint64_t(b[5]) << 40; __attribute__((fallthrough));
			case 5: h ^= uint64_t(b[4]) << 32; __attribute__((fallthrough));
			case 4: h ^= uint64_t(b[3]) << 24; __attribute__((fallthrough));
			case 3: h ^= uint64_t(b[2]) << 16; __attribute__((fallthrough));
			case 2: h ^= uint64_t(b[1]) << 8;  __attribute__((fallthrough));
			case 1: h ^= uint64_t(b[0]);
				h *= m;
			default:
				break;
		}

		h ^= h >> r;
		h *= m;
		h ^= h >> r;

		return h;
	}
};

}

#endif // FENNEC_MEMORY_BYTES_H
