// lotterytest: muestra que el planificador por lotería reparte la CPU en
// proporción a los tickets.
//
// Crea tres hijos con 30, 20 y 10 tickets que solo gastan CPU. Durante
// una ventana de tiempo mide cuántas veces el planificador eligió a cada
// uno (campo ticks de getpinfo) y al final imprime la proporción.
//
//   $ lotterytest [ventana] [paso]
//
// ventana: duración de la medición en ticks del reloj (por defecto 300,
//          unos 30 segundos; en xv6-riscv hay un tick cada ~0.1 s).
// paso:    cada cuántos ticks imprimir una línea "serie" (por defecto 20).
//          Esas líneas sirven para hacer el gráfico del informe.
//
// Debe correrse con una sola CPU (make qemu CPUS=1): con varias CPUs cada
// hijo puede tener una CPU para él solo y la proporción se pierde.

#include "kernel/types.h"
#include "kernel/param.h"
#include "kernel/pstat.h"
#include "user/user.h"

#define NCHILD 3

static int tickets[NCHILD] = {30, 20, 10};
static int pids[NCHILD];
static struct pstat ps;

static void
spin(void)
{
  volatile uint64 x = 0;
  for (;;)
    x++;
}

// Lee los ticks de cada hijo en out[]. Devuelve 0 si todo salió bien.
static int
snapshot(int out[NCHILD])
{
  if (getpinfo(&ps) != 0) {
    printf("lotterytest: FAIL getpinfo devolvio error\n");
    return -1;
  }
  for (int c = 0; c < NCHILD; c++) {
    out[c] = -1;
    for (int i = 0; i < NPROC; i++)
      if (ps.inuse[i] && ps.pid[i] == pids[c])
        out[c] = ps.ticks[i];
    if (out[c] < 0) {
      printf("lotterytest: FAIL el hijo %d no aparece en getpinfo\n", pids[c]);
      return -1;
    }
  }
  return 0;
}

static void
killall(void)
{
  for (int c = 0; c < NCHILD; c++) {
    if (pids[c] > 0) {
      kill(pids[c]);
      wait(0);
      pids[c] = 0;
    }
  }
}

// Termina siempre con la misma línea final, para que test-p2.sh sepa
// que el programa acabó (bien o mal).
static void
finish(int status)
{
  killall();
  printf("lotterytest: done\n");
  exit(status);
}

int
main(int argc, char *argv[])
{
  int window = 300, step = 20;
  if (argc > 1)
    window = atoi(argv[1]);
  if (argc > 2)
    step = atoi(argv[2]);
  if (window <= 0 || step <= 0 || step > window) {
    fprintf(2, "uso: lotterytest [ventana] [paso]\n");
    exit(1);
  }

  // El padre se da muchos tickets para despertar a tiempo y tomar las
  // mediciones aunque los hijos estén ocupando la CPU.
  if (settickets(1000) != 0) {
    printf("lotterytest: FAIL settickets(1000) devolvio error\n");
    finish(1);
  }

  for (int c = 0; c < NCHILD; c++) {
    int pid = fork();
    if (pid < 0) {
      printf("lotterytest: FAIL fork\n");
      finish(1);
    }
    if (pid == 0) {
      if (settickets(tickets[c]) != 0)
        printf("lotterytest: FAIL settickets(%d) en el hijo\n", tickets[c]);
      spin();
    }
    pids[c] = pid;
  }

  // Dejar que los hijos fijen sus tickets antes de empezar a medir.
  pause(10);

  int start[NCHILD], now[NCHILD];
  if (snapshot(start) != 0)
    finish(1);

  printf("lotterytest: ventana %d ticks, tickets %d:%d:%d\n", window,
         tickets[0], tickets[1], tickets[2]);
  for (int t = step; t <= window; t += step) {
    pause(step);
    if (snapshot(now) != 0)
      finish(1);
    printf("lotterytest: serie %d %d %d %d\n", t, now[0] - start[0],
           now[1] - start[1], now[2] - start[2]);
  }
  killall();

  int total = 0;
  for (int c = 0; c < NCHILD; c++)
    total += now[c] - start[c];
  for (int c = 0; c < NCHILD; c++)
    printf("lotterytest: tickets %d ticks %d\n", tickets[c],
           now[c] - start[c]);
  printf("lotterytest: total %d\n", total);
  if (total > 0) {
    // Sin punto flotante en xv6: porcentajes en décimas (500 = 50.0 %).
    int all = tickets[0] + tickets[1] + tickets[2];
    printf("lotterytest: esperado (por mil) %d %d %d\n",
           tickets[0] * 1000 / all, tickets[1] * 1000 / all,
           tickets[2] * 1000 / all);
    printf("lotterytest: obtenido (por mil) %d %d %d\n",
           (now[0] - start[0]) * 1000 / total,
           (now[1] - start[1]) * 1000 / total,
           (now[2] - start[2]) * 1000 / total);
  }
  finish(0);
}
