// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// taskSwch.h -- Cooperative task switching library for DJGPP
//
// Cooperative (non-preemptive) multitasking for DOS protected mode (DJGPP/DPMI).
//
// Why cooperative instead of preemptive:
// 1. DOS is single-threaded  -- there is no kernel scheduler to preempt us.
// 2. DPMI provides no thread or timer-based preemption primitives.
// 3. The DVX GUI event model is inherently single-threaded: one compositor,
//    one input queue, one window stack. Preemption would require locking
//    around every GUI call, adding latency and complexity for no benefit.
// 4. Cooperative switching lets each task yield at safe points, avoiding
//    the need for synchronization primitives entirely.
//
// Credit-based cooperative multitasking with task pausing.
// Each task receives (priority + 1) credits per scheduling round.
// Tasks run round-robin, consuming one credit per turn.  When all
// credits are spent, every ready task is refilled.  Higher-priority
// tasks run proportionally more often but never starve lower ones.
//
// This is a weighted fair-share scheduler: a priority-10 task gets 11
// turns per round while a priority-0 task gets 1, but the low-priority
// task is guaranteed to run eventually  -- no starvation is possible.

#ifndef TASKSWCH_H
#define TASKSWCH_H

#include <stdint.h>
#include <stdbool.h>

// Error codes
#define TS_OK        0
#define TS_ERR_INIT  (-1)
#define TS_ERR_PARAM (-2)
#define TS_ERR_FULL  (-3)
#define TS_ERR_NOMEM (-4)
#define TS_ERR_STATE (-5)

// 32KB default stack  -- generous for callback-style app tasks that spend
// most of their time in the shell's GUI code. Apps needing deeper recursion
// or large stack allocations can request more via AppDescriptorT.stackSize.
#define TS_DEFAULT_STACK_SIZE 32768
#define TS_NAME_MAX           32

// Priority levels  -- the credit count is (priority + 1), so LOW gets 1 turn
// per round, NORMAL gets 6, HIGH gets 11. The shell's main task runs at
// HIGH to keep UI responsive; app tasks default to NORMAL.
#define TS_PRIORITY_LOW    0
#define TS_PRIORITY_NORMAL 5
#define TS_PRIORITY_HIGH   10

// Task states  -- only Ready tasks participate in scheduling. The Running
// state is cosmetic (marks the currently executing task). Paused tasks
// are skipped until explicitly resumed. Terminated slots are recycled.
typedef enum {
    TaskStateReady      = 0,
    TaskStateRunning    = 1,
    TaskStatePaused     = 2,
    TaskStateTerminated = 3
} TaskStateE;

// Task entry function signature  -- the void* arg lets the caller pass
// arbitrary context (e.g., a ShellAppT* for the DVX shell's app wrapper)
typedef void (*TaskEntryT)(void *arg);

// Per-task context-value hooks (tsSetContextHooks). The save hook is
// called for the outgoing task on every switch and its result stored
// per-task; the restore hook reinstates the incoming task's stored
// value. Lets the host make one global behave as task-local state.
typedef int32_t (*TsCtxSaveFnT)(void);
typedef void (*TsCtxRestoreFnT)(int32_t value);

// Initialize the task system. The calling context becomes task 0 (main).
// Task 0 is special: it cannot be killed or paused, and the crash recovery
// path (tsRecoverToMain) always returns control here. No separate stack is
// allocated for task 0  -- it uses the process stack directly.
int32_t tsInit(void);

// Shut down the task system and free all resources.
void tsShutdown(void);

// Create a new task. Pass 0 for stackSize to use TS_DEFAULT_STACK_SIZE.
// Returns the task ID (>= 0) on success, or a negative error code.
// Terminated task slots are recycled to avoid unbounded array growth.
int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSize, int32_t priority);

// Yield CPU to the next eligible ready task (credit-based round-robin).
// This is the sole mechanism for task switching  -- every app must call
// this (or a GUI function that calls it) periodically, or it will
// monopolize the CPU.
void tsYield(void);

// Pause a task. Cannot pause the main task (id 0).
// If a task pauses itself, an implicit yield occurs.
int32_t tsPause(uint32_t taskId);

// Resume a paused task. Credits are refilled so the task gets a fair
// share of CPU time immediately rather than waiting for the next round.
int32_t tsResume(uint32_t taskId);

// Register the per-task context-value hooks (see TsCtxSaveFnT). Pass
// NULLs to unregister. The DVX shell uses this to keep currentAppId
// correct per task so allocations and windows are attributed properly.
void tsSetContextHooks(TsCtxSaveFnT save, TsCtxRestoreFnT restore);

// Set a task's scheduling priority. Also refills credits so the change
// takes effect immediately rather than at the next credit refill epoch.
int32_t tsSetPriority(uint32_t taskId, int32_t priority);

// Get a task's priority, or TS_ERR_PARAM on invalid id.
int32_t tsGetPriority(uint32_t taskId);

// Get a task's current state.
TaskStateE tsGetState(uint32_t taskId);

// Get the currently running task's ID.
uint32_t tsCurrentId(void);

// Get a task's name, or NULL on invalid id.
const char *tsGetName(uint32_t taskId);

// Terminate the calling task. Must not be called from the main task.
// The stack is freed immediately and the slot is marked for reuse.
// Internally performs a context switch to the next ready task  -- this
// function never returns to the caller.
void tsExit(void);

// Forcibly terminate another task. Cannot kill main task (id 0) or self.
// Safe to call only because we're cooperative: the target task is
// guaranteed to be suspended at a yield point, so its stack can be freed
// without worry about it executing concurrently. In a preemptive system
// this would be undefined behavior.
int32_t tsKill(uint32_t taskId);

// Crash recovery: force scheduler back to main task (id 0).
// Call after longjmp from a signal handler that fired in a non-main task.
// The crashed task is NOT cleaned up  -- call tsKill() afterward.
// This exists because longjmp unwinds the crashed task's stack but the
// scheduler's currentIdx still points to it. We must fix the bookkeeping
// before doing anything else.
void tsRecoverToMain(void);

// Get the number of non-terminated tasks.
uint32_t tsActiveCount(void);

#endif // TASKSWCH_H
