/*
 * ExecutiveAPI example
 *
 * This file is public domain.
 *
 * Author: Petri Nordlund <petrin@megabaud.fi>
 *
 * $Id: Example.c 1.1 1996/10/01 23:06:09 petrin Exp petrin $
 *
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <proto/exec.h>
#include <proto/dos.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "ExecutiveAPI.h"

#ifdef __SASC
#include <dos.h>
#define GETA4() geta4()
#else /* GCC */
#define GETA4() ix_geta4()
#endif

#define STACK_SIZE 10000L

void MyExit(void);
BOOL SendMessage(struct ExecutiveMessage *msg);
void WatchMe(void);
void WatchMe_entry(void);
void WatchRelative(void);
void WatchRelative_entry(void);

struct ExecutiveMessage *msg = NULL;
struct Task *parent_task;
BYTE parent_signal;
WORD active_tasks = 0;


/**
 ** This is an example on how to use ExecutiveAPI. All ExecutiveAPI features
 ** are demonstrated.
 **
 **/

int
main(int argc, char **argv)
{
	LONG oldnice;

	atexit(MyExit);

/**
 ** Allocate ExecutiveMessage structure and initialize it.
 **
 **/

	if(!(msg=AllocVec(sizeof(struct ExecutiveMessage), MEMF_PUBLIC|MEMF_CLEAR)))
	{
		puts("Can't allocate ExecutiveMessage structure.");
		exit(RETURN_FAIL);
	}

	if(!(msg->message.mn_ReplyPort=CreateMsgPort()))
	{
		puts("Can't create message port.");
		exit(RETURN_FAIL);
	}

	msg->message.mn_Node.ln_Type = NT_MESSAGE;
	msg->message.mn_Length       = sizeof(struct ExecutiveMessage);

	/** Make sure this is always initialized to zero! **/
	msg->ident = 0;

/**
 ** Add this program as Executive client. Executive can't quit
 ** before all clients have been removed.
 **
 **/

	msg->command = EXAPI_CMD_ADD_CLIENT;

	if(!SendMessage(msg))
	{
		puts("Can't add new client.");
		exit(RETURN_FAIL);
	}

/**
 ** Ask Executive to return the real (not the scheduling) priority of this
 ** task. If this task is currently scheduled and you read the priority
 ** directly from task-structure, it will be somewhere in the dynamic
 ** range. There's no GetTaskPri() routine in exec.library which could
 ** be patched to return the real priority.
 **
 ** In a case a new task is launched with its parent task's priority, which
 ** is in the dynamic range, Executive notices this, and sets the real
 ** priority of the new task to its parent task's real priority. But if
 ** wish to create a childtask whose priority will be the priority of the 
 ** parent task plus one, and you read the priority from task-structure, it
 ** might be something like -58. Add one to this value, and you'll get -57.
 ** Executive can't correct this, so in this case use the
 ** EXAPI_CMD_GET_PRIORITY command to obtain task's real priority.
 **
 **/

	msg->command = EXAPI_CMD_GET_PRIORITY;
	msg->task    = FindTask(NULL);

	if(!SendMessage(msg))
	{
		puts("Can't obtain this task's real priority.");
		exit(RETURN_FAIL);
	}

	printf("Scheduling priority: %d --- Real priority: %d\n",
			FindTask(NULL)->tc_Node.ln_Pri,
			msg->value1);

/**
 ** Set this task's nice-value to -10. Store old value and restore it later.
 ** You should restore the nice-value, if the program can be executed in a
 ** shell, as the shell process will then get the nice-value.
 **
 **/

	msg->command = EXAPI_CMD_SET_NICE;
	msg->task    = FindTask(NULL);
	msg->value1  = -10;						/* new nice-value */

	if(!SendMessage(msg))
	{
		puts("Can't set this task's nice-value.");
		exit(RETURN_FAIL);
	}

	oldnice = msg->value1;

	printf("Old nice-value: %d\n",oldnice);

/**
 ** Get current nice-value.
 **
 **/

	msg->command = EXAPI_CMD_GET_NICE;
	msg->task    = FindTask(NULL);

	if(!SendMessage(msg))
	{
		puts("Can't obtain this task's nice-value.");
		exit(RETURN_FAIL);
	}

	printf("Current nice-value: %d\n",msg->value1);

/**
 ** Restore old nice-value.
 **
 **/

	msg->command = EXAPI_CMD_SET_NICE;
	msg->task    = FindTask(NULL);
	msg->value1  = oldnice;

	if(!SendMessage(msg))
	{
		puts("Can't set this task's nice-value.");
		exit(RETURN_FAIL);
	}

/**
 ** Two EXAPI_CMD_WATCH examples.
 **
 **/

	WatchMe();
	Delay(2*50);
	WatchRelative();

/**
 ** Client is removed in MyExit(). Don't forget to do it.
 **
 **/

	return(RETURN_OK);
}



void
MyExit(void)
{

/**
 ** Remove the client. This can't fail.
 **
 **/

	if(msg && (msg->message.mn_ReplyPort))
	{
		msg->command = EXAPI_CMD_REM_CLIENT;

		SendMessage(msg);
	}

/**
 ** Delete the message.
 **
 **/

	if(msg)
	{
		if(msg->message.mn_ReplyPort)
			DeleteMsgPort(msg->message.mn_ReplyPort);

		FreeVec(msg);
	}
}



/**
 ** Send message to Executive's public message port.
 ** Return TRUE if success, FALSE if error occurred.
 ** See msg->error for the specific error code.
 **
 **/
BOOL
SendMessage(struct ExecutiveMessage *message)
{
	struct MsgPort *port;

/**
 ** Find Executive public message port and send the message.
 ** Wait for reply.
 **
 **/

	Forbid();

	if(port=FindPort(EXECUTIVEAPI_PORTNAME))
	{
		PutMsg(port, (struct Message *) message);
		Permit();
		WaitPort(message->message.mn_ReplyPort);
		while(GetMsg(message->message.mn_ReplyPort))
			;
		if(!message->error)
			return(TRUE);
	}
	else
	{
		/** Executive is not running **/
		Permit();
	}

	return(FALSE);
}


/**
 ** Create a new task. The new task will send a message to Executive and ask
 ** that its priority is kept below all scheduled tasks. This is like having
 ** the following entry in Executive.prefs:
 **
 **   TASK  Example_childtask NOSCHEDULE  BELOW
 **
 ** Start the Top client and you'll se what happens to the childtask's
 ** priority.
 **
 **/

void
WatchMe(void)
{
	if((parent_signal=AllocSignal(-1)) != -1)
	{
		parent_task = FindTask(NULL);

		if(CreateNewProcTags(	NP_Entry,		(ULONG) WatchMe_entry,
								NP_Name,		(ULONG) "Example_childtask",
								NP_Priority,	0,
								NP_StackSize,	STACK_SIZE,
								TAG_DONE))
		{
			puts("Childtask started. Wait...");
			Wait(1L << parent_signal);
			puts("Childtask finished.");
		}
		else
		{
			FreeSignal(parent_signal);
			puts("Can't create new process.");
			exit(RETURN_FAIL);
		}

		FreeSignal(parent_signal);
	}
	else
	{
		puts("Can't allocate signal.");
		exit(RETURN_FAIL);
	}
}

void
WatchMe_entry(void)
{
	struct ExecutiveMessage *message;
	int t,i;

	/** This routine needs DosBase, parent_task and parent_signal **/
	GETA4();

	if(message=AllocVec(sizeof(struct ExecutiveMessage), MEMF_PUBLIC|MEMF_CLEAR))
	{
		if(message->message.mn_ReplyPort=CreateMsgPort())
		{
			message->message.mn_Node.ln_Type = NT_MESSAGE;
			message->message.mn_Length       = sizeof(struct ExecutiveMessage);

			/** Make sure this is always initialized to zero! **/
			message->ident = 0;

			message->command = EXAPI_CMD_WATCH;
			message->task    = FindTask(NULL);
			message->value1  = EXAPI_WHICH_TASK;
			message->value2  = EXAPI_TYPE_NOSCHEDULE;
			message->value3  = EXAPI_PRI_BELOW;

			/** Ignore error **/
			SendMessage(message);
		}
	}

	/** Use some CPU time **/
	for(t=0;t<20;t++)
	{
		for(i=0;i<1000000;i++)
			;
		Delay(30);
	}

	if(message)
	{
		if(message->message.mn_ReplyPort)
			DeleteMsgPort(message->message.mn_ReplyPort);

		FreeVec(message);
	}

	/** Forbid so we can finish completely, before the parent cleans up. **/
	Forbid();

	Signal(parent_task, 1L << parent_signal);
}


/**
 ** Create three tasks with priorities -1, 0 and +1. Assume that this task's
 ** priority is 0. Configure Executive to schedule all childtasks of this
 ** task so that their priority is relative to this task's priority. This is
 ** like having the following entry in Executive.prefs:
 **
 **   CHILDTASKS  Example  RELATIVE
 **
 ** Start the Top client and you'll se what happens to childtask priorities.
 ** Try using the PGRP option with Ps: "ps PGRP=<this task's pid>". You'll
 ** see that the childtasks don't seem to use any CPU time. CPU usage is
 ** transferred to the parent task, because its CPU usage is used when
 ** scheduling priority is calculated.
 **
 ** IMPORTANT! You'll have to issue the WATCH-command BEFORE starting any
 ** childtasks!
 **
 **/

void
WatchRelative(void)
{
	msg->command = EXAPI_CMD_WATCH;
	msg->task    = FindTask(NULL);
	msg->value1  = EXAPI_WHICH_CHILDTASKS;
	msg->value2  = EXAPI_TYPE_RELATIVE;

	if(!SendMessage(msg))
	{
		puts("EXAPI_CMD_WATCH command failed.");
		if(msg->error == EXAPI_ERROR_ALREADY_WATCHED)
		{
			puts("You can only issue an EXAPI_CMD_WATCH command once for each task.");
			puts("Executive remembers the task name, and if that task is started again,");
			puts("it knows what to do with it.");
			puts("If EXAPI_CMD_WATCH fails, check for EXAPI_ERROR_ALREADY_WATCHED");
			puts("in msg->error. You can safely ignore this error. It's also possible");
			puts("that user has put the task to Executive.prefs.");
		}
		else
			exit(RETURN_FAIL);
	}

	if((parent_signal=AllocSignal(-1)) != -1)
	{
		struct Process *proc1, *proc2, *proc3;

		parent_task = FindTask(NULL);

		/** Forbid so all tasks start to run at the same time **/
		Forbid();

		proc1 = CreateNewProcTags(NP_Entry,	(ULONG) WatchRelative_entry,
					  NP_Name,	(ULONG) "Example_childtask1",
					  NP_Priority,	-1,
					  NP_StackSize,	STACK_SIZE,
					  TAG_DONE);
		proc2 = CreateNewProcTags(NP_Entry,	(ULONG) WatchRelative_entry,
					  NP_Name,	(ULONG) "Example_childtask2",
					  NP_Priority,	0,
					  NP_StackSize,	STACK_SIZE,
					  TAG_DONE);
		proc3 = CreateNewProcTags(NP_Entry,	(ULONG) WatchRelative_entry,
					  NP_Name,	(ULONG) "Example_childtask3",
					  NP_Priority,	1,
					  NP_StackSize,	STACK_SIZE,
					  TAG_DONE);
		Permit();

		if(proc1)	active_tasks++;
		if(proc2)	active_tasks++;
		if(proc3)	active_tasks++;

		if(active_tasks)
		{
			puts("Childtasks started. Wait...");
			do {
				Wait(1L << parent_signal);
			} while(active_tasks);
			puts("Childtasks finished.");
		}
		else
		{
			FreeSignal(parent_signal);
			puts("Can't create new process.");
			exit(RETURN_FAIL);
		}

		FreeSignal(parent_signal);
	}
	else
	{
		puts("Can't allocate signal.");
		exit(RETURN_FAIL);
	}
}

void
WatchRelative_entry(void)
{
	int t,i;

	/** We need DosBase, parent_task, parent_signal and active_tasks **/
	GETA4();

	/** Use some CPU time **/
	for(t=0;t<20;t++)
	{
		for(i=0;i<350000;i++)
			;
		Delay(40);
	}

	/** Forbid so we can finish completely, before the parent cleans up. **/
	Forbid();

	active_tasks--;

	Signal(parent_task, 1L << parent_signal);
}
