/*
 * 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 "defines.hpp"
#include "stddef.hpp"
#include "types.hpp"
#include "typetraits.hpp"

namespace ox {

template<typename T>
[[nodiscard]]
constexpr T byteSwap(T const i) noexcept requires(sizeof(T) == 1) {
	return i;
}

template<typename T>
[[nodiscard]]
constexpr T byteSwap(T const i) noexcept requires(sizeof(T) == 2) {
	return static_cast<T>(i << 8) | static_cast<T>(i >> 8);
}

template<typename T>
[[nodiscard]]
constexpr T byteSwap(T const i) noexcept requires(sizeof(T) == 4) {
	return ((i >> 24) & 0x000000ff) |
	       ((i >>  8) & 0x0000ff00) |
	       ((i <<  8) & 0x00ff0000) |
	       ((i << 24) & 0xff000000);
}

template<typename T>
[[nodiscard]]
constexpr T byteSwap(T const i) noexcept requires(sizeof(T) == 8) {
	return ((i >> 56) & 0x00000000000000ff) |
	       ((i >> 40) & 0x000000000000ff00) |
	       ((i >> 24) & 0x0000000000ff0000) |
	       ((i >>  8) & 0x00000000ff000000) |
	       ((i <<  8) & 0x000000ff00000000) |
	       ((i << 24) & 0x0000ff0000000000) |
	       ((i << 40) & 0x00ff000000000000) |
	       ((i << 56) & 0xff00000000000000);
}


/**
 * Takes an int and byte swaps if the platform is the given condition is true.
 */
template<typename T, bool byteSwap>
[[nodiscard]]
constexpr T conditionalByteSwap(T i) noexcept {
	if constexpr(byteSwap) {
		return ox::byteSwap<T>(i);
	} else {
		return i;
	}
}

template<typename T>
[[nodiscard]]
constexpr T fromLittleEndian(T i) noexcept {
	return conditionalByteSwap<T, defines::BigEndian>(i);
}

template<typename T>
[[nodiscard]]
constexpr T fromBigEndian(T i) noexcept {
	return conditionalByteSwap<T, defines::LittleEndian>(i);
}

template<typename T>
[[nodiscard]]
constexpr T toLittleEndian(T i) noexcept {
	return conditionalByteSwap<T, defines::BigEndian>(i);
}

template<typename T>
[[nodiscard]]
constexpr T toBigEndian(T i) noexcept {
	return conditionalByteSwap<T, defines::LittleEndian>(i);
}

template<typename T, bool byteSwap>
class OX_PACKED ByteSwapInteger {
	private:
		T m_value;

	public:
		constexpr ByteSwapInteger() noexcept = default;

		constexpr ByteSwapInteger(const ByteSwapInteger &other) noexcept {
			m_value = other.m_value;
		}

		constexpr ByteSwapInteger(T value) noexcept: m_value(conditionalByteSwap<T, byteSwap>(value)) {
		}

		constexpr ByteSwapInteger &operator=(const ByteSwapInteger &other) noexcept {
			m_value = other.m_value;
			return *this;
		}

		constexpr ByteSwapInteger &operator=(T value) noexcept {
			m_value = conditionalByteSwap<T, byteSwap>(value);
			return *this;
		}

		constexpr operator T() const noexcept {
			return conditionalByteSwap<T, byteSwap>(m_value);
		}

		constexpr ByteSwapInteger operator+=(T other) noexcept {
			const auto newVal = static_cast<T>(*this + other);
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return *this;
		}

		constexpr ByteSwapInteger operator-=(T other) noexcept {
			const auto newVal = static_cast<T>(*this - other);
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return *this;
		}

		constexpr ByteSwapInteger operator*=(T other) noexcept {
			const auto newVal = static_cast<T>(*this * other);
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return *this;
		}

		constexpr ByteSwapInteger operator/=(T other) noexcept {
			const auto newVal = static_cast<T>(*this / other);
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return *this;
		}

		// Prefix increment
		constexpr ByteSwapInteger operator++() noexcept {
			return operator+=(1);
		}

		// Prefix decrement
		constexpr ByteSwapInteger operator--() noexcept {
			return operator-=(1);
		}

		template<typename I>
		constexpr T operator&=(I other) noexcept {
			auto newVal = *this & other;
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return newVal;
		}

		template<typename I>
		constexpr T operator|=(I other) noexcept {
			auto newVal = *this | other;
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return newVal;
		}

		template<typename I>
		constexpr T operator^=(I other) noexcept {
			auto newVal = *this ^ other;
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return newVal;
		}

		template<typename I>
		constexpr T operator>>=(I other) noexcept {
			auto newVal = *this >> other;
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return newVal;
		}

		template<typename I>
		constexpr T operator<<=(I other) noexcept {
			auto newVal = *this << other;
			m_value = conditionalByteSwap<T, byteSwap>(newVal);
			return newVal;
		}

		[[nodiscard]]
		constexpr auto get() const noexcept -> T {
			return static_cast<T>(*this);
		}

		/**
		 * Returns the integer as it is stored. If it is stored as little endian,
		 * a little endian integer is returned regardless of the endianness of
		 * the system.
		 */
		[[nodiscard]]
		constexpr auto raw() const noexcept -> T {
			return m_value;
		}

};

template<typename T>
using LittleEndian = ByteSwapInteger<T, defines::BigEndian>;

template<typename T>
using BigEndian = ByteSwapInteger<T, defines::LittleEndian>;

}
