import math

from seq_interp.src.hardware.constraints import HardwareConstraints


class Synchronizer:
    def __init__(self, hw: HardwareConstraints):
        self.hw = hw

    def process(self, sync_sequence):
        """
        Повторяет логику synchronization(...) из test1_full/srv_interp.py
        и возвращает все массивы, нужные для экспорта XML и PicoScope.
        """
        synchro_block_timer = self.hw.MIN_BLOCK_DURATION
        tr_delay = self.hw.TR_DELAY
        rf_delay = self.hw.RF_DELAY
        # Стартовый блок (индекс 0) не эмитится в XML — его представляет
        # заголовок — но синхронизатору он нужен как ненулевой буфер для
        # вычитаний задержек и как смещение начала ADC. Поэтому выключенный
        # START_DELAY = минимальный блок (20 тактов), а не ноль (синхронизатор
        # не умеет работать с нулевыми длительностями).
        min_event_duration = 20 * synchro_block_timer
        if self.hw.START_DELAY_ENABLED:
            start_delay = max(self.hw.START_DELAY, self.hw.RF_DELAY)
        else:
            start_delay = min_event_duration

        min_block_time = 800e-9

        if tr_delay < synchro_block_timer:
            tr_delay = synchro_block_timer
        if rf_delay < synchro_block_timer:
            rf_delay = synchro_block_timer

        number_of_blocks = len(sync_sequence.block_events)
        gate_adc = [0]
        gate_rf = [0]
        gate_tr_switch = [1]
        blocks_duration = [start_delay]

        added_blocks = 0
        for block_counter in range(number_of_blocks):
            is_not_adc_block = True

            if sync_sequence.block_events[block_counter + 1][5]:
                is_not_adc_block = False

                # Блок задержки TR вставляется только если включён (иначе
                # давал артефакт в 1 такт при TR_DELAY = 20 нс).
                if self.hw.TR_DELAY_ENABLED:
                    gate_adc.append(0)
                    gate_rf.append(gate_rf[-1])
                    blocks_duration[-1] -= tr_delay
                    blocks_duration.append(tr_delay)
                    gate_tr_switch.append(0)
                    added_blocks += 1

                gate_adc.append(1)
                gate_tr_switch.append(0)
            else:
                gate_tr_switch.append(1)
                gate_adc.append(0)

            if sync_sequence.block_events[block_counter + 1][1] and is_not_adc_block:
                # Блок задержки RF вставляется только если включён (иначе
                # вычитание rf_delay могло обнулить стартовый блок -> 0 тактов).
                if self.hw.RF_DELAY_ENABLED:
                    gate_rf.append(1)
                    gate_adc.append(gate_adc[-1])
                    blocks_duration[-1] -= rf_delay
                    blocks_duration.append(rf_delay)
                    gate_tr_switch.append(gate_tr_switch[-1])
                    added_blocks += 1

                gate_rf.append(1)
            else:
                gate_rf.append(0)

            current_block_dur = sync_sequence.block_durations[block_counter + 1]
            blocks_duration.append(current_block_dur)

        number_of_blocks += added_blocks

        # Минимальная длительность события в синхро-последовательности
        # ограничена 20 тактами (влияет на реальную длительность блоков).
        # Сначала поднимаем все короткие блоки до минимума, копим суммарный
        # дефицит, затем гасим его из блоков с запасом (не опуская их ниже
        # минимума) — так сохраняется общая длительность (TR) без каскадов,
        # когда компенсация возвращала бы уже исправленный блок ниже минимума.
        # Доноры без ADC берём первыми, чтобы не урезать окно ADC.
        n_dur = len(blocks_duration)
        total_deficit = 0.0
        for i in range(n_dur):
            if blocks_duration[i] < min_event_duration:
                total_deficit += min_event_duration - blocks_duration[i]
                blocks_duration[i] = min_event_duration

        donor_order = sorted(
            (j for j in range(n_dur) if gate_adc[j] == 0),
            key=lambda j: blocks_duration[j], reverse=True,
        )
        donor_order += sorted(
            (j for j in range(n_dur) if gate_adc[j] == 1),
            key=lambda j: blocks_duration[j], reverse=True,
        )
        for j in donor_order:
            if total_deficit <= 0:
                break
            surplus = blocks_duration[j] - min_event_duration
            if surplus <= 0:
                continue
            take = min(surplus, total_deficit)
            blocks_duration[j] -= take
            total_deficit -= take

        # Максимальная длительность блока ограничена 999999 тактами.
        # Блоки длиннее разбиваются на равные части (уровни гейтов
        # дублируются на каждую часть, суммарная длительность сохраняется).
        max_event_duration = 999999 * synchro_block_timer
        split_blocks_duration = []
        split_gate_adc = []
        split_gate_rf = []
        split_gate_tr_switch = []
        for i in range(len(blocks_duration)):
            dur = blocks_duration[i]
            if dur > max_event_duration:
                n_parts = math.ceil(dur / max_event_duration)
                part_dur = dur / n_parts
            else:
                n_parts = 1
                part_dur = dur
            for _ in range(n_parts):
                split_blocks_duration.append(part_dur)
                # Уровни гейтов (в т.ч. ADC HIGH) дублируются на все части:
                # разбитый блок остаётся непрерывным HIGH, триггер не дёргается.
                split_gate_adc.append(gate_adc[i])
                split_gate_rf.append(gate_rf[i])
                split_gate_tr_switch.append(gate_tr_switch[i])

        number_of_blocks += len(split_blocks_duration) - len(blocks_duration)
        blocks_duration = split_blocks_duration
        gate_adc = split_gate_adc
        gate_rf = split_gate_rf
        gate_tr_switch = split_gate_tr_switch

        return {
            "number_of_blocks": number_of_blocks,
            "gate_adc": gate_adc,
            "gate_rf": gate_rf,
            "gate_tr_switch": gate_tr_switch,
            "blocks_duration": blocks_duration,
            "synchro_block_timer": synchro_block_timer,
            "min_block_time": min_block_time,
        }
