#pragma once

#include "../../../core/dvassert.hpp"
#include "parser.hpp"

#include <boost/endian.hpp>

namespace dv::io::camera::parser::DVS128 {

static constexpr int16_t WIDTH{128};
static constexpr int16_t HEIGHT{128};

class Parser final : public ParserBase {
public:
	Parser() = default;

	[[nodiscard]] bool getDVSFlipHorizontal() const {
		return !mDvs.flipX;
	}

	void setDVSFlipHorizontal(const bool flip_x) {
		// Default orientation is flipped.
		mDvs.flipX = !flip_x;
	}

	[[nodiscard]] bool getDVSFlipVertical() const {
		return !mDvs.flipY;
	}

	void setDVSFlipVertical(const bool flip_y) {
		// Default orientation is flipped.
		mDvs.flipY = !flip_y;
	}

	[[nodiscard]] bool getDVSConvertAllOn() const {
		return mDvs.convertAllOn;
	}

	void setDVSConvertAllOn(const bool convertAllOn) {
		mDvs.convertAllOn = convertAllOn;
	}

	void parseData(const std::span<const uint8_t> buffer, [[maybe_unused]] const bool dataLost) override {
		for (size_t bufferPos = 0; bufferPos < buffer.size(); bufferPos += 4) {
			if ((buffer[bufferPos + 3] & DVS128_TIMESTAMP_WRAP_MASK) == DVS128_TIMESTAMP_WRAP_MASK) {
				mLogger(true, "Timestamp wrap event received.");

				// timestamp bit 15 is one -> wrap: now we need to increment
				// the wrapAdd, uses only 14 bit timestamps. Each wrap is 2^14 µs (~16ms).
				mTimestamps.wrapAdd += TS_WRAP_ADD;

				updateTimestamp<false>(mTimestamps.wrapAdd);
			}
			else if ((buffer[bufferPos + 3] & DVS128_TIMESTAMP_RESET_MASK) == DVS128_TIMESTAMP_RESET_MASK) {
				mLogger(true, "Timestamp reset event received.");

				// timestamp bit 14 is one -> wrapAdd reset: this firmware
				// version uses reset events to reset timestamps
				mTimestamps.wrapAdd = 0;

				// Let's keep timestamping simple: wait for a TS_RESET to initialize.
				timestampInit(std::chrono::microseconds::zero());
			}
			else {
				// address is LSB MSB (USB is LE)
				const uint16_t addressUSB = boost::endian::load_little_u16(&buffer[bufferPos]);

				// same for timestamp, LSB MSB (USB is LE)
				// 15 bit value of timestamp in 1 us tick
				const uint16_t timestampUSB = boost::endian::load_little_u16(&buffer[bufferPos + 2]);

				// Expand to 32 bits. (Tick is 1µs already.)
				updateTimestamp<false>(mTimestamps.wrapAdd + timestampUSB);

				if ((addressUSB & DVS128_SYNC_EVENT_MASK) != 0) {
					// Special Trigger Event (MSB is set)
					mLogger(true, "External input (rising edge) event received.");

					mBuffers.triggers.elements.emplace_back(
						getCurrentTimestamp(), dv::TriggerType::EXTERNAL_SIGNAL_RISING_EDGE);
				}
				else {
					// Invert X values (flip along X axis). To correct for flipped camera.
					auto x = static_cast<uint16_t>((addressUSB >> DVS128_X_ADDR_SHIFT) & DVS128_X_ADDR_MASK);
					if (mDvs.flipX) {
						x = (WIDTH - 1) - x;
					}

					// Invert Y values (flip along Y axis). To convert to CG format.
					auto y = static_cast<uint16_t>((addressUSB >> DVS128_Y_ADDR_SHIFT) & DVS128_Y_ADDR_MASK);
					if (mDvs.flipY) {
						y = (HEIGHT - 1) - y;
					}

					// Invert polarity bit. Hardware is like this.
					const bool polarity
						= mDvs.convertAllOn
					   || ((static_cast<uint16_t>(addressUSB >> DVS128_POLARITY_SHIFT) & DVS128_POLARITY_MASK) == 0);

					// Check range conformity.
					dv::runtime_assert(
						[x] {
							return x < WIDTH;
						},
						[x] {
							return fmt::format("DVS: X address out of range (0-{}): {}", (WIDTH - 1), x);
						});
					dv::runtime_assert(
						[y] {
							return y < HEIGHT;
						},
						[y] {
							return fmt::format("DVS: Y address out of range (0-{}): {}", (HEIGHT - 1), y);
						});

					mBuffers.events.elements.emplace_back(getCurrentTimestamp(), x, y, polarity);
				}
			}
		}
	}

private:
	static constexpr uint8_t DVS128_TIMESTAMP_WRAP_MASK{0x80};
	static constexpr uint8_t DVS128_TIMESTAMP_RESET_MASK{0x40};
	static constexpr uint8_t DVS128_POLARITY_SHIFT{0};
	static constexpr uint16_t DVS128_POLARITY_MASK{0x0001};
	static constexpr uint8_t DVS128_Y_ADDR_SHIFT{8};
	static constexpr uint16_t DVS128_Y_ADDR_MASK{0x007F};
	static constexpr uint8_t DVS128_X_ADDR_SHIFT{1};
	static constexpr uint16_t DVS128_X_ADDR_MASK{0x007F};
	static constexpr uint16_t DVS128_SYNC_EVENT_MASK{0x8000};
	static constexpr uint16_t TS_WRAP_ADD{0x4000};

	struct {
		int64_t wrapAdd{0};
	} mTimestamps;

	struct {
		// DVS specific fields
		std::atomic<bool> flipX{true}; // Default orientation is flipped.
		std::atomic<bool> flipY{true}; // Default orientation is flipped.
		std::atomic<bool> convertAllOn{false};
	} mDvs;
};

} // namespace dv::io::camera::parser::DVS128
