"""
Lab005. Передача пакетов через виртуальный радиоканал.

Эксперимент:
1. Формируем 100 цифровых пакетов.
2. Передаём их через виртуальный канал.
3. Канал может:
   - полностью потерять пакет;
   - изменить отдельные биты.
4. Приёмник проверяет структуру пакета и CRC-32.
5. Выводится статистика доставки.
"""

import random

from protocol.packet import (
    CRCError,
    MESSAGE_TYPE_TEXT,
    PacketError,
    build_packet,
    parse_packet,
)

from tools.virtual_channel import transmit


# ============================================================
# Настройки эксперимента
# ============================================================

# Количество передаваемых пакетов.
PACKET_COUNT = 100

# Вероятность полной потери одного пакета.
#
# 0.10 означает 10 %.
DROP_PROBABILITY = 0.10

# Вероятность повреждения каждого отдельного бита.
#
# 0.002 означает:
# 0,2 % вероятности ошибки для каждого бита.
BIT_ERROR_PROBABILITY = 0.002

# Генератор случайных чисел.
#
# Число 2026 позволяет получать одинаковый результат
# при каждом запуске программы.
random_generator = random.Random(2026)


# ============================================================
# Счётчики результатов
# ============================================================

sent_packets = 0

# Пакеты, полностью исчезнувшие в канале.
dropped_packets = 0

# Доставленные, но повреждённые пакеты.
rejected_packets = 0

# Пакеты, правильно принятые и разобранные.
received_ok = 0

# Общее количество изменённых битов.
total_flipped_bits = 0


# ============================================================
# Передача серии пакетов
# ============================================================

for sequence_number in range(PACKET_COUNT):

    # Создаём уникальное сообщение для каждого пакета.
    message = f"ПАКЕТ {sequence_number}"

    # Текст превращается в байты UTF-8.
    payload = message.encode("utf-8")

    # Формируем полный пакет с заголовком и CRC-32.
    packet = build_packet(
        payload=payload,
        message_type=MESSAGE_TYPE_TEXT,
        sequence_number=sequence_number,
    )

    sent_packets += 1

    # Передаём пакет через виртуальный канал.
    received_packet, flipped_bits = transmit(
        packet=packet,
        drop_probability=DROP_PROBABILITY,
        bit_error_probability=BIT_ERROR_PROBABILITY,
        random_generator=random_generator,
    )

    total_flipped_bits += flipped_bits

    # None означает, что пакет полностью потерян.
    if received_packet is None:
        dropped_packets += 1
        continue

    # Пытаемся разобрать принятый пакет.
    try:
        parsed_packet = parse_packet(received_packet)

    except (CRCError, PacketError):
        # CRCError:
        # контрольная сумма не совпала.
        #
        # PacketError:
        # повреждены SYNC, версия, длина
        # или другая часть структуры.
        rejected_packets += 1
        continue

    # Восстанавливаем текст из полезной нагрузки.
    restored_message = parsed_packet.payload.decode("utf-8")

    # Проверяем, что получен именно тот пакет,
    # который был передан.
    assert parsed_packet.sequence_number == sequence_number
    assert restored_message == message

    received_ok += 1


# ============================================================
# Вывод настроек
# ============================================================

print("=== Lab005. Виртуальный канал ===")

print("\nНастройки эксперимента:")

print(
    "Количество пакетов:",
    PACKET_COUNT,
)

print(
    "Вероятность полной потери пакета:",
    DROP_PROBABILITY,
)

print(
    "Вероятность ошибки отдельного бита:",
    BIT_ERROR_PROBABILITY,
)


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

print("\nРезультаты:")

print(
    "Передано пакетов:",
    sent_packets,
)

print(
    "Потеряно полностью:",
    dropped_packets,
)

print(
    "Отклонено из-за повреждений:",
    rejected_packets,
)

print(
    "Принято правильно:",
    received_ok,
)

print(
    "Всего изменено битов:",
    total_flipped_bits,
)


# ============================================================
# Расчёт процентов
# ============================================================

delivery_rate = (
    received_ok
    / sent_packets
    * 100
)

drop_rate = (
    dropped_packets
    / sent_packets
    * 100
)

rejection_rate = (
    rejected_packets
    / sent_packets
    * 100
)

packet_failure_rate = (
    dropped_packets
    + rejected_packets
) / sent_packets * 100


print("\nСтатистика:")

print(
    f"Успешно доставлено: "
    f"{delivery_rate:.1f} %"
)

print(
    f"Потеряно полностью: "
    f"{drop_rate:.1f} %"
)

print(
    f"Отклонено приёмником: "
    f"{rejection_rate:.1f} %"
)

print(
    f"Общая доля недоставленных пакетов: "
    f"{packet_failure_rate:.1f} %"
)


# ============================================================
# Контроль правильности
# ============================================================

accounted_packets = (
    received_ok
    + dropped_packets
    + rejected_packets
)

assert accounted_packets == sent_packets

print(
    "\nПроверка пройдена: "
    "все переданные пакеты учтены."
)