#!/usr/bin/env python3
"""Un paquete perdido, tres streams: cuándo recibe la aplicación cada dato.

Simulador, no una medición: no hay servidor HTTP/3 en el contenedor del curso. El modelo se
reduce a lo que importa aquí, sin ventanas, ni ACK, ni control de congestión:

  - Los paquetes salen de a uno cada TX ms, y llegan ONE_WAY ms después.
  - Cada paquete lleva datos de un stream, por turnos: A, B, C, A, B, C...
  - El paquete perdido se recupera con una retransmisión que llega RECOVERY ms más tarde.

La única diferencia entre los modos es qué ordena el receptor:
  tcp   hay un solo orden, el del stream de bytes de TCP. Un hueco detiene todo lo que sigue.
  quic  hay un orden por stream. Un hueco detiene solo a su stream.
"""
import argparse

TX = 10
ONE_WAY = 50
RECOVERY = 100
STREAMS = "ABC"


def packets(count, streams=STREAMS):
    """Lista de (número, stream, número dentro del stream)."""
    seen = {name: 0 for name in streams}
    result = []
    for number in range(count):
        name = streams[number % len(streams)]
        result.append((number, name, seen[name]))
        seen[name] += 1
    return result


def arrival_times(count, lost):
    times = {number: number * TX + ONE_WAY for number in range(count)}
    if lost is not None:
        times[lost] += RECOVERY
    return times


def deliver(count, lost, mode, streams=STREAMS):
    """Devuelve {número de paquete: instante en que la aplicación recibe sus datos}."""
    times = arrival_times(count, lost)
    delivered = {}
    latest = {}
    for number, name, _ in packets(count, streams):
        key = "todos" if mode == "tcp" else name
        # Se entrega cuando llegó él y todo lo anterior en su mismo orden.
        latest[key] = max(latest.get(key, 0), times[number])
        delivered[number] = latest[key]
    return delivered


def delayed_by_stream(count, lost, mode, streams=STREAMS):
    """Por stream: cuántos paquetes esperaron más de lo necesario y cuánto, como máximo."""
    delivered = deliver(count, lost, mode, streams)
    clean = arrival_times(count, None)
    result = {name: [0, 0] for name in streams}
    for number, name, _ in packets(count, streams):
        wait = delivered[number] - clean[number]
        if wait > 0:
            result[name][0] += 1
            result[name][1] = max(result[name][1], wait)
    return result


def table(count, lost):
    tcp = deliver(count, lost, "tcp")
    quic = deliver(count, lost, "quic")
    times = arrival_times(count, lost)
    print("paq stream llega  entrega(TCP)  entrega(QUIC)")
    for number, name, index in packets(count):
        mark = "  <- perdido, llega retransmitido" if number == lost else ""
        print(f"{number:3} {name}{index:<5} {times[number]:5} {tcp[number]:12} {quic[number]:14}{mark}")


def summary(count, lost):
    print(f"\n{'':6}{'TCP: esperan, máx.':>22}{'QUIC: esperan, máx.':>24}")
    tcp = delayed_by_stream(count, lost, "tcp")
    quic = delayed_by_stream(count, lost, "quic")
    for name in STREAMS:
        print(f"{name:6}{tcp[name][0]:>12}{tcp[name][1]:>7} ms{quic[name][0]:>15}{quic[name][1]:>6} ms")


def main():
    parser = argparse.ArgumentParser(description="Un paquete perdido: TCP frente a QUIC")
    parser.add_argument("--packets", type=int, default=12)
    parser.add_argument("--lost", type=int, default=3, help="número del paquete perdido")
    args = parser.parse_args()
    print(f"TX={TX} ms, retardo={ONE_WAY} ms, recuperación={RECOVERY} ms, se pierde el paquete {args.lost}")
    table(args.packets, args.lost)
    summary(args.packets, args.lost)


if __name__ == "__main__":
    main()
