#ifndef MINORI_CORE_BYTE_STREAM_H_
#define MINORI_CORE_BYTE_STREAM_H_

#include "core/endian.h"

#include <cstdint>
#include <cstdlib>
#include <string>
#include <type_traits>
#include <vector>

struct ByteStream {
public:
	enum class ByteOrder {
		Little,
		Big,
	};

	ByteStream(std::uint8_t *bytes, std::size_t size);

	void ResetOffset();
	void SetEndianness(ByteOrder endian);

	template<typename T>
	bool ReadBinary(T &ret)
	{
		if (offset_ + sizeof(T) >= size_)
			return false;

		std::memcpy(&ret, bytes_ + offset_, sizeof(ret));
		Advance(sizeof(T));
		return true;
	}

	template<typename T>
	bool ReadInt(T &ret)
	{
		static_assert(std::is_integral<T>::value);

		if (!ReadBinary<T>(ret))
			return false;

		switch (endian_) {
			case ByteOrder::Little:
				if constexpr (std::is_unsigned<T>::value) {
					ret = Endian::byteswap_little_to_host(ret);
				} else {
					ret = Endian::signed_byteswap_little_to_host(ret);
				}
				break;
			case ByteOrder::Big:
				if constexpr (std::is_unsigned<T>::value) {
					ret = Endian::byteswap_big_to_host(ret);
				} else {
					ret = Endian::signed_byteswap_big_to_host(ret);
				}
				break;
			default:
				/* can't know for sure. punt */
				return false;
		}

		return true;
	}

	bool ReadString(std::string &str, std::size_t size);
	bool Advance(std::size_t amount);

private:
	/* raw data */
	std::uint8_t *bytes_ = nullptr;
	std::size_t offset_ = 0;
	std::size_t size_ = 0;

	ByteOrder endian_ = ByteOrder::Little;
};

#endif /* MINORI_CORE_BYTE_STREAM_H_ */
