#include <common.h>

/**
** Initial process; it starts the other top-level user processes.
**
** Prints a message at startup, '+' after each user process is spawned,
** and '!' before transitioning to wait() mode to the SIO, and
** startup and transition messages to the console. It also reports
** each child process it collects via wait() to the console along
** with that child's exit status.
*/

/*
** "Spawn table" process entry. Similar to the one in shell.c, but
** this version has a field to hold the PID of the spawned process
** to allow 'init' to respawn it when it terminates.
*/
typedef struct proc_s {
	uint_t index; // process table index
	uint_t pid; // its PID (when spawned)
	uint8_t e_prio; // process priority
	char select[3]; // identifying character, NUL, extra
	char *args[N_ARGS]; // argument vector strings
} proc_t;

/*
** Create a spawn table entry for a process with a string literal
** as its argument buffer.	We rely on the fact that the C standard
** ensures our array of pointers will be filled out with NULLs
*/
#define PROCENT(e, p, s, ...) \
	{                         \
		e, 0, p, s,           \
		{                     \
			__VA_ARGS__, NULL \
		}                     \
	}

// sentinel value for the end of the table - must be updated
// if you have more than 90,210 user programs in the table
#define TBLEND 90210

/*
** This table contains one entry for each process that should be
** started by 'init'. Typically, this includes the 'idle' process
** and a 'shell' process.
*/
static proc_t spawn_table[] = {

	// the idle process; it runs at Deferred priority,
	// so it will only be dispatched when there is
	// nothing else available to be dispatched
	PROCENT(Idle, PRIO_DEFERRED, "!", "idle", "."),

	// the user shell
	PROCENT(Shell, PRIO_STD, "@", "shell"),

	// PROCENT( 0, 0, 0, 0 )
	{ TBLEND }
};

// character to be printed by init when it spawns a process
static char ch = '+';

/**
** process - spawn all user processes listed in the supplied table
**
** @param proc  pointer to the spawn table entry to be used
*/

static void process(proc_t *proc)
{
	char buf[128];

	// kick off the process
	int32_t p = fork();
	if (p < 0) {
		// error!
		sprint(buf, "INIT: fork for #%d failed\n", (uint32_t)(proc->index));
		cwrites(buf);

	} else if (p == 0) {
		// change child's priority
		(void)setprio(proc->e_prio);

		// now, send it on its way
		exec(proc->index, proc->args);

		// uh-oh - should never get here!
		sprint(buf, "INIT: exec(0x%08x) failed\n", (uint32_t)(proc->index));
		cwrites(buf);

	} else {
		// parent just reports that another one was started
		swritech(ch);

		proc->pid = p;
	}
}

/*
** The initial user process. Should be invoked with zero or one
** argument; if provided, the first argument should be the ASCII
** character 'init' will print to indicate the spawning of a process.
*/
USERMAIN(main)
{
	char buf[128];

	// check to see if we got a non-standard "spawn" character
	if (argc > 1) {
		// maybe - check it to be sure it's printable
		uint_t i = argv[1][0];
		if (i > ' ' && i < 0x7f) {
			ch = argv[1][0];
		}
	}

	cwrites("Init started\n");

	// home up, clear on a TVI 925
	swritech('\x1a');

	// wait a bit
	DELAY(SHORT);

	// a bit of Dante to set the mood :-)
	swrites("\n\nSpem relinquunt qui huc intrasti!\n\n\r");

	/*
	** Start all the user processes
	*/

	cwrites("INIT: starting user processes\n");

	proc_t *next;
	for (next = spawn_table; next->index != TBLEND; ++next) {
		process(next);
	}

	swrites(" !!!\r\n\n");

	/*
	** At this point, we go into an infinite loop waiting
	** for our children (direct, or inherited) to exit.
	*/

	cwrites("INIT: transitioning to wait() mode\n");

	for (;;) {
		int32_t status;
		int whom = waitpid(0, &status);

		// PIDs must be positive numbers!
		if (whom <= 0) {
			sprint(buf, "INIT: waitpid() returned %d???\n", whom);
			cwrites(buf);
		} else {
			// got one; report it
			sprint(buf, "INIT: pid %d exit(%d)\n", whom, status);
			cwrites(buf);

			// figure out if this is one of ours
			for (next = spawn_table; next->index != TBLEND; ++next) {
				if (next->pid == whom) {
					// one of ours - reset the PID field
					// (in case the spawn attempt fails)
					next->pid = 0;
					// and restart it
					process(next);
					break;
				}
			}
		}
	}

	/*
	** SHOULD NEVER REACH HERE
	*/

	cwrites("*** INIT IS EXITING???\n");
	exit(1);

	return (1); // shut the compiler up
}