"""
Lab006. Надёжная передача пакетов методом Stop-and-Wait ARQ.

Передатчик:
1. Отправляет пакет.
2. Ожидает подтверждение ACK.
3. При отсутствии правильного ACK повторяет передачу.
4. После исчерпания попыток считает сообщение недоставленным.

Приёмник:
1. Проверяет пакет.
2. Отбрасывает повреждённые пакеты.
3. Отправляет ACK для правильного пакета.
4. Не передаёт приложению повторные копии одного сообщения.
"""

import random

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

from tools.virtual_channel import transmit


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

MESSAGE_COUNT = 100

# Максимальное количество передач одного сообщения.
MAX_ATTEMPTS = 5

# Параметры канала от передатчика к приёмнику.
DATA_DROP_PROBABILITY = 0.10
DATA_BIT_ERROR_PROBABILITY = 0.002

# Параметры обратного канала для ACK.
ACK_DROP_PROBABILITY = 0.10
ACK_BIT_ERROR_PROBABILITY = 0.002

# Фиксированное число делает эксперимент повторяемым.
random_generator = random.Random(2026)


# ============================================================
# Счётчики
# ============================================================

delivered_messages = 0
failed_messages = 0

data_transmissions = 0
ack_transmissions = 0

duplicate_packets = 0

invalid_data_packets = 0
invalid_ack_packets = 0

dropped_data_packets = 0
dropped_ack_packets = 0

# Номера пакетов, уже переданных приложению приёмника.
received_sequences = set()


# ============================================================
# Передача сообщений
# ============================================================

for sequence_number in range(MESSAGE_COUNT):

    message = f"ПАКЕТ {sequence_number}"
    payload = message.encode("utf-8")

    data_packet = build_packet(
        payload=payload,
        message_type=MESSAGE_TYPE_TEXT,
        sequence_number=sequence_number,
    )

    message_confirmed = False

    # Повторяем передачу до MAX_ATTEMPTS раз.
    for attempt_number in range(1, MAX_ATTEMPTS + 1):

        data_transmissions += 1

        received_data_packet, _ = transmit(
            packet=data_packet,
            drop_probability=DATA_DROP_PROBABILITY,
            bit_error_probability=DATA_BIT_ERROR_PROBABILITY,
            random_generator=random_generator,
        )

        # Пакет полностью потерялся.
        if received_data_packet is None:
            dropped_data_packets += 1
            continue

        # Приёмник проверяет пакет.
        try:
            parsed_data = parse_packet(received_data_packet)

        except (CRCError, PacketError):
            invalid_data_packets += 1
            continue

        # Проверка типа сообщения.
        if parsed_data.message_type != MESSAGE_TYPE_TEXT:
            invalid_data_packets += 1
            continue

        # Проверяем, не получали ли мы этот пакет ранее.
        if parsed_data.sequence_number in received_sequences:
            duplicate_packets += 1

        else:
            restored_message = parsed_data.payload.decode("utf-8")

            assert restored_message == message

            received_sequences.add(
                parsed_data.sequence_number
            )

        # Формируем подтверждение.
        #
        # ACK имеет тот же sequence_number,
        # что и подтверждаемый пакет.
        ack_packet = build_packet(
            payload=b"",
            message_type=MESSAGE_TYPE_ACK,
            sequence_number=parsed_data.sequence_number,
        )

        ack_transmissions += 1

        received_ack_packet, _ = transmit(
            packet=ack_packet,
            drop_probability=ACK_DROP_PROBABILITY,
            bit_error_probability=ACK_BIT_ERROR_PROBABILITY,
            random_generator=random_generator,
        )

        # ACK полностью потерялся.
        if received_ack_packet is None:
            dropped_ack_packets += 1
            continue

        # Передатчик проверяет ACK.
        try:
            parsed_ack = parse_packet(received_ack_packet)

        except (CRCError, PacketError):
            invalid_ack_packets += 1
            continue

        # ACK должен иметь правильный тип и номер.
        if (
            parsed_ack.message_type != MESSAGE_TYPE_ACK
            or parsed_ack.sequence_number != sequence_number
        ):
            invalid_ack_packets += 1
            continue

        # Подтверждение получено.
        message_confirmed = True
        break

    if message_confirmed:
        delivered_messages += 1
    else:
        failed_messages += 1


# ============================================================
# Расчёт статистики
# ============================================================

delivery_rate = (
    delivered_messages
    / MESSAGE_COUNT
    * 100
)

failure_rate = (
    failed_messages
    / MESSAGE_COUNT
    * 100
)

average_data_transmissions = (
    data_transmissions
    / MESSAGE_COUNT
)


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

print("=== Lab006. Stop-and-Wait ARQ ===")

print("\nНастройки:")

print("Количество сообщений:", MESSAGE_COUNT)
print("Максимум попыток:", MAX_ATTEMPTS)

print(
    "Вероятность потери DATA:",
    DATA_DROP_PROBABILITY,
)

print(
    "Вероятность битовой ошибки DATA:",
    DATA_BIT_ERROR_PROBABILITY,
)

print(
    "Вероятность потери ACK:",
    ACK_DROP_PROBABILITY,
)

print(
    "Вероятность битовой ошибки ACK:",
    ACK_BIT_ERROR_PROBABILITY,
)


print("\nОсновные результаты:")

print(
    "Успешно подтверждено сообщений:",
    delivered_messages,
)

print(
    "Не подтверждено сообщений:",
    failed_messages,
)

print(
    f"Доля успешной доставки: "
    f"{delivery_rate:.1f} %"
)

print(
    f"Доля недоставленных сообщений: "
    f"{failure_rate:.1f} %"
)


print("\nПередачи:")

print(
    "Всего передач DATA:",
    data_transmissions,
)

print(
    "Всего передач ACK:",
    ack_transmissions,
)

print(
    f"Среднее число передач DATA "
    f"на одно сообщение: "
    f"{average_data_transmissions:.2f}"
)


print("\nОшибки канала:")

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

print(
    "Повреждено DATA:",
    invalid_data_packets,
)

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

print(
    "Повреждено ACK:",
    invalid_ack_packets,
)

print(
    "Получено повторных DATA-пакетов:",
    duplicate_packets,
)


# ============================================================
# Автоматические проверки
# ============================================================

assert (
    delivered_messages
    + failed_messages
    == MESSAGE_COUNT
)

assert delivered_messages <= len(received_sequences)

assert data_transmissions >= MESSAGE_COUNT

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