#include "srtf.h"
#include "main.h"
#include <stdio.h>
#include <stdlib.h>

double alpha = 0.5;

void srtf_run(process processes[], int num_processes) {
  // Compute predicted burst times based on exponential averaging
  cpu_time_t predicted_bursts[num_processes];
  predicted_bursts[0] = AVG_BURST;
  for (int i = 1; i < num_processes; i++) {
    predicted_bursts[i] = (cpu_time_t)(alpha * processes[i - 1].burst +
                                       (1 - alpha) * predicted_bursts[i - 1]);
  }

  // Allocate memory
  for (int i = 0; i < num_processes; i++) {
    processes[i].starts = malloc(5 * sizeof(cpu_time_t));
    processes[i].durations = malloc(5 * sizeof(cpu_time_t));
  }

  process *ready_queue[num_processes];
  int num_ready = 0;
  int num_processed = 0;
  int num_run = 0;
  cpu_time_t next_free = 0;
  while (num_run < num_processes) {
    // Add new arrivals to ready queue
    for (int i = num_processed;
         i < num_processes && processes[i].arrival <= next_free; i++) {
      ready_queue[num_ready] = &processes[i];
      num_ready++;
      num_processed++;
    }

    // Idle if ready queue is empty
    if (num_ready == 0) {
      next_free = processes[num_processed].arrival;
      continue;
    }

    // Remove process with shortest predicted burst remaining
    int next_i = 0;
    for (int i = 0; i < num_ready; i++) {
      int next_remaining = predicted_bursts[ready_queue[next_i]->pid] -
                           ready_queue[next_i]->executed;
      int i_remaining =
          predicted_bursts[ready_queue[i]->pid] - ready_queue[i]->executed;
      if (i_remaining < next_remaining) {
        next_i = i;
      }
    }
    process *next = ready_queue[next_i];
    num_ready--;
    for (int i = next_i; i < num_ready; i++) {
      ready_queue[i] = ready_queue[i + 1];
    }

    // Grow memory if not big enough
    if (next->num_schedules % 5 == 0) {
      if (!realloc(next->starts, next->num_schedules + 5) ||
          !realloc(next->durations, next->num_schedules + 5)) {
        exit(1);
      }
    }

    // Run process preemptively (only until next one arrives)
    size_t num_sch = next->num_schedules;
    next->starts[num_sch] = next_free;
    if (num_processed != num_processes &&
        next_free + next->burst - next->executed >
            processes[num_processed].arrival) {
      // Partial execution
      next->durations[num_sch] = processes[num_processed].arrival - next_free;
      next->executed += next->durations[num_sch];

      // Place back into execution
      ready_queue[num_ready] = next;
      num_ready++;
    } else {
      next->durations[num_sch] = next->burst - next->executed;
      num_run++;
    }
    next->num_schedules++;

    next_free = next->starts[num_sch] + next->durations[num_sch];
  }
}

scheduler srtf = (scheduler){.name = "srtf",
                             .description = "Shortest Remaining Time First",
                             .run = srtf_run,
                             .uses_priority = 0};
