"""
Lab007. Разбиение JPEG на фрагменты и обратная сборка.

Этапы:
1. Создание тестового JPEG.
2. Чтение файла как последовательности байтов.
3. Разбиение изображения на фрагменты.
4. Формирование цифрового пакета для каждого фрагмента.
5. Перемешивание порядка доставки пакетов.
6. Проверка CRC и разбор пакетов.
7. Обратная сборка изображения.
8. Сравнение контрольных сумм файлов.
"""

from hashlib import sha256
from pathlib import Path
import random

from PIL import Image

from protocol.image_fragments import (
    decode_image_fragment,
    encode_image_fragment,
    reassemble_image,
    split_image_bytes,
)

from protocol.packet import (
    MESSAGE_TYPE_IMAGE_FRAGMENT,
    build_packet,
    parse_packet,
)


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

IMAGE_WIDTH = 320
IMAGE_HEIGHT = 240

IMAGE_ID = 20260713

FRAGMENT_DATA_SIZE = 512

random_generator = random.Random(2026)


# ============================================================
# Пути к файлам
# ============================================================

source_directory = Path("data/raw")
received_directory = Path("data/received")

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

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

source_path = (
    source_directory
    / "lab007_source.jpg"
)

received_path = (
    received_directory
    / "lab007_reassembled.jpg"
)


# ============================================================
# Создание тестового JPEG
# ============================================================

image = Image.new(
    "RGB",
    (IMAGE_WIDTH, IMAGE_HEIGHT),
)

pixels = image.load()

for y in range(IMAGE_HEIGHT):
    for x in range(IMAGE_WIDTH):

        red = (
            x * 255
            // (IMAGE_WIDTH - 1)
        )

        green = (
            y * 255
            // (IMAGE_HEIGHT - 1)
        )

        blue = (
            (x + y) * 255
            // (
                IMAGE_WIDTH
                + IMAGE_HEIGHT
                - 2
            )
        )

        pixels[x, y] = (
            red,
            green,
            blue,
        )

image.save(
    source_path,
    format="JPEG",
    quality=60,
    optimize=True,
)


# ============================================================
# Чтение изображения
# ============================================================

source_bytes = source_path.read_bytes()

source_hash = sha256(
    source_bytes
).hexdigest()


# ============================================================
# Разбиение изображения на фрагменты
# ============================================================

fragments = split_image_bytes(
    image_bytes=source_bytes,
    image_id=IMAGE_ID,
    fragment_data_size=FRAGMENT_DATA_SIZE,
)


# ============================================================
# Формирование цифровых пакетов
# ============================================================

packets = []

for fragment in fragments:

    fragment_payload = encode_image_fragment(
        fragment
    )

    packet = build_packet(
        payload=fragment_payload,
        message_type=MESSAGE_TYPE_IMAGE_FRAGMENT,
        sequence_number=fragment.fragment_index,
    )

    packets.append(packet)


# ============================================================
# Имитация доставки в произвольном порядке
# ============================================================

random_generator.shuffle(packets)

received_fragments = []

for packet in packets:

    parsed_packet = parse_packet(packet)

    assert (
        parsed_packet.message_type
        == MESSAGE_TYPE_IMAGE_FRAGMENT
    )

    fragment = decode_image_fragment(
        parsed_packet.payload
    )

    assert (
        parsed_packet.sequence_number
        == fragment.fragment_index
    )

    received_fragments.append(fragment)


# ============================================================
# Сборка изображения
# ============================================================

received_bytes = reassemble_image(
    received_fragments
)

received_path.write_bytes(
    received_bytes
)

received_hash = sha256(
    received_bytes
).hexdigest()


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

assert received_bytes == source_bytes
assert received_hash == source_hash

with Image.open(received_path) as received_image:
    received_image.verify()


# ============================================================
# Результаты
# ============================================================

print("=== Lab007. Фрагментация изображения ===")

print("\nИсходный файл:")
print(source_path)

print("\nВосстановленный файл:")
print(received_path)

print("\nРазмер изображения:")
print(
    IMAGE_WIDTH,
    "x",
    IMAGE_HEIGHT,
    "пикселей",
)

print("\nРазмер JPEG:")
print(
    len(source_bytes),
    "байт",
)

print("\nРазмер данных одного фрагмента:")
print(
    FRAGMENT_DATA_SIZE,
    "байт",
)

print("\nКоличество фрагментов:")
print(
    len(fragments)
)

print("\nSHA-256 исходного файла:")
print(source_hash)

print("\nSHA-256 восстановленного файла:")
print(received_hash)

print(
    "\nПроверка пройдена: "
    "JPEG восстановлен байт в байт."
)