// queue_sim.c: simula la cola de salida de un router.
//
//   ./queue_sim <llegadas> <rho> [buffer] [paquetes]
//
// Todos los paquetes tienen el mismo tamaño L y el enlace transmite a R, así que
// medimos el tiempo en unidades de L/R: transmitir un paquete toma exactamente 1.
//
//   llegadas   periodic   un paquete cada 1/rho, sin azar
//              poisson    tiempos entre llegadas exponenciales, de media 1/rho
//              burst:N    ráfagas de N paquetes juntos, una cada N/rho
//   rho        intensidad de tráfico La/R
//   buffer     cuántos paquetes caben, contando el que se transmite (0 = sin límite)
//   paquetes   cuántos paquetes simular (por defecto, diez millones)
//
// El retardo que se informa es solo el de cola: lo que el paquete espera antes
// de empezar a transmitirse. La semilla es fija, así que la salida se repite.
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEFAULT_PACKETS 10000000L
#define SEED 88172645463325252ULL

enum arrivals { PERIODIC, POISSON, BURST };

struct config {
    enum arrivals arrivals;
    long burst_size;
    double rho;
    long buffer;
    long packets;
};

// Generador xorshift64*: uniforme en (0, 1]. Propio para no depender de la
// biblioteca de C y obtener lo mismo en todas las máquinas.
static double next_uniform(uint64_t *state) {
    uint64_t x = *state;
    x ^= x >> 12;
    x ^= x << 25;
    x ^= x >> 27;
    *state = x;
    return ((x * 2685821657736338717ULL) >> 11) * (1.0 / 9007199254740992.0) + 1e-16;
}

// Tiempo entre la llegada del paquete i-1 y la del paquete i.
static double next_gap(const struct config *cfg, long i, uint64_t *state) {
    switch (cfg->arrivals) {
    case PERIODIC:
        return 1 / cfg->rho;
    case POISSON:
        return -log(next_uniform(state)) / cfg->rho;
    case BURST:
        return i % cfg->burst_size == 0 ? cfg->burst_size / cfg->rho : 0;
    }
    return 0;
}

static void simulate(const struct config *cfg) {
    uint64_t state = SEED;
    double backlog = 0; // trabajo pendiente en el enlace, en unidades de L/R
    double total_delay = 0;
    double max_delay = 0;
    long sent = 0;
    long dropped = 0;

    for (long i = 0; i < cfg->packets; i++) {
        backlog -= next_gap(cfg, i, &state);
        if (backlog < 0) {
            backlog = 0;
        }
        // Con paquetes iguales, ceil(backlog) es cuántos hay en el buffer.
        if (cfg->buffer > 0 && ceil(backlog) >= cfg->buffer) {
            dropped++;
            continue;
        }
        total_delay += backlog;
        if (backlog > max_delay) {
            max_delay = backlog;
        }
        backlog += 1;
        sent++;
    }
    printf("rho=%.2f  retardo_medio=%10.2f  retardo_max=%10.2f  perdida=%5.1f%%\n",
           cfg->rho, sent > 0 ? total_delay / sent : 0.0, max_delay,
           100.0 * dropped / cfg->packets);
}

// Lee el tipo de llegadas. Devuelve 0 si el texto es válido.
static int parse_arrivals(const char *text, struct config *cfg) {
    if (strcmp(text, "periodic") == 0) {
        cfg->arrivals = PERIODIC;
    } else if (strcmp(text, "poisson") == 0) {
        cfg->arrivals = POISSON;
    } else if (strncmp(text, "burst:", 6) == 0 && atol(text + 6) > 0) {
        cfg->arrivals = BURST;
        cfg->burst_size = atol(text + 6);
    } else {
        return -1;
    }
    return 0;
}

int main(int argc, char *argv[]) {
    struct config cfg = {.packets = DEFAULT_PACKETS};
    if (argc < 3 || argc > 5 || parse_arrivals(argv[1], &cfg) != 0) {
        fprintf(stderr, "uso: %s <periodic|poisson|burst:N> <rho> [buffer] [paquetes]\n", argv[0]);
        return EXIT_FAILURE;
    }
    cfg.rho = atof(argv[2]);
    if (argc >= 4) {
        cfg.buffer = atol(argv[3]);
    }
    if (argc == 5) {
        cfg.packets = atol(argv[4]);
    }
    if (cfg.rho <= 0 || cfg.buffer < 0 || cfg.packets <= 0) {
        fprintf(stderr, "queue_sim: se necesita rho > 0, buffer >= 0 y paquetes > 0\n");
        return EXIT_FAILURE;
    }
    simulate(&cfg);
    return EXIT_SUCCESS;
}
