#include "IrFoxDecoder.h"
#include <AnalyzerResults.h>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>

void IrFoxDecoder::reset()
{
	*this = IrFoxDecoder{};
	rise_sync_time_us = irfox::kBitTimeUs;
	next_control_bit = irfox::kBitPerByte;
	have_last_processed = false;
	last_processed_edge_us = 0;
}

uint16_t IrFoxDecoder::ceil_div_u16(uint16_t val, uint16_t divider)
{
	if (divider == 0)
		return 0;
	int ret = val / divider;
	if ((val << 4) / divider - (ret << 4) >= 8)
		ret++;
	return static_cast<uint16_t>(ret);
}

uint8_t IrFoxDecoder::crc8(const uint8_t* data, uint8_t start, uint8_t end, uint8_t poly)
{
	uint8_t crc = 0xff;
	for (size_t i = start; i < end; i++)
	{
		crc ^= data[i];
		for (size_t j = 0; j < 8; j++)
		{
			if ((crc & 0x80) != 0)
				crc = static_cast<uint8_t>((crc << 1) ^ poly);
			else
				crc <<= 1;
		}
	}
	return crc;
}

bool IrFoxDecoder::crc_check(uint8_t len, uint16_t& crc_out)
{
	crc_out = 0;
	crc_out = static_cast<uint16_t>(static_cast<uint16_t>(crc8(data_buffer, 0, len, irfox::kPoly1) << 8) & 0xFF00u);
	crc_out = static_cast<uint16_t>(crc_out | (crc8(data_buffer, 0, static_cast<uint8_t>(len + 1), irfox::kPoly2) & 0xFFu));

	const bool ok = (data_buffer[len] == static_cast<uint8_t>((crc_out >> 8) & 0xFF)) &&
					(data_buffer[len + 1] == static_cast<uint8_t>(crc_out & 0xFF));
	return ok;
}

void IrFoxDecoder::first_rx()
{
	err_low_signal = err_high_signal = err_other = 0;
	pack_size = 0;
	is_buffer_overflow = false;
	is_available = false;
	buf_bit_pos = 0;
	is_data = true;
	i_data_buffer = 0;
	next_control_bit = irfox::kBitPerByte;
	i_sync_bit = 0;
	err_sync_bit = 0;
	is_wrong_pack = false;
	is_preamb = true;
	is_recive = false;
	is_recive_raw = false;
	msg_type_receive = 0;
	rise_sync_time_us = irfox::kBitTimeUs;
	std::memset(data_buffer, 0, sizeof data_buffer);
	preamble_bubble_start_valid_ = false;
	trim_first_data_bit_cell_ = false;
}

void IrFoxDecoder::listen_start(double t_us)
{
	const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
	// Как IR_DecoderRaw::listenStart: пауза по lastEdgeTime, не по prevRise.
	if (is_recive_raw && last_edge_time_us > 0.0 && (t_us - last_edge_time_us) > irmax * 2.0)
	{
		is_recive_raw = false;
		first_rx();
	}
}

void IrFoxDecoder::check_timeout(double t_us)
{
	if (!is_recive)
		return;
	const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
	if (t_us - last_edge_time_us > irmax * 2.0)
	{
		// Как IR_DecoderRaw::checkTimeout после фикса: полный сброс, иначе залипание FSM.
		is_recive = false;
		msg_type_receive = 0;
		is_recive_raw = false;
		first_rx();
		// Не last_edge_time_us = t_us: как IR_DecoderRaw — не расходить с «хвостом» фронтов.
	}
}

void IrFoxDecoder::write_to_buffer(bool bit, bool pack_trace_invert_fix, uint64_t cell_start_s, uint64_t cell_end_s,
								   const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt, IrFoxEmitBitMode emit_mode)
{
	if (i_data_buffer > irfox::kDataByteSizeMax * 8u)
	{
		if (!is_buffer_overflow && on_bit)
		{
			IrFoxEmitBit e{};
			e.start_sample = static_cast<int64_t>(cell_start_s);
			e.end_sample = static_cast<int64_t>(cell_end_s);
			e.frame_type = IRF_FT_OVERFLOW;
			e.mflags = DISPLAY_AS_ERROR_FLAG;
			fill_err_snapshot(e);
			std::strncpy(e.bubble_text, "OVF", sizeof e.bubble_text);
			e.bubble_text[sizeof e.bubble_text - 1] = '\0';
			on_bit(e);
		}
		is_buffer_overflow = true;
	}

	if (is_buffer_overflow || is_preamb || is_wrong_pack)
	{
		// Как IR_DecoderRaw::writeToBuffer: полный first_rx() вместо только сброса флагов приёма.
		first_rx();
		return;
	}

	if (buf_bit_pos == next_control_bit)
	{
		next_control_bit = next_control_bit + (is_data ? irfox::kSyncBits : irfox::kBitPerByte);
		is_data = !is_data;
		i_sync_bit = 0;
		err_sync_bit = 0;
	}

	if (is_data)
	{
		const bool was_first_data_bit = (i_data_buffer == 0);
		data_buffer[i_data_buffer / 8] |= static_cast<uint8_t>(bit ? 1 : 0) << (7 - (i_data_buffer % 8));
		i_data_buffer++;
		buf_bit_pos++;

		uint8_t fl = 0;
		if (pack_trace_invert_fix)
			fl |= DISPLAY_AS_WARNING_FLAG;
		if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
		{
			IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_DATA_BIT,
						   bit ? 1u : 0u, static_cast<uint64_t>(i_data_buffer - 1), fl, pack_trace_invert_fix, 0, 0, 0};
			fill_err_snapshot(e);
			e.bubble_text[0] = static_cast<char>(bit ? '1' : '0');
			e.bubble_text[1] = '\0';
			on_bit(e);
		}
		if (was_first_data_bit && trim_first_data_bit_cell_)
			trim_first_data_bit_cell_ = false;
	}
	else
	{
		if (i_sync_bit == 0)
		{
			const bool last_data_bit =
				(data_buffer[((i_data_buffer - 1) / 8)] >> (7 - ((i_data_buffer - 1) % 8))) & 1;
			if (bit != static_cast<bool>(last_data_bit))
			{
				buf_bit_pos++;
				i_sync_bit++;
				if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
				{
					IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_SYNC_BIT,
								   bit ? 1u : 0u, static_cast<uint64_t>(buf_bit_pos), 0, false, 0, 0, 0};
					fill_err_snapshot(e);
					std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %c", bit ? '1' : '0');
					on_bit(e);
				}
			}
			else
			{
				i_sync_bit = 0;
				err_other++;
				err_sync_bit++;
				const bool fatal_sync = (err_sync_bit >= irfox::kSyncBits);
				if (fatal_sync)
					is_wrong_pack = true;
				if (on_bit && fatal_sync)
				{
					IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_ABORT,
								   0, 0, DISPLAY_AS_ERROR_FLAG, false, 0, 0, 0};
					fill_err_snapshot(e);
					std::strncpy(e.bubble_text, "SYNC!", sizeof e.bubble_text);
					e.bubble_text[sizeof e.bubble_text - 1] = '\0';
					on_bit(e);
				}
			}
		}
		else
		{
			buf_bit_pos++;
			i_sync_bit++;
			if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
			{
				IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_SYNC_BIT,
							   bit ? 1u : 0u, static_cast<uint64_t>(buf_bit_pos), 0, false, 0, 0, 0};
				fill_err_snapshot(e);
				std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %c", bit ? '1' : '0');
				on_bit(e);
			}
		}
		is_wrong_pack = (err_sync_bit >= irfox::kSyncBits);
	}

	if (!is_available && is_data && !is_wrong_pack)
	{
		if (i_data_buffer == 8 * irfox::kMsgBytes)
			pack_size = static_cast<uint16_t>(data_buffer[0] & 0x1Fu);

		if (pack_size && (i_data_buffer == 8))
			msg_type_receive = static_cast<uint8_t>((data_buffer[0] >> 5) | 0xF8u);

		if (pack_size && (i_data_buffer == pack_size * irfox::kBitPerByte))
		{
			uint16_t crc_computed = 0;
			const bool crc_ok = crc_check(static_cast<uint8_t>(pack_size - irfox::kCrcBytes), crc_computed);
			crc_value = crc_computed;
			is_recive = false;
			is_recive_raw = false;
			msg_type_receive = 0;
			is_available = crc_ok;

			IrFoxEmitPacket pkt{};
			pkt.start_sample = static_cast<int64_t>(cell_start_s);
			pkt.end_sample = static_cast<int64_t>(cell_end_s);
			pkt.crc_ok = crc_ok;
			pkt.pack_size = static_cast<uint8_t>(pack_size);
			pkt.err_low = err_low_signal;
			pkt.err_high = err_high_signal;
			pkt.err_other = err_other;
			if (pack_size > 0 && pack_size <= irfox::kDataByteSizeMax)
				std::memcpy(pkt.data_bytes, data_buffer, pack_size);
			if (on_pkt)
				on_pkt(pkt);
		}
	}
}

void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const IrFoxOnBit& on_bit,
							   const IrFoxOnPacket& on_pkt)
{
	const double t_us = sample_to_us(sample, fs);
	const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
	uint32_t rise_min_us = rise_sync_time_us > irfox::kToleranceUs ? rise_sync_time_us - irfox::kToleranceUs : 0U;

	listen_start(t_us);

	// Как IR_DecoderRaw: пауза между фронтами по lastEdgeTime при активном приёме кадра.
	if (last_edge_time_us > 0.0 && (t_us - last_edge_time_us) > irmax * 2.0 && is_recive)
		check_timeout(t_us);

	last_edge_time_us = t_us;
	last_edge_sample = sample;

	const uint32_t rise_max_us = rise_sync_time_us + irfox::kToleranceUs;

	/** Визуализация: начало PRE с ближайшего спада в пределах ~3 битовых периодов (ИК-метка). */
	auto new_bubble_preamble_start = [&](uint64_t edge_s, bool is_rising) -> uint64_t {
		if (!is_rising)
			return edge_s;
		if (edge_s > prev_fall_sample)
		{
			const double span_us = double(edge_s - prev_fall_sample) * 1e6 / double(fs);
			const double max_us = double(rise_max_us) * 3.0;
			if (span_us <= max_us)
				return prev_fall_sample;
		}
		return edge_s;
	};

	if (rising)
	{
		const double delta_rp = t_us - prev_rise_us;
		const uint32_t cand_rp = static_cast<uint32_t>(delta_rp);
		const uint32_t cand_ht = static_cast<uint32_t>(t_us - prev_fall_us);
		const uint32_t cand_lt = static_cast<uint32_t>(prev_fall_us - prev_rise_us);

#if IRFOX_SHORT_LOW_GLITCH_REJECT
		const bool short_low_glitch =
			is_recive && !is_preamb && cand_ht < (rise_min_us / 8U) && cand_lt >= rise_min_us &&
			cand_rp >= rise_min_us && cand_rp <= irmax;
		if (short_low_glitch)
		{
			err_other++;
			irfox::irfoxGlitchPhaseNudgeUs(t_us, rise_sync_time_us, prev_rise_us);
			last_processed_edge_us = t_us;
			have_last_processed = true;
			return;
		}
#endif
#if IRFOX_MICRO_GAP_RISE_REJECT
		const bool micro_gap_cand_lt_ok =
			(cand_lt >= rise_min_us) || (cand_lt >= (rise_min_us / 4U) && cand_lt < rise_min_us);
		const bool micro_gap_rise = is_recive && !is_preamb && cand_ht < (rise_min_us / 8U) && micro_gap_cand_lt_ok &&
									cand_rp >= (rise_min_us / 4U) && cand_rp < rise_min_us && cand_rp <= irmax;
		if (micro_gap_rise)
		{
			err_other++;
			irfox::irfoxGlitchPhaseNudgeUs(t_us, rise_sync_time_us, prev_rise_us);
			last_processed_edge_us = t_us;
			have_last_processed = true;
			return;
		}
#endif
		if (cand_rp <= rise_max_us / 4U && !high_count && !low_count)
		{
			err_other++;
			last_processed_edge_us = t_us;
			have_last_processed = true;
			return;
		}

		// Визуализация PRE: длинная пауза, первый подъём — якорь от спада метки (декод как STM32DMA).
		if (cand_rp > irmax * 2U && !is_recive_raw)
		{
			preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, true);
			preamble_bubble_start_valid_ = true;
		}

		const bool accept_rise_timing =
			(delta_rp > static_cast<double>(rise_max_us) / 4.0) || high_count != 0 || low_count != 0;
		if (accept_rise_timing)
		{
			rise_period_anchor_sample_ = prev_rise_sample;
			rise_period_us = cand_rp;
			high_time_us = cand_ht;
			low_time_us = cand_lt;
			prev_rise_us = t_us;
			prev_rise_sample = sample;
		}
		else
		{
			err_other++;
		}
	}
	else
	{
		if (t_us - prev_fall_us > rise_min_us / 4.0)
		{
			prev_fall_us = t_us;
			prev_fall_sample = sample;
		}
		else
		{
			err_other++;
		}
	}

	// Как IR_DecoderRaw::tick: после длинной паузы старт сырого приёма (без отдельного firstRX — флаги ниже).
	if (t_us > prev_rise_us && (t_us - prev_rise_us) > irmax * 2.0 && !is_recive_raw)
	{
		preamb_front_counter = static_cast<int8_t>(irfox::kPreambFronts - 1);
		is_preamb = true;
		is_recive = true;
		is_recive_raw = true;
		is_wrong_pack = false;
		if (!preamble_bubble_start_valid_)
		{
			preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, rising);
			preamble_bubble_start_valid_ = true;
		}
	}

	if (preamb_front_counter)
	{
		if (rising && rise_period_us < irmax)
		{
			if (rise_period_us < rise_min_us / 2U)
			{
				preamb_front_counter += 2;
				err_other++;
			}
		}
		preamb_front_counter--;
	}
	else
	{
		if (is_preamb)
		{
			is_preamb = false;
			// IR_DecoderRaw: prevRise += risePeriod / 2 — фаза как в прошивке.
			// Бабл PRE: до текущего фронта (sample−1), чтобы охватить все kPreambPulse периодов (3 импульса),
			// а не только до предыдущего подъёма (~2 периода).
			const uint64_t preamble_bubble_end_sample = sample > 0 ? sample - 1 : sample;
			prev_rise_us += rise_period_us / 2.0;
			{
				const double half_us = 0.5 * static_cast<double>(rise_period_us);
				const uint64_t half_s = static_cast<uint64_t>(std::llround(half_us * double(fs) / 1e6));
				prev_rise_sample += half_s;
			}
			trim_first_data_bit_cell_ = true;
			if (on_bit && preamble_bubble_start_valid_)
			{
				int64_t pe_start = static_cast<int64_t>(preamble_bubble_start_sample_);
				int64_t pe_end = static_cast<int64_t>(preamble_bubble_end_sample);
				if (preamble_bubble_end_sample == 0 || pe_end < pe_start)
					pe_end = static_cast<int64_t>(sample > 0 ? sample - 1 : sample);
				IrFoxEmitBit pe{};
				pe.start_sample = pe_start;
				pe.end_sample = pe_end;
				pe.frame_type = IRF_FT_PREAMBLE;
				fill_err_snapshot(pe);
				std::strncpy(pe.bubble_text, "PRE", sizeof pe.bubble_text);
				pe.bubble_text[sizeof pe.bubble_text - 1] = '\0';
				on_bit(pe);
			}
			preamble_bubble_start_valid_ = false;
			last_processed_edge_us = t_us;
			have_last_processed = true;
			return;
		}
	}

	if (is_preamb)
	{
		last_processed_edge_us = t_us;
		have_last_processed = true;
		return;
	}

	if (rise_period_us > irmax || is_buffer_overflow || rise_period_us < rise_min_us || is_wrong_pack)
	{
		last_processed_edge_us = t_us;
		have_last_processed = true;
		return;
	}

	if (rising)
	{
		high_count = low_count = all_count = 0;
		bool invert_err = false;

		uint64_t cell_start_s = rise_period_anchor_sample_;
		const uint64_t cell_end_s = sample > 0 ? sample - 1 : sample;
		// После prev_rise += half period якорь может оказаться близко к текущему подъёму;
		// max(anchor, prev_fall) > cell_end даёт пустой интервал — бабл первого бита пропадает.
		if (trim_first_data_bit_cell_ && is_data && i_data_buffer == 0)
		{
			const uint64_t trimmed = std::max(cell_start_s, prev_fall_sample);
			if (trimmed <= cell_end_s)
				cell_start_s = trimmed;
		}

		if (irfox::aroundRisePeriod(rise_period_us, rise_sync_time_us))
		{
			if (high_time_us > low_time_us)
				write_to_buffer(true, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble);
			else
				write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble);
		}
		else
		{
			uint16_t hc = ceil_div_u16(static_cast<uint16_t>(high_time_us > 0xFFFF ? 0xFFFF : high_time_us),
									   static_cast<uint16_t>(rise_sync_time_us));
			uint16_t lc = ceil_div_u16(static_cast<uint16_t>(low_time_us > 0xFFFF ? 0xFFFF : low_time_us),
									   static_cast<uint16_t>(rise_sync_time_us));
			uint16_t ac = ceil_div_u16(static_cast<uint16_t>(rise_period_us > 0xFFFF ? 0xFFFF : rise_period_us),
									   static_cast<uint16_t>(rise_sync_time_us));
			high_count = static_cast<int8_t>(hc > 127 ? 127 : hc);
			low_count = static_cast<int8_t>(lc > 127 ? 127 : lc);
			all_count = static_cast<int8_t>(ac > 127 ? 127 : ac);

			if (high_count == 0 && high_time_us > rise_sync_time_us / 3U)
			{
				high_count++;
				err_other++;
			}

			if (low_count + high_count > all_count)
			{
				if (low_count > high_count)
				{
					low_count = static_cast<int8_t>(all_count - high_count);
					err_low_signal = static_cast<uint8_t>(err_low_signal + static_cast<uint8_t>(low_count));
				}
				else if (low_count < high_count)
				{
					high_count = static_cast<int8_t>(all_count - low_count);
					err_high_signal = static_cast<uint8_t>(err_high_signal + static_cast<uint8_t>(high_count));
				}
				else if (low_count == high_count)
				{
					invert_err = true;
					err_other = static_cast<uint8_t>(err_other + static_cast<uint8_t>(all_count));
				}
			}

			if (low_count < high_count)
				err_high_signal = static_cast<uint8_t>(err_high_signal + static_cast<uint8_t>(high_count));
			else
				err_low_signal = static_cast<uint8_t>(err_low_signal + static_cast<uint8_t>(low_count));

			const bool burst_is_data_start = is_data;
			const uint64_t merge_bit_index =
				burst_is_data_start ? static_cast<uint64_t>(i_data_buffer) : static_cast<uint64_t>(buf_bit_pos + 1);
			char s_bits[20]{};
			char d_bits[20]{};
			size_t s_n = 0;
			size_t d_n = 0;
			int first_merge_bit = -1;
			bool merge_warn = false;
			auto append_merge = [&](bool as_data, bool bitv) {
				if (first_merge_bit < 0)
					first_merge_bit = bitv ? 1 : 0;
				char* buf = as_data ? d_bits : s_bits;
				size_t& n = as_data ? d_n : s_n;
				if (n + 1 < sizeof s_bits)
					buf[n++] = static_cast<char>(bitv ? '1' : '0');
			};
			auto emit_merge_if_needed = [&]() {
				if ((s_n == 0 && d_n == 0) || !on_bit)
					return;
				IrFoxEmitBit e{};
				e.start_sample = static_cast<int64_t>(cell_start_s);
				e.end_sample = static_cast<int64_t>(cell_end_s);
				e.frame_type = (d_n > 0) ? IRF_FT_DATA_BIT : IRF_FT_SYNC_BIT;
				e.bit_value = (first_merge_bit > 0) ? 1u : 0u;
				e.bit_index = merge_bit_index;
				e.mflags = merge_warn ? DISPLAY_AS_WARNING_FLAG : 0;
				e.invert_fix = merge_warn;
				fill_err_snapshot(e);
				s_bits[s_n] = '\0';
				d_bits[d_n] = '\0';
				if (s_n && d_n)
					std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %s d: %s", s_bits, d_bits);
				else if (s_n)
					std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %s", s_bits);
				else
					std::memcpy(e.bubble_text, d_bits, d_n + 1);
				on_bit(e);
			};

			for (int8_t i = 0; i < low_count && 8 - i; i++)
			{
				const bool row_is_data = is_data;
				if (i == low_count - 1 && invert_err)
				{
					invert_err = false;
					write_to_buffer(true, true, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
					merge_warn = true;
					append_merge(row_is_data, true);
				}
				else
				{
					write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
					append_merge(row_is_data, false);
				}
			}

			for (int8_t i = 0; i < high_count && 8 - i; i++)
			{
				const bool row_is_data = is_data;
				if (i == high_count - 1 && invert_err)
				{
					invert_err = false;
					write_to_buffer(false, true, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
					merge_warn = true;
					append_merge(row_is_data, false);
				}
				else
				{
					write_to_buffer(true, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
					append_merge(row_is_data, true);
				}
			}

			emit_merge_if_needed();
		}
	}

	last_processed_edge_us = t_us;
	have_last_processed = true;
}

void IrFoxDecoder::flushEnd(uint64_t last_sample, uint32_t fs, const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt)
{
	const double t_us = sample_to_us(last_sample, fs);
	listen_start(t_us);
	check_timeout(t_us);
	(void)on_bit;
	(void)on_pkt;
}
