/*
** task_hal.c - POSIX HAL implementation for mruby-task
**
** See Copyright Notice in mruby.h
**
** POSIX implementation using SIGALRM and setitimer() for timer,
** and sigprocmask() for interrupt protection.
**
** Supported platforms: Linux, macOS, BSD, Unix
**
** Note: When compiled for Emscripten/WASM, the SIGALRM timer is disabled
** because JavaScript handles tick calls via setInterval. Using both would
** cause tick_ to increment twice as fast, making sleep wake up early.
*/

#include <mruby.h>
#include "task_hal.h"
#ifndef __EMSCRIPTEN__
#include <signal.h>
#include <sys/time.h>
#endif
#include <time.h>
#include <unistd.h>
#include <stdint.h>

/* Time conversion constants */
#define NSEC_PER_MSEC 1000000ULL
#define NSEC_PER_SEC  1000000000ULL
#define USEC_PER_MSEC 1000ULL

#ifndef __EMSCRIPTEN__
/* Multi-VM support */
static mrb_state *vm_list[MRB_TASK_MAX_VMS];
static volatile sig_atomic_t vm_count = 0;
static sigset_t alarm_mask;

/* SIGALRM signal handler - ticks all registered VMs */
static void
sigalrm_handler(int sig)
{
  int i;
  (void)sig;
  /* Tick all registered VMs */
  for (i = 0; i < vm_count; i++) {
    if (vm_list[i]) {
      mrb_tick(vm_list[i]);
    }
  }
}
#endif /* __EMSCRIPTEN__ */

/*
 * HAL Interface Implementation
 */

void
mrb_hal_task_init(mrb_state *mrb)
{
  int i;

  /* Initialize task state */
  for (i = 0; i < 4; i++) {
    mrb->task.queues[i] = NULL;
  }
  mrb->task.tick = 0;
  mrb->task.wakeup_tick = UINT32_MAX;
  mrb->task.switching = FALSE;

#ifndef __EMSCRIPTEN__
  /* POSIX: Set up SIGALRM timer for tick handling */
  struct sigaction sa;
  struct itimerval timer;
  int vm_index = -1;

  /* Block SIGALRM during registration to avoid race */
  sigemptyset(&alarm_mask);
  sigaddset(&alarm_mask, SIGALRM);
  sigprocmask(SIG_BLOCK, &alarm_mask, NULL);

  /* Check if this VM is already registered */
  for (i = 0; i < vm_count; i++) {
    if (vm_list[i] == mrb) {
      vm_index = i;
      break;
    }
  }

  /* Register new VM if not already present */
  if (vm_index < 0) {
    if (vm_count >= MRB_TASK_MAX_VMS) {
      sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
      mrb_raisef(mrb, E_RUNTIME_ERROR,
                 "too many mrb_states with task scheduler (max: %d)",
                 MRB_TASK_MAX_VMS);
    }
    vm_list[vm_count] = mrb;
    vm_count++;
  }

  /* Set up signal handler and timer only for first VM */
  if (vm_count == 1) {
    /* Set up signal handler - SA_RESTART to avoid breaking IO operations */
    sa.sa_handler = sigalrm_handler;
    sa.sa_flags = SA_RESTART;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGALRM, &sa, NULL);

    /* Start timer */
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = MRB_TICK_UNIT * 1000;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = MRB_TICK_UNIT * 1000;
    setitimer(ITIMER_REAL, &timer, NULL);
  }

  /* Unblock SIGALRM */
  sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
#endif
}

void
mrb_task_enable_irq(void)
{
#ifndef __EMSCRIPTEN__
  sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
#endif
}

void
mrb_task_disable_irq(void)
{
#ifndef __EMSCRIPTEN__
  sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
#endif
}

void
mrb_hal_task_idle_cpu(mrb_state *mrb)
{
  (void)mrb;
  /* On POSIX, just pause briefly */
  usleep(MRB_TICK_UNIT * 1000);
}

void
mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec)
{
  struct timespec start, now, sleep_time;
  int ret;

  (void)mrb;

  /* Validate input to prevent overflow */
  if (usec < 0) {
    return;
  }

  ret = clock_gettime(CLOCK_MONOTONIC, &start);
  if (ret != 0) {
    /* Fallback to simple usleep if clock_gettime fails */
    usleep(usec);
    return;
  }

  uint64_t target_ns = (uint64_t)usec * USEC_PER_MSEC;

  /* Loop until enough real time has elapsed */
  while (1) {
    ret = clock_gettime(CLOCK_MONOTONIC, &now);
    if (ret != 0) {
      break;  /* Clock failure - exit loop */
    }

    uint64_t elapsed_ns = (uint64_t)(now.tv_sec - start.tv_sec) * NSEC_PER_SEC +
                          (uint64_t)(now.tv_nsec - start.tv_nsec);

    if (elapsed_ns >= target_ns) {
      break;
    }

    /* Sleep for remaining time, but at least 1ms to allow timer interrupts */
    uint64_t remaining_ns = target_ns - elapsed_ns;
    if (remaining_ns > NSEC_PER_MSEC) {
      sleep_time.tv_sec = remaining_ns / NSEC_PER_SEC;
      sleep_time.tv_nsec = remaining_ns % NSEC_PER_SEC;
    }
    else {
      sleep_time.tv_sec = 0;
      sleep_time.tv_nsec = NSEC_PER_MSEC;
    }

    nanosleep(&sleep_time, NULL);  /* Interrupted by signals - that's OK */
  }
}

void
mrb_hal_task_final(mrb_state *mrb)
{
#ifndef __EMSCRIPTEN__
  struct itimerval timer;
  int i, j;

  /* Block SIGALRM during unregistration */
  sigprocmask(SIG_BLOCK, &alarm_mask, NULL);

  /* Find and remove this VM from the list */
  for (i = 0; i < vm_count; i++) {
    if (vm_list[i] == mrb) {
      /* Shift remaining VMs down */
      for (j = i; j < vm_count - 1; j++) {
        vm_list[j] = vm_list[j + 1];
      }
      vm_list[vm_count - 1] = NULL;
      vm_count--;
      break;
    }
  }

  /* Stop timer if last VM */
  if (vm_count == 0) {
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &timer, NULL);
  }

  /* Unblock SIGALRM */
  sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
#else
  /* WASM: No timer cleanup needed */
  (void)mrb;
#endif
}
