"""
Lab017. Фильтрация Eb/N0 и гистерезис режимов.

Эксперимент сравнивает два адаптера:

1. Наивный адаптер:
   переключает режим при каждом пересечении порога.

2. Устойчивый адаптер:
   использует:
   - экспоненциальное усреднение;
   - гистерезис;
   - подтверждение повышения;
   - аварийное быстрое понижение.

Профиль Eb/N0 специально содержит колебания
около порогов переключения.
"""

from csv import DictWriter
from pathlib import Path

import matplotlib.pyplot as plt

from protocol.link_controller import (
    FRAGMENT_MODE_1024,
    IMAGE_OFF,
    LinkModeController,
)


# ============================================================
# Настройки
# ============================================================

OUTPUT_DIRECTORY = Path(
    "data/processed/lab017"
)

OUTPUT_DIRECTORY.mkdir(
    parents=True,
    exist_ok=True,
)

CSV_PATH = (
    OUTPUT_DIRECTORY
    / "lab017_hysteresis_results.csv"
)

GRAPH_PATH = (
    OUTPUT_DIRECTORY
    / "lab017_hysteresis_comparison.png"
)


# ============================================================
# Профиль качества канала
# ============================================================

EB_N0_PROFILE_DB = [
    10.2,
    9.7,
    9.4,
    9.6,
    9.3,
    9.7,
    9.2,
    9.6,
    9.0,
    8.7,
    8.4,
    8.6,
    8.3,
    8.7,
    8.2,
    8.6,
    8.0,
    7.8,
    7.5,
    7.7,
    7.4,
    7.8,
    7.2,
    6.8,
    6.5,
    6.3,
    6.6,
    6.2,
    6.7,
    6.1,
    5.5,
    6.0,
    6.4,
    6.7,
    6.9,
    7.1,
    7.3,
    8.0,
    8.8,
    9.0,
    9.4,
    9.8,
    10.0,
    10.2,
    10.1,
]


# ============================================================
# Наивное решение без гистерезиса
# ============================================================

def choose_naive_mode(
    eb_n0_db: float,
) -> int:
    """
    Немедленно выбрать режим по одному измерению.

    Гистерезиса и фильтрации нет.
    """

    if eb_n0_db < 6.5:
        return 0

    if eb_n0_db < 8.5:
        return 128

    if eb_n0_db < 9.5:
        return 512

    return 1024


# ============================================================
# Запуск устойчивого контроллера
# ============================================================

controller = LinkModeController(
    initial_mode=FRAGMENT_MODE_1024,
    filter_alpha=0.35,
    upgrade_confirmation_count=3,
    minimum_hold_steps=2,
)

results = []

for measured_eb_n0_db in EB_N0_PROFILE_DB:

    stable_result = controller.update(
        measured_eb_n0_db
    )

    naive_mode = choose_naive_mode(
        measured_eb_n0_db
    )

    results.append(
        {
            "step": stable_result.step,
            "measured_eb_n0_db": (
                stable_result.measured_eb_n0_db
            ),
            "filtered_eb_n0_db": (
                stable_result.filtered_eb_n0_db
            ),
            "naive_mode": naive_mode,
            "stable_mode": (
                stable_result.selected_mode
            ),
            "mode_changed": (
                stable_result.mode_changed
            ),
            "pending_upgrade_mode": (
                stable_result.pending_upgrade_mode
            ),
            "pending_upgrade_count": (
                stable_result.pending_upgrade_count
            ),
            "reason": stable_result.reason,
        }
    )


# ============================================================
# Число переключений
# ============================================================

def count_mode_switches(
    modes: list[int],
) -> int:
    """
    Посчитать число изменений режима.
    """

    return sum(
        previous_mode != current_mode
        for previous_mode, current_mode in zip(
            modes,
            modes[1:],
        )
    )


naive_modes = [
    result["naive_mode"]
    for result in results
]

stable_modes = [
    result["stable_mode"]
    for result in results
]

naive_switch_count = count_mode_switches(
    naive_modes
)

stable_switch_count = count_mode_switches(
    stable_modes
)


# ============================================================
# Вывод результатов
# ============================================================

print(
    "=== Lab017. Гистерезис режима канала ==="
)

print("\nРезультаты по шагам:")

print(
    f"{'Шаг':>5}"
    f"{'Eb/N0':>10}"
    f"{'Фильтр':>11}"
    f"{'Наивный':>11}"
    f"{'Устойчивый':>13}"
    f"{'Изменён':>10}"
    f"{'Причина':>45}"
)

print("-" * 105)

for result in results:

    print(
        f"{result['step']:>5}"
        f"{result['measured_eb_n0_db']:>7.1f} дБ"
        f"{result['filtered_eb_n0_db']:>8.2f} дБ"
        f"{result['naive_mode']:>11}"
        f"{result['stable_mode']:>13}"
        f"{str(result['mode_changed']):>10}"
        f"  {result['reason']}"
    )


print("\nЧисло переключений:")

print(
    "Наивный адаптер:",
    naive_switch_count,
)

print(
    "Устойчивый адаптер:",
    stable_switch_count,
)

print(
    "Предотвращено лишних переключений:",
    naive_switch_count - stable_switch_count,
)


# ============================================================
# Сохранение CSV
# ============================================================

with CSV_PATH.open(
    "w",
    newline="",
    encoding="utf-8-sig",
) as csv_file:

    writer = DictWriter(
        csv_file,
        fieldnames=list(
            results[0].keys()
        ),
    )

    writer.writeheader()
    writer.writerows(results)


# ============================================================
# График
# ============================================================

steps = [
    result["step"]
    for result in results
]

measured_values = [
    result["measured_eb_n0_db"]
    for result in results
]

filtered_values = [
    result["filtered_eb_n0_db"]
    for result in results
]


figure, axes = plt.subplots(
    2,
    1,
    figsize=(12, 9),
    sharex=True,
)


# ------------------------------------------------------------
# Верхний график: Eb/N0
# ------------------------------------------------------------

axes[0].plot(
    steps,
    measured_values,
    marker=".",
    label="Измеренное Eb/N0",
)

axes[0].plot(
    steps,
    filtered_values,
    linewidth=2,
    label="Фильтрованное Eb/N0",
)

axes[0].axhline(
    6.5,
    linestyle=":",
    label="Порог наивного адаптера",
)

axes[0].axhline(
    8.5,
    linestyle=":",
)

axes[0].axhline(
    9.5,
    linestyle=":",
)

axes[0].set_ylabel(
    "Eb/N0, дБ"
)

axes[0].set_title(
    "Фильтрация измерения качества канала"
)

axes[0].grid(
    True
)

axes[0].legend()


# ------------------------------------------------------------
# Нижний график: режимы
# ------------------------------------------------------------

axes[1].step(
    steps,
    naive_modes,
    where="mid",
    marker=".",
    label="Наивный адаптер",
)

axes[1].step(
    steps,
    stable_modes,
    where="mid",
    marker="o",
    label="Гистерезис и фильтрация",
)

axes[1].set_yticks(
    [
        0,
        128,
        512,
        1024,
    ],
    [
        "IMAGE OFF",
        "128 B",
        "512 B",
        "1024 B",
    ],
)

axes[1].set_xlabel(
    "Шаг времени"
)

axes[1].set_ylabel(
    "Режим"
)

axes[1].set_title(
    "Сравнение переключений режимов"
)

axes[1].grid(
    True
)

axes[1].legend()


figure.tight_layout()

figure.savefig(
    GRAPH_PATH,
    dpi=160,
)

plt.close(
    figure
)


# ============================================================
# Проверки
# ============================================================

assert results

assert IMAGE_OFF in stable_modes

assert FRAGMENT_MODE_1024 in stable_modes

assert stable_switch_count < naive_switch_count

assert CSV_PATH.exists()

assert GRAPH_PATH.exists()


print("\nCSV:")

print(CSV_PATH)

print("\nГрафик:")

print(GRAPH_PATH)

print(
    "\nПроверка пройдена: "
    "гистерезис уменьшил количество переключений."
)