/*
* Copyright 2020 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "BitMatrix.h"
#include "Point.h"
#include "StdGenerator.h"

#include <climits>

namespace ZXing {

enum class Direction { LEFT = -1, RIGHT = 1 };

inline Direction opposite(Direction dir) noexcept
{
	return dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT;
}

/**
 * @brief The BitMatrixCursor represents a current position inside an image and current direction it can advance towards.
 *
 * The current position and direction is a PointT<T>. So depending on the type it can be used to traverse the image
 * in a Bresenham style (PointF) or in a discrete way (step only horizontal/vertical/diagonal (PointI)).
 */
template<typename POINT>
class BitMatrixCursor
{
	using this_t = BitMatrixCursor<POINT>;

public:
	const BitMatrix* img;

	POINT p; // current position
	POINT d; // current direction

	BitMatrixCursor(const BitMatrix& image, POINT p, POINT d) : img(&image), p(p) { setDirection(d); }

	class Value
	{
		enum { INVALID = -1, WHITE = 0, BLACK = 1 };
		int v = INVALID;
	public:
		Value() = default;
		Value(bool isBlack) : v(isBlack) {}
		bool isValid() const noexcept { return v != INVALID; }
		bool isWhite() const noexcept { return v == WHITE; }
		bool isBlack() const noexcept { return v == BLACK; }

		operator bool() const noexcept { return isValid(); }

		bool operator==(Value o) const { return v == o.v; }
	};

	template <typename T>
	Value testAt(PointT<T> p) const
	{
		return img->isIn(p) ? Value{img->get(p)} : Value{};
	}

	bool blackAt(POINT pos) const noexcept { return testAt(pos).isBlack(); }
	bool whiteAt(POINT pos) const noexcept { return testAt(pos).isWhite(); }

	bool isIn(POINT p) const noexcept { return img->isIn(p); }
	bool isIn() const noexcept { return isIn(p); }
	bool isBlack() const noexcept { return blackAt(p); }
	bool isWhite() const noexcept { return whiteAt(p); }

	POINT front() const noexcept { return d; }
	POINT back() const noexcept { return -d; }
	POINT left() const noexcept { return ZXing::left(d); }
	POINT right() const noexcept { return ZXing::right(d); }
	POINT direction(Direction dir) const noexcept { return static_cast<int>(dir) * right(); }

	void turnBack() noexcept { d = back(); }
	void turnLeft() noexcept { d = left(); }
	void turnRight() noexcept { d = right(); }
	void turn(Direction dir) noexcept { d = direction(dir); }

	Value edgeAt(POINT d) const noexcept
	{
		Value v = testAt(p);
		return testAt(p + d) != v ? v : Value();
	}

	Value edgeAtFront() const noexcept { return edgeAt(front()); }
	Value edgeAtBack() const noexcept { return edgeAt(back()); }
	Value edgeAtLeft() const noexcept { return edgeAt(left()); }
	Value edgeAtRight() const noexcept { return edgeAt(right()); }
	Value edgeAt(Direction dir) const noexcept { return edgeAt(direction(dir)); }

	this_t& setDirection(PointF dir) { return d = bresenhamDirection(dir), *this; }
	this_t& setDirection(PointI dir) { return d = dir, *this; }

	bool step(typename POINT::value_t s = 1)
	{
		p += s * d;
		return isIn(p);
	}

	this_t movedBy(POINT o) const noexcept { return {*img, p + o, d}; }
	this_t turnedBack() const noexcept { return {*img, p, back()}; }

	/**
	 * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge.
	 * @param nth number of edges to pass
	 * @param range max number of steps to take
	 * @param backup whether or not to backup one step so we land in front of the edge
	 * @return number of steps taken or 0 if moved outside of range/image
	 */
	int stepToEdge(int nth = 1, int range = 0, bool backup = false)
	{
		int steps = 0;
		auto lv = testAt(p);

		while (nth && (!range || steps < range) && lv.isValid()) {
			++steps;
			auto v = testAt(p + steps * d);
			if (lv != v) {
				lv = v;
				--nth;
			}
		}
		if (backup)
			--steps;
		p += steps * d;
		return steps * (nth == 0);
	}

	bool stepAlongEdge(Direction dir, bool skipCorner = false)
	{
		if (!edgeAt(dir))
			turn(dir);
		else if (edgeAtFront()) {
			turn(opposite(dir));
			if (edgeAtFront()) {
				turn(opposite(dir));
				if (edgeAtFront())
					return false;
			}
		}

		bool ret = step();

		if (ret && skipCorner && !edgeAt(dir)) {
			turn(dir);
			ret = step();
		}

		return ret;
	}

	int countEdges(int range)
	{
		int res = 0;

		while (int steps = range ? stepToEdge(1, range) : 0) {
			range -= steps;
			++res;
		}

		return res;
	}

	template<typename ARRAY>
	ARRAY readPattern(int max = 0, int min = 0)
	{
		ARRAY res = {};
		for (auto& i : res) {
			i = stepToEdge(1, max);
			if (!i)
				return res;
			if (max)
				max -= i;
		}
		if (min && max) {
			min -= Reduce(res);
			int steps = -1;
			while (min > 0 && max && (steps = stepToEdge(2, max))) {
				max -= steps;
				min -= steps;
			}
		}
		return res;
	}

	template<typename ARRAY>
	ARRAY readPatternFromBlack(int maxWhitePrefix, int max = 0, int min = 0)
	{
		if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix))
			return {};
		return readPattern<ARRAY>(max, min);
	}
};

using BitMatrixCursorF = BitMatrixCursor<PointF>;
using BitMatrixCursorI = BitMatrixCursor<PointI>;

class FastEdgeToEdgeCounter
{
	const uint8_t* p = nullptr;
	int stride = 0;
	int stepsToBorder = 0;

public:
	FastEdgeToEdgeCounter(const BitMatrixCursorI& cur)
	{
		stride = cur.d.y * cur.img->width() + cur.d.x;
		p = cur.img->row(cur.p.y).begin() + cur.p.x;

		int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX;
		int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX;
		stepsToBorder = std::min(maxStepsX, maxStepsY);
	}

	int stepToNextEdge(int range)
	{
		int maxSteps = std::min(stepsToBorder, range);
		int steps = 0;
		do {
			if (++steps > maxSteps) {
				if (maxSteps == stepsToBorder)
					break;
				else
					return 0;
			}
		} while (p[steps * stride] == p[0]);

		p += steps * stride;
		stepsToBorder -= steps;

		return steps;
	}
};

// Generate points in a spiral pattern around the center
inline std::generator<PointI> Spiral(int radius)
{
	co_yield{0, 0};
	for (int r = 1; r <= radius; ++r) {
		// clang-format off
		for (int k = 0; k < 2 * r; ++k) co_yield{           r, -(r - 1) + k}; // right -> down
		for (int k = 0; k < 2 * r; ++k)	co_yield{ (r - 1) - k,            r}; // bottom -> left
		for (int k = 0; k < 2 * r; ++k)	co_yield{          -r,  (r - 1) - k}; // left -> up
		for (int k = 0; k < 2 * r; ++k)	co_yield{-(r - 1) + k,           -r}; // top -> right
		// clang-format on
	}
}

} // ZXing
