#pragma once

#include <iterator>

#include "point.hpp"
#include "rectangle.hpp"

namespace devilution {

template <typename CoordT>
class PointsInRectangleIteratorBase {
public:
	using iterator_category = std::random_access_iterator_tag;
	using difference_type = CoordT;
	using value_type = PointOf<CoordT>;
	using pointer = void;
	using reference = value_type;

protected:
	constexpr PointsInRectangleIteratorBase(PointOf<CoordT> origin, int majorDimension, int majorIndex, int minorIndex)
	    : origin(origin)
	    , majorDimension(majorDimension)
	    , majorIndex(majorIndex)
	    , minorIndex(minorIndex)
	{
	}

	explicit constexpr PointsInRectangleIteratorBase(PointOf<CoordT> origin, int majorDimension, int index = 0)
	    : PointsInRectangleIteratorBase(origin, majorDimension, index / majorDimension, index % majorDimension)
	{
	}

	void Increment()
	{
		++minorIndex;
		if (minorIndex >= majorDimension) {
			++majorIndex;
			minorIndex -= majorDimension;
		}
	}

	void Decrement()
	{
		if (minorIndex <= 0) {
			--majorIndex;
			minorIndex += majorDimension;
		}
		--minorIndex;
	}

	void Offset(difference_type delta)
	{
		majorIndex += (minorIndex + delta) / majorDimension;
		minorIndex = (minorIndex + delta) % majorDimension;
		if (minorIndex < 0) {
			minorIndex += majorDimension;
			--majorIndex;
		}
	}

	PointOf<CoordT> origin;

	int majorDimension;

	int majorIndex;
	int minorIndex;
};

template <typename CoordT>
class PointsInRectangleRange {
public:
	using const_iterator = class PointsInRectangleIterator : public PointsInRectangleIteratorBase<CoordT> {
	public:
		using iterator_category = typename PointsInRectangleIteratorBase<CoordT>::iterator_category;
		using difference_type = typename PointsInRectangleIteratorBase<CoordT>::difference_type;
		using value_type = typename PointsInRectangleIteratorBase<CoordT>::value_type;
		using pointer = typename PointsInRectangleIteratorBase<CoordT>::pointer;
		using reference = typename PointsInRectangleIteratorBase<CoordT>::reference;

		constexpr PointsInRectangleIterator(RectangleOf<CoordT> region, int index = 0)
		    : PointsInRectangleIteratorBase<CoordT>(region.position, region.size.width, index)
		{
		}

		value_type operator*() const
		{
			// Row-major iteration e.g. {0, 0}, {1, 0}, {2, 0}, {0, 1}, {1, 1}, ...
			return this->origin + Displacement { this->minorIndex, this->majorIndex };
		}

		// Equality comparable concepts
		bool operator==(const PointsInRectangleIterator &rhs) const
		{
			return this->majorIndex == rhs.majorIndex && this->minorIndex == rhs.minorIndex;
		}

		bool operator!=(const PointsInRectangleIterator &rhs) const
		{
			return !(*this == rhs);
		}

		// Partially ordered concepts
		bool operator>=(const PointsInRectangleIterator &rhs) const
		{
			return this->majorIndex > rhs.majorIndex || (this->majorIndex == rhs.majorIndex && this->minorIndex >= rhs.minorIndex);
		}

		bool operator<(const PointsInRectangleIterator &rhs) const
		{
			return !(*this >= rhs);
		}

		bool operator<=(const PointsInRectangleIterator &rhs) const
		{
			return this->majorIndex < rhs.majorIndex || (this->majorIndex == rhs.majorIndex && this->minorIndex <= rhs.minorIndex);
		}

		bool operator>(const PointsInRectangleIterator &rhs) const
		{
			return !(*this <= rhs);
		}

		difference_type operator-(const PointsInRectangleIterator &rhs) const
		{
			return (this->majorIndex - rhs.majorIndex) * this->majorDimension + (this->minorIndex - rhs.minorIndex);
		}

		// Forward concepts
		PointsInRectangleIterator &operator++()
		{
			this->Increment();
			return *this;
		}

		PointsInRectangleIterator operator++(int)
		{
			auto copy = *this;
			++(*this);
			return copy;
		}

		// Bidirectional concepts
		PointsInRectangleIterator &operator--()
		{
			this->Decrement();
			return *this;
		}

		PointsInRectangleIterator operator--(int)
		{
			auto copy = *this;
			--(*this);
			return copy;
		}

		// Random access concepts
		PointsInRectangleIterator operator+(difference_type delta) const
		{
			auto copy = *this;
			return copy += delta;
		}

		PointsInRectangleIterator &operator+=(difference_type delta)
		{
			this->Offset(delta);
			return *this;
		}

		friend PointsInRectangleIterator operator+(difference_type delta, const PointsInRectangleIterator &it)
		{
			return it + delta;
		}

		PointsInRectangleIterator &operator-=(difference_type delta)
		{
			return *this += -delta;
		}

		PointsInRectangleIterator operator-(difference_type delta) const
		{
			auto copy = *this;
			return copy -= delta;
		}

		value_type operator[](difference_type offset) const
		{
			return **(this + offset);
		}
	};

	constexpr PointsInRectangleRange(RectangleOf<CoordT> region)
	    : region(region)
	{
	}

	[[nodiscard]] const_iterator cbegin() const
	{
		return region;
	}

	[[nodiscard]] const_iterator begin() const
	{
		return cbegin();
	}

	[[nodiscard]] const_iterator cend() const
	{
		return { region, region.size.width * region.size.height };
	}

	[[nodiscard]] const_iterator end() const
	{
		return cend();
	}

	[[nodiscard]] auto crbegin() const
	{
		// explicit type needed for older GCC versions
		return std::reverse_iterator<const_iterator>(cend());
	}

	[[nodiscard]] auto rbegin() const
	{
		return crbegin();
	}

	[[nodiscard]] auto crend() const
	{
		// explicit type needed for older GCC versions
		return std::reverse_iterator<const_iterator>(cbegin());
	}

	[[nodiscard]] auto rend() const
	{
		return crend();
	}

protected:
	RectangleOf<CoordT> region;
};

template <typename CoordT>
constexpr PointsInRectangleRange<CoordT> PointsInRectangle(RectangleOf<CoordT> region)
{
	return PointsInRectangleRange<CoordT> { region };
}

template <typename CoordT>
class PointsInRectangleColMajorRange {
public:
	using const_iterator = class PointsInRectangleIteratorColMajor : public PointsInRectangleIteratorBase<CoordT> {
	public:
		using iterator_category = typename PointsInRectangleIteratorBase<CoordT>::iterator_category;
		using difference_type = typename PointsInRectangleIteratorBase<CoordT>::difference_type;
		using value_type = typename PointsInRectangleIteratorBase<CoordT>::value_type;
		using pointer = typename PointsInRectangleIteratorBase<CoordT>::pointer;
		using reference = typename PointsInRectangleIteratorBase<CoordT>::reference;

		constexpr PointsInRectangleIteratorColMajor(RectangleOf<CoordT> region, int index = 0)
		    : PointsInRectangleIteratorBase<CoordT>(region.position, region.size.height, index)
		{
		}

		value_type operator*() const
		{
			// Col-major iteration e.g. {0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, ...
			return this->origin + Displacement { this->majorIndex, this->minorIndex };
		}

		// Equality comparable concepts
		bool operator==(const PointsInRectangleIteratorColMajor &rhs) const
		{
			return this->majorIndex == rhs.majorIndex && this->minorIndex == rhs.minorIndex;
		}

		bool operator!=(const PointsInRectangleIteratorColMajor &rhs) const
		{
			return !(*this == rhs);
		}

		// Partially ordered concepts
		bool operator>=(const PointsInRectangleIteratorColMajor &rhs) const
		{
			return this->majorIndex > rhs.majorIndex || (this->majorIndex == rhs.majorIndex && this->minorIndex >= rhs.minorIndex);
		}

		bool operator<(const PointsInRectangleIteratorColMajor &rhs) const
		{
			return !(*this >= rhs);
		}

		bool operator<=(const PointsInRectangleIteratorColMajor &rhs) const
		{
			return this->majorIndex < rhs.majorIndex || (this->majorIndex == rhs.majorIndex && this->minorIndex <= rhs.minorIndex);
		}

		bool operator>(const PointsInRectangleIteratorColMajor &rhs) const
		{
			return !(*this <= rhs);
		}

		difference_type operator-(const PointsInRectangleIteratorColMajor &rhs) const
		{
			return (this->majorIndex - rhs.majorIndex) * this->majorDimension + (this->minorIndex - rhs.minorIndex);
		}

		// Forward concepts
		PointsInRectangleIteratorColMajor &operator++()
		{
			this->Increment();
			return *this;
		}

		PointsInRectangleIteratorColMajor operator++(int)
		{
			auto copy = *this;
			++(*this);
			return copy;
		}

		// Bidirectional concepts
		PointsInRectangleIteratorColMajor &operator--()
		{
			this->Decrement();
			return *this;
		}

		PointsInRectangleIteratorColMajor operator--(int)
		{
			auto copy = *this;
			--(*this);
			return copy;
		}

		// Random access concepts
		PointsInRectangleIteratorColMajor operator+(difference_type delta) const
		{
			auto copy = *this;
			return copy += delta;
		}

		PointsInRectangleIteratorColMajor &operator+=(difference_type delta)
		{
			this->Offset(delta);
			return *this;
		}

		friend PointsInRectangleIteratorColMajor operator+(difference_type delta, const PointsInRectangleIteratorColMajor &it)
		{
			return it + delta;
		}

		PointsInRectangleIteratorColMajor &operator-=(difference_type delta)
		{
			return *this += -delta;
		}

		PointsInRectangleIteratorColMajor operator-(difference_type delta) const
		{
			auto copy = *this;
			return copy -= delta;
		}

		value_type operator[](difference_type offset) const
		{
			return **(this + offset);
		}
	};

	// gcc6 needs a defined constructor?
	constexpr PointsInRectangleColMajorRange(RectangleOf<CoordT> region)
	    : region(region)
	{
	}

	[[nodiscard]] const_iterator cbegin() const
	{
		return region;
	}

	[[nodiscard]] const_iterator begin() const
	{
		return cbegin();
	}

	[[nodiscard]] const_iterator cend() const
	{
		return { region, region.size.width * region.size.height };
	}

	[[nodiscard]] const_iterator end() const
	{
		return cend();
	}

	[[nodiscard]] auto crbegin() const
	{
		// explicit type needed for older GCC versions
		return std::reverse_iterator<const_iterator>(cend());
	}

	[[nodiscard]] auto rbegin() const
	{
		return crbegin();
	}

	[[nodiscard]] auto crend() const
	{
		// explicit type needed for older GCC versions
		return std::reverse_iterator<const_iterator>(cbegin());
	}

	[[nodiscard]] auto rend() const
	{
		return crend();
	}

protected:
	RectangleOf<CoordT> region;
};

template <typename CoordT = int>
constexpr PointsInRectangleColMajorRange<CoordT> PointsInRectangleColMajor(RectangleOf<CoordT> region)
{
	return PointsInRectangleColMajorRange<CoordT> { region };
}

} // namespace devilution
