#!/usr/bin/env python3
"""
Симуляция логики IR_Encoder::buildGateRuns (IR-protocol) и утилиты CRC8.
Запуск из корня репозитория: python docs/scripts/ir_protocol_gate_runs_sim.py
Или: python ir_protocol_gate_runs_sim.py из каталога scripts/
"""

from __future__ import annotations

import sys

# --- IR_config.h (фрагмент) ---
bitPauseTakts = 12
bitActiveTakts = 25
preambPulse = 3
syncBits = 3
bitPerByte = 8
preambToggle = ((bitPauseTakts * 2 + bitActiveTakts) * 2 - 1)
bitHigh = [(bitPauseTakts) * 2 - 1, (bitActiveTakts) * 2 - 1]
bitLow = [(bitPauseTakts // 2 + bitActiveTakts) * 2 - 1, (bitPauseTakts) - 1]

preamb, data, sync, noSignal = 0, 1, 2, 3
HIGH = True


def crc8(data: bytes, start: int, end: int, poly: int) -> int:
    """Как IR_FOX::crc8 в IR_config.cpp: [start, end)."""
    crc = 0xFF
    for i in range(start, end):
        crc ^= data[i]
        for _ in range(8):
            if (crc & 0x80) != 0:
                crc = ((crc << 1) ^ poly) & 0xFF
            else:
                crc = (crc << 1) & 0xFF
    return crc


def crc_pair_over_wire(packet: bytes) -> tuple[int, int]:
    """Два байта CRC как в IR_Encoder::sendDataFULL (poly1 старший, poly2 младший)."""
    ps = len(packet)
    if ps < 2:
        return 0, 0
    b1 = crc8(packet, 0, ps - 2, 0x31) & 0xFF
    b2 = crc8(packet, 0, ps - 1, 0x8C) & 0xFF
    return b1, b2


# Как dataByteSizeMax в IR_config.h (msg+addr+addr+bytePerPack+crc)
DATA_BYTE_SIZE_MAX = 1 + 2 + 2 + 31 + 2


def build_gate_runs(packet: bytes):
    """
    Повторяет IR_Encoder::buildGateRuns: список (gate: bool, lenTicks: int), сумма lenTicks = totalTicks DMA.
    Буфер дополняется нулями до dataByteSizeMax, как sendBufferLocal[dataByteSizeMax] в C++.
    """
    send_len = len(packet)
    send_buf = bytearray(packet) + bytes(max(0, DATA_BYTE_SIZE_MAX - len(packet)))

    toggle = preambToggle
    data_bit = bitPerByte - 1
    data_byte = 0
    preamb_front = preambPulse * 2 - 1
    data_seq = bitPerByte * 2
    sync_seq = syncBits * 2
    sync_last = False
    sig = preamb
    state = HIGH
    cur_seq = bitHigh

    runs: list[tuple[bool, int]] = []
    outer_steps = 0

    while True:
        outer_steps += 1
        gate = state
        run_len = toggle + 1  # как в C++: (uint16_t)toggleCounterLocal + 1U

        if runs and runs[-1][0] == gate:
            g, ln = runs[-1]
            runs[-1] = (g, ln + run_len)
        else:
            runs.append((gate, run_len))

        while True:
            if sig == noSignal:
                return runs, outer_steps

            if sig == preamb:
                if preamb_front:
                    preamb_front -= 1
                    toggle = preambToggle
                    break
                sig = data
                state = not False
                continue

            if sig == data:
                if data_seq:
                    if not (data_seq & 1):
                        cur_seq = bitHigh if ((send_buf[data_byte] >> data_bit) & 1) else bitLow
                        data_bit -= 1
                    toggle = cur_seq[not state]
                    data_seq -= 1
                    break
                sync_last = send_buf[data_byte] & 1
                data_byte += 1
                data_bit = bitPerByte - 1
                data_seq = bitPerByte * 2
                sig = sync
                continue

            if sig == sync:
                if sync_seq:
                    if not (sync_seq & 1):
                        if sync_seq == 2:
                            cur_seq = bitLow if (send_buf[data_byte] & 0x80) else bitHigh
                        else:
                            cur_seq = bitLow if sync_last else bitHigh
                            sync_last = not sync_last
                    toggle = cur_seq[not state]
                    sync_seq -= 1
                    break
                sig = data
                sync_seq = syncBits * 2
                if data_byte >= send_len:
                    sig = noSignal
                continue

            return [], 0

        state = not state


def main() -> int:
    print("IR-protocol: preambToggle =", preambToggle)
    print()

    # Пример из лога: 8-байтный эхо-пакет Version_Query (CRC OK на приёме)
    echo = bytes.fromhex("C8 FA 2A FD E8 5D AA B4")
    c1, c2 = crc_pair_over_wire(echo)
    print("8 байт (эхо): CRC вычисленный:", f"{c1:02X}", f"{c2:02X}", "| на проводе:", f"{echo[6]:02X}", f"{echo[7]:02X}")

    runs8, steps8 = build_gate_runs(echo)
    total8 = sum(r[1] for r in runs8)
    print("  buildGateRuns: внешних шагов FSM =", steps8, ", totalTicks =", total8, ", число run-сегментов =", len(runs8))
    print()

    # 31 байт из лога Frame reject (пример)
    reject = bytes.fromhex(
        "DF 00 00 FA 2A 5E 43 61 72 5F 76 34 2E 33 2E 38 5F 5B 31 32 4D 68 7A 5D 6B ED 1D 9A 53 96 62"
    )
    if len(reject) == 31:
        c1, c2 = crc_pair_over_wire(reject)
        print("31 байт (reject): CRC по телу 0..28 должен быть:", f"{c1:02X}", f"{c2:02X}", "| байты [29:31]:", f"{reject[29]:02X}", f"{reject[30]:02X}")
        print("  Совпадение с формулой:", c1 == reject[29] and c2 == reject[30])

    runs31, steps31 = build_gate_runs(reject)
    total31 = sum(r[1] for r in runs31)
    print("  buildGateRuns: внешних шагов =", steps31, ", totalTicks =", total31, ", run-сегментов =", len(runs31))
    print()

    # Связь totalTicks с моделью «N тиков на сегмент до шага FSM»
    # total_build = sum(toggle_i + 1); если бы было sum(toggle_i), разница = steps
    theoretical_isr_ticks = total31 - steps31
    print("Для 31-байт пакета: totalTicks (buildGateRuns) =", total31)
    print("  Если каждый внешний шаг даёт +1 к длине сегмента относительно ISR (runLen = toggle+1 vs toggle),")
    print("  оценка «ISR-тиков» как totalTicks - outer_steps =", theoretical_isr_ticks)
    return 0


if __name__ == "__main__":
    sys.exit(main())
