#include "hub_manager.h"
#include "elm_loader.h"


#define MAX_PROGRAMS        64
#define MAX_EVENTS          128
#define MAX_MESSAGES        256
#define EVENT_STACK_SIZE    8192

static program_manager  p_manager;
static bay              pm_initialized = 0;
static program_card    *program_pool   = NULL;
static event_card      *event_pool     = NULL;
static message         *message_pool   = NULL;
static aisle            next_program_slot = 1;
static aisle            next_event_slot   = 1;

static void idle_event_main(void *arg)
{
    (void)arg;
    while (1)
    {
        asm volatile("hlt");
    }
}


// ============================================================================
// INIT
// ============================================================================

/* program manager handles tracking active programs and shared library
 * reference counts. .lmods = shared / .lmod = static / .mod = object code.
 * when no program is using a shared lib we end it fast to keep system fast. */
void init_program_manager(void)
{
    write_line("[PROGRAM]      sizeof(program)=%u  sizeof(event)=%u  sizeof(message)=%u\n",
          sizeof(program_card), sizeof(event), sizeof(message));

    memset(&p_manager, 0, sizeof(program_manager));

    program_pool = (program_card *)allocate_core_malloc(MAX_PROGRAMS * sizeof(program_card));
    if (!program_pool)
    {
        write_line("[PROGRAM]      FATAL: program pool\n");
        while (1);
    }
    memset(program_pool, 0, MAX_PROGRAMS * sizeof(program_card));
    write_line("[PROGRAM]      program_pool at 0x%x\n", (aisle)program_pool);

    event_pool = (event *)allocate_core_malloc(MAX_EVENTS * sizeof(event));
    if (!event_pool)
    {
        write_line("[PROGRAM]      FATAL: event pool\n");
        while (1);
    }
    memset(event_pool, 0, MAX_EVENTS * sizeof(event));
    write_line("[PROGRAM]      event_pool   at 0x%x\n", (aisle)event_pool);

    message_pool = (message *)allocate_core_malloc(MAX_MESSAGES * sizeof(message));
    if (!message_pool)
    {
        write_line("[PROGRAM]      FATAL: message pool\n");
        while (1);
    }
    memset(message_pool, 0, MAX_MESSAGES * sizeof(message));
    write_line("[PROGRAM]      message_pool at 0x%x\n", (aisle)message_pool);

    p_manager.all_programs = (program_card **)allocate_core_malloc(MAX_PROGRAMS * sizeof(program_card *));
    if (!p_manager.all_programs)
    {
        write_line("[PROGRAM]      FATAL: lookup table\n");
        while (1);
    }
    memset(p_manager.all_programs, 0, MAX_PROGRAMS * sizeof(program_card *));

    p_manager.program_count          = 0;
    p_manager.next_program_index_tag = 1;
    p_manager.max_programs           = MAX_PROGRAMS;
    p_manager.current_program        = NULL;
    p_manager.program_list_head      = NULL;
    p_manager.msg_pool               = message_pool;
    p_manager.msg_pool_size          = MAX_MESSAGES;
    p_manager.next_msg_index_tag     = 1;

    p_manager.idle_program = _create_program("core_system", "[core]", 0, master_permission);
    if (p_manager.idle_program)
    {
        event *idle_evt = _create_event(p_manager.idle_program, idle_event_main, NULL, priority_idle);
        if (idle_evt)
        {
            p_manager.idle_program->main_event = idle_evt;
        }
    }

    pm_initialized = 1;
    write_line("[PROGRAM]      manager ready (programs=%u events=%u messages=%u)\n",
          MAX_PROGRAMS, MAX_EVENTS, MAX_MESSAGES);
}

program_manager *_get_program_manager(void)
{
    return &p_manager;
}

program_card *_get_current_program(void)
{
    return p_manager.current_program;
}

void _set_current_program(program_card *prog)
{
    p_manager.current_program = prog;
}

program_card *_get_program_index_tag(aisle program_index_tag)
{
    if (program_index_tag == 0 || program_index_tag >= MAX_PROGRAMS)
    {
        return NULL;
    }
    return p_manager.all_programs[program_index_tag];
}

event_card *_get_event_index_tag(aisle event_index_tag)
{
    if (event_index_tag == 0 || event_index_tag >= next_event_slot)
    {
        return NULL;
    }
    return &event_pool[event_index_tag];
}


// ============================================================================
// PROGRAM
// ============================================================================

program_card *_create_program(const char *name, const char *path,
                               aisle user_index_tag, permission_level perm)
{
    if (next_program_slot >= MAX_PROGRAMS)
    {
        write_line("[PROGRAM]      out of slots\n");
        return NULL;
    }

    program_card *prog = &program_pool[next_program_slot];
    memset(prog, 0, sizeof(program_card));

    prog->program_index_tag = next_program_slot;
    prog->program_child_index_tag =
        p_manager.current_program ? p_manager.current_program->program_index_tag : 0;

    strncpy(prog->name,            name, sizeof(prog->name)            - 1);
    strncpy(prog->executable_path, path, sizeof(prog->executable_path) - 1);

    prog->program_state      = program_ready;
    prog->user_index_tag     = user_index_tag;
    prog->permission_level   = perm;
    prog->next_msg_index_tag = 1;

    prog->inbox  = _create_message_queue(program_max_messages);
    prog->outbox = _create_message_queue(program_max_messages);

    p_manager.all_programs[next_program_slot] = prog;
    prog->next                  = p_manager.program_list_head;
    p_manager.program_list_head = prog;

    p_manager.program_count++;
    p_manager.total_programs_created++;
    next_program_slot++;

    write_line("[PROGRAM]      created '%s' index=%u user=%u perm=%u\n",
          name, prog->program_index_tag, user_index_tag, perm);
    return prog;
}

/*
 * _execute_program
 *
 * loads and launches an elm binary from path. links to parent if given.
 *
 * previously this called _create_program before elm_load_card, which also
 * calls _create_program internally. that left a ghost slot in program_list_head
 * with program_state = program_ready and main_event = NULL every single launch.
 * any list walker dereffing prog->main_event without a null check would hang.
 * it also double-called _create_child_program making parent->child_count wrong.
 *
 * fix: do not call _create_program here at all. elm_load_card owns program
 * creation. retrieve the program by index after the load, link parent once.
 */
program_card *_execute_program(const char *path, const char *name, program_card *parent)
{
    if (!path || !name)
    {
        return NULL;
    }

    file_card *card = _open_card(path, card_flag_read);
    if (!card)
    {
        write_line("[PROGRAM]      failed to open %s\n", path);
        return NULL;
    }

    permission_level caller_perm = parent ? parent->permission_level : user_permission;
    elm_load_result  res         = elm_load_card(card, (int)caller_perm);

    _close_card(card);

    if (res.result != _elm_loader_ok)
    {
        write_line("[PROGRAM]      elm_load_card failed  path=%s  err=%d\n", path, res.result);
        return NULL;
    }

    program_card *loaded_prog = _get_program_index_tag((aisle)res.program_index);
    if (!loaded_prog)
    {
        write_line("[PROGRAM]      elm_load_card: program index %d not found\n", res.program_index);
        return NULL;
    }

    if (parent)
    {
        _create_child_program(parent, loaded_prog);
    }

    loaded_prog->program_state = program_running;

    write_line("[PROGRAM]      executing '%s'  prog=%d  event=%d\n",
               name, res.program_index, res.event_index);

    return loaded_prog;
}

void _terminate_program(program_card *prog, aisles exit_code)
{
    if (!prog)
    {
        return;
    }
    prog->program_state = program_terminated;
    prog->exit_code     = exit_code;

    _flush_program(prog);

    for (aisle i = 0; i < prog->event_count; i++)
    {
        if (prog->events && prog->events[i])
        {
            _remove_event_from_queue(prog->events[i]);
        }
    }

    if (prog->parent &&
        prog->parent->program_state == program_waiting &&
        prog->parent->waiting_for_child_index_tag == prog->program_index_tag)
    {
        prog->parent->program_state = program_ready;
        if (prog->parent->main_event)
        {
            _unblock_event(prog->parent->main_event);
        }
    }

    write_line("[PROGRAM]      terminated '%s' exit=%d\n", prog->name, exit_code);
}

/* exit is the clean path, terminate is the harsh path */
void _exit_program(program_card *prog, aisles exit_code)
{
    (void)prog;
    (void)exit_code;
}

void _clean_terminated_program(program_card *prog)
{
    if (!prog)
    {
        return;
    }

    for (aisle i = 0; i < prog->event_count; i++)
    {
        if (prog->events && prog->events[i])
        {
            _remove_event_from_queue(prog->events[i]);
        }
    }

    if (prog->inbox)
    {
        _destroy_message_queue(prog->inbox);
    }
    if (prog->outbox)
    {
        _destroy_message_queue(prog->outbox);
    }

    if (prog->program_index_tag < MAX_PROGRAMS)
    {
        p_manager.all_programs[prog->program_index_tag] = NULL;
    }

    program_card *prev = NULL;
    program_card *cur  = p_manager.program_list_head;
    while (cur)
    {
        if (cur == prog)
        {
            if (prev)
            {
                prev->next = cur->next;
            }
            else
            {
                p_manager.program_list_head = cur->next;
            }
            break;
        }
        prev = cur;
        cur  = cur->next;
    }

    p_manager.program_count--;
    p_manager.total_programs_destroyed++;
    write_line("[PROGRAM]      destroyed '%s' index=%u\n",
          prog->name, prog->program_index_tag);
}

void _suspend_program(program_card *prog)
{
    if (!prog)
    {
        return;
    }
    prog->program_state = program_suspended;
    for (aisle i = 0; i < prog->event_count; i++)
    {
        if (prog->events && prog->events[i])
        {
            _block_event(prog->events[i]);
        }
    }
    write_line("[PROGRAM]      suspended '%s'\n", prog->name);
}

void _resume_program(program_card *prog)
{
    if (!prog)
    {
        return;
    }
    prog->program_state = program_running;
    for (aisle i = 0; i < prog->event_count; i++)
    {
        if (prog->events && prog->events[i])
        {
            _unblock_event(prog->events[i]);
        }
    }
    write_line("[PROGRAM]      resumed '%s'\n", prog->name);
}

bay _create_child_program(program_card *parent, program_card *child)
{
    if (!parent || !child)
    {
        return 0;
    }
    child->parent = parent;
    parent->child_count++;
    return 1;
}

bay _execute_child_program(program_card *parent, program_card *child)
{
    (void)parent;
    (void)child;
    return 0;
}

bay _exit_child_program(program_card *parent, program_card *child)
{
    if (!parent || !child)
    {
        return 0;
    }
    child->parent = NULL;
    if (parent->child_count > 0)
    {
        parent->child_count--;
    }
    return 1;
}

bay _terminate_child_program(program_card *parent, program_card *child)
{
    if (!parent || !child)
    {
        return 0;
    }
    child->parent = NULL;
    if (parent->child_count > 0)
    {
        parent->child_count--;
    }
    return 1;
}

bay _clean_terminated_child_program(program_card *parent, program_card *child)
{
    if (!parent || !child)
    {
        return 0;
    }
    child->parent = NULL;
    if (parent->child_count > 0)
    {
        parent->child_count--;
    }
    return 1;
}

void _suspend_child_program(program_card *prog)
{
    if (!prog)
    {
        return;
    }
    prog->program_state = program_suspended;
    for (aisle i = 0; i < prog->event_count; i++)
    {
        if (prog->events && prog->events[i])
        {
            _block_event(prog->events[i]);
        }
    }
    write_line("[PROGRAM]      suspended '%s'\n", prog->name);
}

void _resume_child_program(program_card *prog)
{
    if (!prog)
    {
        return;
    }
    prog->program_state = program_running;
    for (aisle i = 0; i < prog->event_count; i++)
    {
        if (prog->events && prog->events[i])
        {
            _unblock_event(prog->events[i]);
        }
    }
    write_line("[PROGRAM]      resumed '%s'\n", prog->name);
}


// ============================================================================
// EVENT
// ============================================================================

/* exit stub pushed as the cdecl return address on every event stack.
 * when an event's entry_point returns instead of calling terminate,
 * execution lands here rather than jumping to 0x00000000. */
static void _event_exit_stub(void)
{
    event_manager *em = _get_event_manager();
    if (em && em->current_event)
    {
        program_card *prog = em->current_event->program_ptr;
        if (prog)
        {
            window_hub   *wh   = _get_window_hub();
            desktop_card *desk = _get_active_desktop();
            if (wh)
            {
                aisle i = wh->window_count;
                while (i > 0)
                {
                    i--;
                    window_card *w = wh->windows[i];
                    if (w && w->owner_program_index_tag == prog->program_index_tag)
                    {
                        _destroy_window(w);
                    }
                }
            }
            if (desk)
            {
                desk->is_used_cache = used_cache_all;
            }
        }
        _terminate_event(em->current_event);
    }
    /* do NOT call _yield_event here — it re-enqueues the just-terminated
     * event as event_ready, the scheduler picks it up, and it resumes here
     * with IF=0 (if reached via a syscall), triggering HLT with IF=0. */
    _switch_away_from_dead_event();
    asm volatile("sti");
    for (;;)
    {
        asm volatile("hlt");
    }
}

event *_create_event(program_card *prog, void (*entry_point)(void *), void *arg, bay priority)
{
    if (!prog)
    {
        write_line("[EVENT]        NULL program\n");
        return NULL;
    }
    if (next_event_slot >= MAX_EVENTS)
    {
        write_line("[EVENT]        pool exhausted\n");
        return NULL;
    }

    event *evt = &event_pool[next_event_slot];
    memset(evt, 0, sizeof(event));

    evt->event_index_tag = next_event_slot;
    evt->program_ptr     = prog;
    evt->state           = event_ready;
    evt->priority        = priority;

    strncpy(evt->name, prog->name, 63);
    evt->name[63] = '\0';

    char *stack = (char *)allocate_core_malloc(EVENT_STACK_SIZE);
    if (!stack)
    {
        stack = (char *)allocate_program_memory(prog->program_index_tag, EVENT_STACK_SIZE);
    }
    if (!stack)
    {
        write_line("[EVENT]        stack alloc failed\n");
        return NULL;
    }
    memset(stack, 0, EVENT_STACK_SIZE);

    evt->stack_start = (aisle)stack;
    evt->stack_size  = EVENT_STACK_SIZE;

    /* allocate a private kernel stack for this event.
     * TSS.ESP0 is updated to this before every ring-3 iret so hardware IRQs
     * that fire while the program is in ring 3 land here, not on the stale
     * global start_stack from the linker.
     * ring-0 events leave core_stack_top = 0 (no TSS update). */
    #define CORE_STACK_SIZE 8192
    char *kstack = (char *)allocate_core_malloc(CORE_STACK_SIZE);
    if (kstack)
    {
        memset(kstack, 0, CORE_STACK_SIZE);
        evt->core_stack_top = (aisle)(kstack + CORE_STACK_SIZE);
    }
    else
    {
        write_line("[EVENT]        core stack alloc failed for event %u -- ring-3 unsafe\n",
                   next_event_slot);
        evt->core_stack_top = 0;
    }

    if (entry_point)
    {
        aisle *stack_top = (aisle *)(stack + EVENT_STACK_SIZE);
        *(--stack_top) = (aisle)arg;
        *(--stack_top) = (aisle)_event_exit_stub;
        evt->context.eip    = (aisle)entry_point;
        evt->context.esp    = (aisle)stack_top;
        evt->context.ebp    = evt->context.esp;
        evt->context.eflags = 0x00000202;
        evt->context.cs     = 0x08;
        evt->context.ds     = evt->context.es = evt->context.fs =
        evt->context.gs     = evt->context.ss = 0x10;
    }

    next_event_slot++;
    _add_event_to_program(prog, evt);

    write_line("[EVENT]        created event %u for '%s' (priority=%u stack=0x%x)\n",
          evt->event_index_tag, prog->name, priority, evt->stack_start);
    return evt;
}

event_card *_execute_event(program_card *prog, void (*entry_point)(void *),
                            void *arg, bay priority)
{
    if (!prog || !entry_point)
    {
        write_line("[EVENT]        _execute_event: NULL prog or entry\n");
        return NULL;
    }

    event *evt = _create_event(prog, entry_point, arg, priority);
    if (!evt)
    {
        return NULL;
    }

    _add_event_to_queue(evt);

    write_line("[EVENT]        _execute_event %u for '%s' enqueued\n",
          evt->event_index_tag, prog->name);
    return evt;
}

void _terminate_event(event_card *evt)
{
    if (!evt)
    {
        return;
    }
    evt->state = event_blocked;
    _remove_event_from_queue(evt);
    _remove_event_from_program(evt->program_ptr, evt);
    write_line("[EVENT]        terminated event %u\n", evt->event_index_tag);
}

void _exit_event(event_card *evt)
{
    if (!evt)
    {
        return;
    }
    _terminate_event(evt);
    write_line("[EVENT]        exited event %u\n", evt->event_index_tag);
}

void _clean_terminated_event(event *evt)
{
    if (!evt)
    {
        return;
    }
    if (evt->stack_start)
    {
        free_core_malloc((void *)evt->stack_start);
    }
    evt->stack_start = 0;
    evt->state       = event_blocked;
    write_line("[EVENT]        cleaned event %u\n", evt->event_index_tag);
}

void _suspend_event(event *evt)
{
    if (!evt)
    {
        return;
    }
    _block_event(evt);
    write_line("[EVENT]        suspended event %u\n", evt->event_index_tag);
}

void _resume_event(event *evt)
{
    if (!evt)
    {
        return;
    }
    _unblock_event(evt);
    write_line("[EVENT]        resumed event %u\n", evt->event_index_tag);
}

bay _add_event_to_program(program_card *prog, event *evt)
{
    if (!prog || !evt)
    {
        return 0;
    }
    if (prog->event_count >= program_max_events)
    {
        write_line("[EVENT]        '%s' event list full\n", prog->name);
        return 0;
    }
    if (!prog->events)
    {
        prog->events = (event **)allocate_core_malloc(program_max_events * sizeof(event *));
        if (!prog->events)
        {
            return 0;
        }
        memset(prog->events, 0, program_max_events * sizeof(event *));
        prog->event_capacity = program_max_events;
    }
    prog->events[prog->event_count++] = evt;
    return 1;
}

bay _remove_event_from_program(program_card *prog, event *evt)
{
    if (!prog || !evt)
    {
        return 0;
    }
    for (aisle i = 0; i < prog->event_count; i++)
    {
        if (prog->events[i] == evt)
        {
            for (aisle j = i; j < prog->event_count - 1; j++)
            {
                prog->events[j] = prog->events[j + 1];
            }
            prog->event_count--;
            _remove_event_from_queue(evt);
            return 1;
        }
    }
    return 0;
}


// ============================================================================
// ACTION
// ============================================================================

action_card *_create_action(event *evt, action_type type, void (*handler)(action_card *, void *))
{
    if (!evt)
    {
        return NULL;
    }
    action_card *act = (action_card *)allocate_core_malloc(sizeof(action_card));
    if (!act)
    {
        return NULL;
    }
    memset(act, 0, sizeof(action_card));
    act->type      = type;
    act->event_ptr = evt;
    act->handler   = handler;
    return act;
}

void _terminate_action(action_card *act)
{
    if (act)
    {
        free_core_malloc(act);
    }
}

void _exit_action(action_card *act)
{
    if (act)
    {
        free_core_malloc(act);
    }
}

bay _add_action_to_event_queue(event *evt, action_card *act)
{
    if (!evt || !act)
    {
        return 0;
    }
    act->next    = evt->actions;
    evt->actions = act;
    evt->action_count++;
    return 1;
}

bay _remove_action_from_event_queue(event *evt, action_card *act)
{
    if (!evt || !act)
    {
        return 0;
    }
    action_card *prev = NULL;
    action_card *cur  = evt->actions;
    while (cur)
    {
        if (cur == act)
        {
            if (prev)
            {
                prev->next = cur->next;
            }
            else
            {
                evt->actions = cur->next;
            }
            evt->action_count--;
            return 1;
        }
        prev = cur;
        cur  = cur->next;
    }
    return 0;
}

void _trigger_action(action_card *act, void *data)
{
    if (act && act->handler)
    {
        act->handler(act, data);
    }
}


// ============================================================================
// MESSAGE QUEUE
// ============================================================================

msg_queue *_create_message_queue(aisle capacity)
{
    msg_queue *q = (msg_queue *)allocate_core_malloc(sizeof(msg_queue));
    if (!q)
    {
        return NULL;
    }
    q->slots = (message **)allocate_core_malloc(capacity * sizeof(message *));
    if (!q->slots)
    {
        free_core_malloc(q);
        return NULL;
    }
    memset(q->slots, 0, capacity * sizeof(message *));
    q->head     = 0;
    q->tail     = 0;
    q->count    = 0;
    q->capacity = capacity;
    return q;
}

void _destroy_message_queue(msg_queue *q)
{
    if (!q)
    {
        return;
    }
    if (q->slots)
    {
        free_core_malloc(q->slots);
    }
    free_core_malloc(q);
}

bay _message_queue_empty(msg_queue *q)
{
    return q ? (q->count == 0) : 1;
}

bay _message_queue_full(msg_queue *q)
{
    return q ? (q->count >= q->capacity) : 1;
}

bay _enqueue_message(msg_queue *q, message *msg)
{
    if (!q || !msg || _message_queue_full(q))
    {
        return 0;
    }
    q->slots[q->tail] = msg;
    q->tail = (q->tail + 1) % q->capacity;
    q->count++;
    return 1;
}

message *_dequeue_message(msg_queue *q)
{
    if (!q || _message_queue_empty(q))
    {
        return NULL;
    }
    message *msg      = q->slots[q->head];
    q->slots[q->head] = NULL;
    q->head = (q->head + 1) % q->capacity;
    q->count--;
    return msg;
}


// ============================================================================
// MESSAGE POOL
// ============================================================================

message *_alloc_message(void)
{
    for (aisle i = 0; i < MAX_MESSAGES; i++)
    {
        if (message_pool[i].msg_index_tag == 0)
        {
            memset(&message_pool[i], 0, sizeof(message));
            message_pool[i].msg_index_tag = p_manager.next_msg_index_tag++;
            return &message_pool[i];
        }
    }
    write_line("[MSG]          pool exhausted\n");
    return NULL;
}

void _free_message(message *msg)
{
    if (msg)
    {
        memset(msg, 0, sizeof(message));
    }
}


// ============================================================================
// SEND
// ============================================================================

aisle _send_message(aisle sender_program_index_tag,
                    aisle receiver_program_index_tag,
                    msg_type type, aisle flags,
                    const void *data, aisle data_size)
{
    program_card *receiver = _get_program_index_tag(receiver_program_index_tag);
    if (!receiver)
    {
        write_line("[MSG]          receiver %u not found\n", receiver_program_index_tag);
        return 0;
    }
    if (!receiver->inbox || _message_queue_full(receiver->inbox))
    {
        write_line("[MSG]          inbox full (prog %u)\n", receiver_program_index_tag);
        return 0;
    }

    message *msg = _alloc_message();
    if (!msg)
    {
        return 0;
    }

    msg->type                       = type;
    msg->flags                      = flags;
    msg->sender_program_index_tag   = sender_program_index_tag;
    msg->receiver_program_index_tag = receiver_program_index_tag;
    msg->delivered                  = 0;
    msg->responded                  = 0;
    msg->next                       = NULL;

    if (data && data_size > 0)
    {
        aisle sz = data_size < (aisle)sizeof(msg->data)
                   ? data_size : (aisle)sizeof(msg->data);
        memcpy(msg->data, data, sz);
        msg->data_size = sz;
    }

    _enqueue_message(receiver->inbox, msg);
    p_manager.total_messages_sent++;

    if (receiver->main_event &&
        receiver->main_event->state == event_blocked &&
        receiver->main_event->wait_msg_index_tag == 0)
    {
        _unblock_event(receiver->main_event);
    }

    write_line("[MSG]          %u -> %u  index=%u type=%u size=%u\n",
          sender_program_index_tag, receiver_program_index_tag,
          msg->msg_index_tag, type, msg->data_size);

    if ((flags & MSG_FLAG_BLOCKING) && p_manager.current_program)
    {
        event *cur_evt = p_manager.current_program->main_event;
        if (cur_evt)
        {
            cur_evt->wait_msg_index_tag = msg->msg_index_tag;
            _block_event(cur_evt);
        }
    }

    return msg->msg_index_tag;
}

aisle _request_message(aisle sender_program_index_tag,
                        aisle receiver_program_index_tag,
                        const void *data, aisle data_size)
{
    return _send_message(sender_program_index_tag, receiver_program_index_tag,
                         msg_type_request, MSG_FLAG_BLOCKING, data, data_size);
}

aisle _respond_message(aisle responder_program_index_tag,
                        aisle original_msg_index_tag,
                        const void *data, aisle data_size)
{
    aisle requester_index_tag = 0;

    for (aisle i = 0; i < MAX_MESSAGES; i++)
    {
        if (message_pool[i].msg_index_tag == original_msg_index_tag)
        {
            requester_index_tag       = message_pool[i].sender_program_index_tag;
            message_pool[i].responded = 1;
            break;
        }
    }

    if (!requester_index_tag)
    {
        write_line("[MSG]          respond: msg %u not found\n", original_msg_index_tag);
        return 0;
    }

    program_card *requester = _get_program_index_tag(requester_index_tag);
    if (requester && requester->main_event)
    {
        event *evt = requester->main_event;
        if (evt->state == event_blocked &&
            evt->wait_msg_index_tag == original_msg_index_tag)
        {
            evt->wait_msg_index_tag = 0;
            _unblock_event(evt);
        }
    }

    aisle reply_index_tag = _send_message(responder_program_index_tag,
                                          requester_index_tag,
                                          msg_type_response, MSG_FLAG_RESPONSE,
                                          data, data_size);

    for (aisle i = 0; i < MAX_MESSAGES; i++)
    {
        if (message_pool[i].msg_index_tag == reply_index_tag)
        {
            message_pool[i].reply_to_msg_index_tag = original_msg_index_tag;
            break;
        }
    }

    write_line("[MSG]          respond %u -> %u  reply=%u re:msg=%u\n",
          responder_program_index_tag, requester_index_tag,
          reply_index_tag, original_msg_index_tag);

    return reply_index_tag;
}

aisle _broadcast_message(aisle sender_program_index_tag, msg_type type,
                          const void *data, aisle data_size)
{
    aisle         count = 0;
    program_card *cur   = p_manager.program_list_head;

    while (cur)
    {
        if (cur->program_index_tag != sender_program_index_tag &&
            cur->program_state    != program_terminated)
        {
            _send_message(sender_program_index_tag, cur->program_index_tag,
                          type, MSG_FLAG_BROADCAST, data, data_size);
            count++;
        }
        cur = cur->next;
    }

    write_line("[MSG]          broadcast from %u to %u programs\n",
          sender_program_index_tag, count);
    return count;
}


// ============================================================================
// RECEIVE
// ============================================================================

message *_peek_message(program_card *prog)
{
    if (!prog || !prog->inbox || _message_queue_empty(prog->inbox))
    {
        return NULL;
    }
    return prog->inbox->slots[prog->inbox->head];
}

message *_receive_message(program_card *prog)
{
    if (!prog || !prog->inbox)
    {
        return NULL;
    }
    message *msg = _dequeue_message(prog->inbox);
    if (msg)
    {
        msg->delivered = 1;
        p_manager.total_messages_delivered++;
        write_line("[MSG]          prog %u received msg %u\n",
              prog->program_index_tag, msg->msg_index_tag);
    }
    return msg;
}

message *receive_msg_blocking(program_card *prog)
{
    if (!prog)
    {
        return NULL;
    }
    while (_message_queue_empty(prog->inbox))
    {
        if (prog->main_event)
        {
            prog->main_event->wait_msg_index_tag = 0;
            _block_event(prog->main_event);
        }
    }
    return _receive_message(prog);
}

message *receive_response(program_card *prog, aisle original_msg_index_tag)
{
    if (!prog)
    {
        return NULL;
    }
    while (1)
    {
        msg_queue *q = prog->inbox;
        if (!q)
        {
            return NULL;
        }

        for (aisle i = 0; i < q->count; i++)
        {
            aisle    idx = (q->head + i) % q->capacity;
            message *msg = q->slots[idx];

            if (msg && msg->reply_to_msg_index_tag == original_msg_index_tag)
            {
                for (aisle j = i; j < q->count - 1; j++)
                {
                    aisle c      = (q->head + j)     % q->capacity;
                    aisle n      = (q->head + j + 1) % q->capacity;
                    q->slots[c]  = q->slots[n];
                }
                q->tail  = (q->tail - 1 + q->capacity) % q->capacity;
                q->count--;
                msg->delivered = 1;
                p_manager.total_messages_delivered++;
                return msg;
            }
        }

        if (prog->main_event)
        {
            prog->main_event->wait_msg_index_tag = original_msg_index_tag;
            _block_event(prog->main_event);
        }
    }
}


// ============================================================================
// CARD
// ============================================================================

bay _open_card_program(program_card *prog, file_card *card)
{
    if (!prog || !card)
    {
        return 0;
    }
    prog->card_count++;
    _add_program_ref_card(card, prog->program_index_tag);
    return 1;
}

bay _close_card_program(program_card *prog, file_card *card)
{
    if (!prog || !card)
    {
        return 0;
    }
    if (prog->card_count > 0)
    {
        prog->card_count--;
    }
    _remove_program_ref_card(card, prog->program_index_tag);
    return 1;
}

file_card *_get_card_program(program_card *prog, const char *path)
{
    if (!prog)
    {
        return NULL;
    }
    return _open_card(path, card_flag_read);
}

void _redirect_stdin_program(program_card *prog, file_card *card)
{
    if (prog)
    {
        prog->stdin_card = card;
    }
}

void _redirect_stdout_program(program_card *prog, file_card *card)
{
    if (prog)
    {
        prog->stdout_card = card;
    }
}

void _redirect_stderr_program(program_card *prog, file_card *card)
{
    if (prog)
    {
        prog->stderr_card = card;
    }
}


// ============================================================================
// CONTEXT SWITCHING
// ============================================================================

void _save_context_event(event *evt)
{
    (void)evt;
}

void _restore_context_event(event *evt)
{
    if (!evt)
    {
        return;
    }
    _set_current_program(evt->program_ptr);
}

void _switch_context_event(event *from, event *to)
{
    /* all context switching goes through _run_schedule_manager which uses
     * _save_event_state + _restore_event_state directly.
     * _switch_context_event is not currently called anywhere. */
    (void)from;
    (void)to;
}