#pragma once

#include "../core/utils.hpp"
#include "../data/event_base.hpp"

namespace dv {

/**
 * The EventColor enum contains the color of the Bayer color filter for a specific event address.
 * WHITE means White/No Filter.
 * Please take into account that there are usually twice as many green pixels as there are red or blue ones.
 */
enum class EventColor : uint8_t {
	WHITE  = 0,
	RED    = 1,
	GREEN1 = 2,
	BLUE   = 3,
	GREEN2 = 4,
};

inline std::ostream &operator<<(std::ostream &os, const EventColor &var) {
	switch (var) {
		case EventColor::WHITE:
			return os << "WHITE";

		case EventColor::RED:
			return os << "RED";

		case EventColor::GREEN1:
			return os << "GREEN1";

		case EventColor::BLUE:
			return os << "BLUE";

		case EventColor::GREEN2:
			return os << "GREEN2";
	}

	return os;
}

/**
 * Color pixel block arrangement on the sensor. The sensor usually contain one red, one blue, and two green pixels.
 * They can be arranged in different order, so exact color extraction, the pixel arrangement needs to be known.
 */
enum class PixelArrangement : uint8_t {
	MONO = 0, //!< No color filter present, all light passes.
	RGBG = 1, //!< Standard Bayer color filter, 1 red 2 green 1 blue. Variation 1.
	GRGB = 2, //!< Standard Bayer color filter, 1 red 2 green 1 blue. Variation 2.
	GBGR = 3, //!< Standard Bayer color filter, 1 red 2 green 1 blue. Variation 3.
	BGRG = 4, //!< Standard Bayer color filter, 1 red 2 green 1 blue. Variation 4.
};

inline std::ostream &operator<<(std::ostream &os, const PixelArrangement &var) {
	switch (var) {
		case PixelArrangement::MONO:
			return os << "MONO";

		case PixelArrangement::RGBG:
			return os << "RGBG";

		case PixelArrangement::GRGB:
			return os << "GRGB";

		case PixelArrangement::GBGR:
			return os << "GBGR";

		case PixelArrangement::BGRG:
			return os << "BGRG";
	}

	return os;
}

/**
 * Address to Color mapping for events based on Bayer filter.
 */
static constexpr std::array<std::array<EventColor, 4>, 5> PIXEL_COLOR_KEYS{
	{
     {EventColor::WHITE, EventColor::WHITE, EventColor::WHITE, EventColor::WHITE},
     {EventColor::RED, EventColor::GREEN1, EventColor::GREEN2, EventColor::BLUE},
     {EventColor::GREEN1, EventColor::RED, EventColor::BLUE, EventColor::GREEN2},
     {EventColor::GREEN2, EventColor::BLUE, EventColor::RED, EventColor::GREEN1},
     {EventColor::BLUE, EventColor::GREEN2, EventColor::GREEN1, EventColor::RED},
	 }
};

/**
 * Determine the color of the Bayer color filter for a specific event, based on its address.
 * Please take into account that there are usually twice as many green pixels as there are red or blue ones.
 *
 * @param event event to determine filter color for.
 * @param pixelArrangement color pixel arrangement for a sensor.
 *
 * @return filter color.
 */
[[nodiscard]] inline EventColor colorForEvent(
	const dv::Event &event, const PixelArrangement pixelArrangement = PixelArrangement::MONO) {
	if (pixelArrangement == PixelArrangement::MONO) {
		// No Filter.
		return EventColor::WHITE;
	}

	const auto x   = static_cast<uint32_t>(event.x());
	const auto y   = static_cast<uint32_t>(event.y());
	const auto idx = static_cast<size_t>(((y & 0x01) << 1) | (x & 0x01));

	return PIXEL_COLOR_KEYS[dv::EnumAsInteger(pixelArrangement)][idx];
}

/**
 * Determine the color of the Bayer color filter for specific pixel coordinates.
 * Please take into account that there are usually twice as many green pixels as there are red or blue ones.
 *
 * @param pixelCoordinates position to determine filter color for.
 * @param pixelArrangement color pixel arrangement for a sensor.
 *
 * @return filter color.
 */
[[nodiscard]] inline EventColor colorForPoint(
	const cv::Point pixelCoordinates, const PixelArrangement pixelArrangement = PixelArrangement::MONO) {
	if (pixelArrangement == PixelArrangement::MONO) {
		// No Filter.
		return EventColor::WHITE;
	}

	const auto x   = static_cast<uint32_t>(pixelCoordinates.x);
	const auto y   = static_cast<uint32_t>(pixelCoordinates.y);
	const auto idx = static_cast<size_t>(((y & 0x01) << 1) | (x & 0x01));

	return PIXEL_COLOR_KEYS[dv::EnumAsInteger(pixelArrangement)][idx];
}

} // namespace dv

// fmt compatibility for enum class printing.
template<>
struct fmt::formatter<dv::EventColor> : fmt::ostream_formatter {};

template<>
struct fmt::formatter<dv::PixelArrangement> : fmt::ostream_formatter {};
