#pragma once

#include "matrix.hpp"

template <typename Element>
Matrix<Element>::Matrix(const Bounds &bounds) noexcept
	: _matrix(new Element *[bounds.get_height()]),
	  _row_cnt(bounds.get_height()),
	  _column_cnt(bounds.get_width())
{
	for (gsl::owner<Element **> row = _matrix; row != _matrix + _row_cnt; row++)
	{
		*row = static_cast<gsl::owner<Element *>>(new Element[bounds.get_width()]);
	}
};

template <typename Element>
Matrix<Element>::Matrix(const Matrix &matrix) noexcept
	: _matrix(new Element *[matrix._row_cnt]),
	  _row_cnt(matrix._row_cnt),
	  _column_cnt(matrix._column_cnt)
{
	_copy_matrix_from(matrix);
}

template <typename Element>
Matrix<Element>::Matrix(Matrix &&matrix) noexcept
	: _matrix(matrix._matrix), _row_cnt(matrix._row_cnt), _column_cnt(matrix._column_cnt)
{
	matrix._matrix = nullptr;
}

template <typename Element>
Matrix<Element>::~Matrix() noexcept
{
	_delete_matrix();
}

template <typename Element>
void Matrix<Element>::fill(Element element) noexcept
{
	for (auto row : *this)
	{
		for (auto &col : row)
		{
			col = element;
		}
	}
}

template <typename Element>
auto Matrix<Element>::get(const Vector2 &pos) const noexcept -> Element
{

	auto x = static_cast<std::size_t>(pos.get_x());
	auto y = static_cast<std::size_t>(pos.get_y());

	// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
	return _matrix[y][x];
}

template <typename Element>
void Matrix<Element>::set(const Vector2 &pos, Element element) noexcept
{
	auto x = static_cast<std::size_t>(pos.get_x());
	auto y = static_cast<std::size_t>(pos.get_y());

	// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
	_matrix[y][x] = element;
}

template <typename Element>
auto Matrix<Element>::get_row_cnt() const noexcept -> uint32_t
{
	return _row_cnt;
}

template <typename Element>
auto Matrix<Element>::get_column_cnt() const noexcept -> uint32_t
{
	return _column_cnt;
}

template <typename Element>
auto Matrix<Element>::begin() const noexcept -> MatrixIterator<Element>
{
	return MatrixIterator(_matrix, _column_cnt);
}

template <typename Element>
auto Matrix<Element>::end() const noexcept -> MatrixIterator<Element>
{
	return MatrixIterator(_matrix + _row_cnt, _column_cnt);
}

template <typename Element>
auto Matrix<Element>::operator=(const Matrix &rhs) noexcept -> Matrix<Element> &
{
	if (&rhs != this)
	{
		_delete_matrix();

		// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
		_matrix = nullptr;

		_matrix = new Element *[rhs._row_cnt];
		_copy_matrix_from(rhs);
	}

	return *this;
}

template <typename Element>
auto Matrix<Element>::operator=(Matrix &&rhs) noexcept -> Matrix<Element> &
{
	if (&rhs != this)
	{
		_delete_matrix();

		// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
		_matrix = rhs._matrix;

		rhs._matrix = nullptr;
	}

	return *this;
}

template <typename Element>
void Matrix<Element>::_delete_matrix() noexcept
{
	for (gsl::owner<Element **> row = _matrix; row != _matrix + _row_cnt; row++)
	{
		delete[](*row);
	}

	delete[] _matrix;
}

template <typename Element>
void Matrix<Element>::_copy_matrix_from(const Matrix &source) noexcept
{
	// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
	gsl::owner<Element **> source_row = source._matrix;

	for (gsl::owner<Element **> row = _matrix; row != _matrix + _row_cnt;
		 row++, source_row++)
	{
		*row = static_cast<gsl::owner<Element *>>(new Element[_column_cnt]);

		// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
		auto *end = *source_row + _column_cnt;

		std::copy(*source_row, end, *row);
	}
}