/*
 * 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 "bit.hpp"
#include "def.hpp"
#include "error.hpp"
#include "initializerlist.hpp"
#include "iterator.hpp"
#include "math.hpp"
#include "new.hpp"
#include "types.hpp"
#include "utility.hpp"

OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)

namespace ox {

template<typename T, std::size_t ArraySize>
class Array {

	public:
		using value_type = T;
		using size_type = std::size_t;

		template<typename RefType = T&, typename PtrType = T*, bool reverse = false>
		using iterator = SpanIterator<T, RefType, PtrType, reverse>;

	private:
		T m_items[ArraySize]{};

	public:
		constexpr Array() noexcept = default;

		template<typename ...Args>
		constexpr Array(Args ...list) noexcept: m_items{std::move(list)...} {
		}

		constexpr Array(std::initializer_list<T> list) noexcept;

		constexpr Array(const Array &other);

		constexpr Array(Array &&other) noexcept;

		constexpr ~Array() = default;

		constexpr iterator<> begin() noexcept {
			return iterator<>(&m_items[0], 0, ArraySize);
		}

		constexpr iterator<> end() noexcept {
			return iterator<>(&m_items[0], ArraySize, ArraySize);
		}

		constexpr iterator<const T&, const T*> begin() const noexcept {
			return iterator<const T&, const T*>(&m_items[0], 0, ArraySize);
		}

		constexpr iterator<const T&, const T*> end() const noexcept {
			return iterator<const T&, const T*>(&m_items[0], ArraySize, ArraySize);
		}

		constexpr iterator<T&, T*, true> rbegin() noexcept {
			return iterator<T&, T*, true>(&m_items[0], ArraySize - 1, ArraySize);
		}

		constexpr iterator<T&, T*, true> rend() noexcept {
			return iterator<T&, T*, true>(&m_items[0], MaxValue<size_type>, ArraySize);
		}

		constexpr iterator<const T&, const T*, true> rbegin() const noexcept {
			return iterator<const T&, const T*, true>(m_items, ArraySize - 1, ArraySize);
		}

		constexpr iterator<const T&, const T*, true> rend() const noexcept {
			return iterator<const T&, const T*, true>(m_items, MaxValue<size_type>, ArraySize);
		}

		constexpr bool operator==(const Array &other) const;

		constexpr Array &operator=(const Array &other);

		constexpr Array &operator=(Array &&other) noexcept;

		[[nodiscard]]
		constexpr T &operator[](std::size_t i) noexcept;

		[[nodiscard]]
		constexpr const T &operator[](std::size_t i) const noexcept;

		[[nodiscard]]
		constexpr std::size_t size() const noexcept;

		[[nodiscard]]
		constexpr T *data() noexcept {
			return m_items;
		}

		[[nodiscard]]
		constexpr const T *data() const noexcept {
			return m_items;
		}

		[[nodiscard]]
		constexpr bool contains(const T&) const;

};

template<typename T, std::size_t ArraySize, typename RefType, bool reverse>
using ArrayIt = typename Array<T, ArraySize>::template iterator<RefType, reverse>;

template<typename T, std::size_t ArraySize, typename RefType, bool reverse>
constexpr ArrayIt<T, ArraySize, RefType, reverse> operator+(std::size_t n, const ArrayIt<T, ArraySize, RefType, reverse> &a) {
	return a + n;
}

template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize>::Array(std::initializer_list<T> list) noexcept {
	for (auto i = 0ul; auto &item : list) {
		this->operator[](i) = item;
		++i;
	}
}

template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize>::Array(const Array &other) {
	for (std::size_t i = 0; i < ArraySize; ++i) {
		m_items[i] = T(other.m_items[i]);
	}
}

template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize>::Array(Array &&other) noexcept {
	if (this != &other) {
		for (std::size_t i = 0; i < ArraySize; ++i) {
			m_items[i] = T(std::move(other.m_items[i]));
		}
	}
}

template<typename T, std::size_t ArraySize>
constexpr bool Array<T, ArraySize>::operator==(const Array &other) const {
	if (std::is_constant_evaluated()) {
		for (std::size_t i = 0; i < ArraySize; i++) {
			if (!(m_items[i] == other.m_items[i])) {
				return false;
			}
		}
		return true;
	} else {
		return memcmp(this, &other, sizeof(*this)) == 0;
	}
}

template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize> &Array<T, ArraySize>::operator=(const Array &other) {
	if (this != &other) {
		for (std::size_t i = 0; i < ArraySize; ++i) {
			m_items[i] = other.m_items[i];
		}
	}
	return *this;
	return *this;
}

template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize> &Array<T, ArraySize>::operator=(Array &&other) noexcept {
	if (this != &other) {
		for (std::size_t i = 0; i < ArraySize; ++i) {
			m_items[i] = std::move(other.m_items[i]);
		}
	}
	return *this;
}

template<typename T, std::size_t ArraySize>
constexpr T &Array<T, ArraySize>::operator[](std::size_t i) noexcept {
	boundsCheck(i, size(), "Array access overflow");
	return m_items[i];
}

template<typename T, std::size_t ArraySize>
constexpr const T &Array<T, ArraySize>::operator[](std::size_t i) const noexcept {
	boundsCheck(i, size(), "Array access overflow");
	return m_items[i];
}

template<typename T, std::size_t ArraySize>
constexpr std::size_t Array<T, ArraySize>::size() const noexcept {
	return ArraySize;
}

template<typename T, std::size_t ArraySize>
constexpr bool Array<T, ArraySize>::contains(const T &v) const {
	for (std::size_t i = 0; i < ArraySize; i++) {
		if (m_items[i] == v) {
			return true;
		}
	}
	return false;
}

}

OX_CLANG_NOWARN_END
