#pragma once

#include <cassert>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <algorithm>

namespace amt {

	namespace detail {
		[[nodiscard]] constexpr auto cal_index(
							 std::size_t r,
							 std::size_t c,
			[[maybe_unused]] std::size_t rs,
			[[maybe_unused]] std::size_t cs
		) -> std::size_t {
			return r * cs + c;
		}
	}

	template <typename T>
	struct Matrix {
		using value_type = T;
		using pointer = value_type*;
		using const_pointer = value_type const*;
		using reference = value_type&;
		using const_reference = value_type const&;
		using iterator = pointer;
		using const_iterator = const_pointer;
		using reverse_iterator = std::reverse_iterator<iterator>;
		using const_reverse_iterator = std::reverse_iterator<const_iterator>;
		using difference_type = std::ptrdiff_t;
		using size_type = std::size_t;

		template <bool IsConst>
		struct View {
			using base_type = std::conditional_t<IsConst, const_pointer, pointer>;
			base_type data;
			size_type r;
			size_type rows;
			size_type cols;

			constexpr reference operator[](size_type c) noexcept requires (!IsConst) {
				assert(c < cols && "Out of bound access");
				auto const index = detail::cal_index(r, c, rows, cols);
				return data[index];
			}

			constexpr const_reference operator[](size_type c) const noexcept {
				assert(c < cols && "Out of bound access");
				auto const index = detail::cal_index(r, c, rows, cols);
				return data[index];
			}
		};

		constexpr Matrix() noexcept = default;
		Matrix(Matrix const& other)
			: Matrix(other.rows(), other.cols())
		{
			std::copy(other.begin(), other.end(), begin());
		}
		Matrix& operator=(Matrix const& other) {
			if (this == &other) return *this;
			auto temp = Matrix(other);
			swap(temp, *this);
			return *this;
		}
		constexpr Matrix(Matrix && other) noexcept
			: m_data(other.m_data)
			, m_row(other.m_row)
			, m_col(other.m_col)
		{
			other.m_data = nullptr;
		}
		constexpr Matrix& operator=(Matrix && other) noexcept {
			if (this == &other) return *this;
			swap(*this, other);
			return *this;
		}
		~Matrix() {
			if (m_data) delete[] m_data;
		}


		Matrix(size_type row, size_type col)
			: m_data(new value_type[row * col])
			, m_row(row)
			, m_col(col)
		{}

		Matrix(size_type row, size_type col, value_type def)
			: Matrix(row, col)
		{
			std::fill(begin(), end(), def);
		}

		Matrix(std::initializer_list<std::initializer_list<value_type>> li)
			: m_row(li.size())
		{
			for (auto const& row: li) {
				m_col = std::max(m_col, row.size());
			}

			auto const size = m_row * m_col;

			if (size == 0) return;

			m_data = new value_type[size];
			std::fill_n(m_data, size, 0);

			for (auto r = 0ul; auto const& row: li) {
				for (auto c = 0ul; auto const& col: row) {
					this->operator()(r, c++) = col;
				}
				++r;
			}
		}

		constexpr bool empty() const noexcept { return size() == 0; }
		constexpr size_type size() const noexcept { return rows() * cols(); }
		constexpr size_type rows() const noexcept { return m_row; }
		constexpr size_type cols() const noexcept { return m_col; }
		constexpr auto data() noexcept -> pointer { return m_data; }
		constexpr auto data() const noexcept -> const_pointer { return m_data; }

		constexpr iterator begin() noexcept { return m_data; }
		constexpr iterator end() noexcept { return m_data + size(); }
		constexpr const_iterator begin() const noexcept { return m_data; }
		constexpr const_iterator end() const noexcept { return m_data + size(); }
		constexpr reverse_iterator rbegin() noexcept { return std::reverse_iterator(end()); }
		constexpr reverse_iterator rend() noexcept { return std::reverse_iterator(begin()); }
		constexpr const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator(end()); }
		constexpr const_reverse_iterator rend() const noexcept { return std::reverse_iterator(begin()); }

		constexpr auto operator()(size_type r, size_type c) noexcept -> reference {
			auto const index = detail::cal_index(r, c, rows(), cols());
			assert(index < size() && "Out of bound access");
			return m_data[index];
		}

		constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference {
			auto const index = detail::cal_index(r, c, rows(), cols());
			assert(index < size() && "Out of bound access");
			return m_data[index];
		}

		constexpr auto operator[](size_type r) noexcept -> View<false> {
			assert(r < rows() && "Out of bound access");
			return { .data = m_data, .r = r, .rows = m_row, .cols = m_col };
		}

		constexpr auto operator[](size_type r) const noexcept -> View<true> {
			assert(r < rows() && "Out of bound access");
			return { .data = m_data, .r = r, .rows = m_row, .cols = m_col };
		}

		friend void swap(Matrix& lhs, Matrix& rhs) noexcept {
			using std::swap;
			swap(lhs.m_data, rhs.m_data);
			swap(lhs.m_row, rhs.m_row);
			swap(lhs.m_col, rhs.m_col);
		}

	private:
		pointer	  m_data{};
		size_type m_row{};
		size_type m_col{};
	};

} // namespace amt

#if 0
#include <format>
namespace std {
	template <typename T>
	struct formatter<amt::Matrix<T>> {
		constexpr auto parse(format_parse_context& ctx) {
			return ctx.begin();
		}

		auto format(amt::Matrix<T> const& m, auto& ctx) const {
			std::string s = "[\n";
			for (auto r = std::size_t{}; r < m.rows(); ++r) {
				for (auto c = std::size_t{}; c < m.cols(); ++c) {
					s += std::format("{}, ", m(r, c));
				}
				s += '\n';
			}
			s += "]";
			return format_to(ctx.out(), "{}", s);
		}
	};

} // namespace std
#endif
