/* Demonstration program for RTKernel Version 3.0.

   Program RTDemo consists of 8 tasks. It serves to demonstrate how several
   tasks can run in parallel within one program.

   Three tasks have their own respective output window on the screen :

   Clock       This task displays the current system time once per second
	       in the lower right corner of the screen. Two different times
	       are displayed: time-of-day in hours, minutes, and seconds as
	       supplied by DOS, and the number of timer ticks elapsed since
	       program start. This is supplied by RTKernel and is used for
	       all tasking operations with time limit. Furthermore, the task
	       "CPU load" displays the current CPU load in percent.

   Dialog      This task performs the user dialog.
	       The following functions may be requested:

		  HELP - displays a list of available commands.

		  DIR - displays a file directory on the screen.
		  For example, "DIR C:\DOS\*.EXE" would display a list of
		  DOS programs in the DOS directory. To create a text file
		  containing the directory information, RTDemo executes
		  COMMAND.COM in parallel to the other tasks. Please note
		  that RTKernel can only start another process if enough
		  free memory is available. In some versions of DOS, FORMAT
		  only releases unused memory after several seconds. During
		  this time, DIR cannot be performed (DOS error 8: Out of
		  memory).

		  TASKS - displays a list of all tasks. This command shows
		  the usage of RTKListTasks.

		  INTS - displays statistics of the hardware interrupts.
		  For each interrupt, the interrupt request (IRQ), the number
		  of interrupts that have occurred, the unused interrupt stack
		  space, and the number of recursive interrupts is displayed.

		  TICK - sets the timer interrupt rate to the number of
		  milliseconds given, e.g., "TICK 20" will set the duration
		  of a timer tick to 20 milliseconds. Caution should be
		  taken not to specify too low values, in which case the
		  computer might crash because of interrupt overload.

		  TYPE - types the file specified to the screen, e.g.,
		  "TYPE RTDEMO.C".

		  EXIT - ends the program.

	       Please note that commands DIR start an additional DOS process.

   Main Task and Clock perform DOS calls. Thanks to RTKernel's DOS-protection
   mechanism, there are no reentrance problems even though task switches are
   allowed at all times. Even while a task performs a DOS call, another task
   may be activated, e.g., through an interrupt.                        */

#include <dir.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ctype.h>

#include <rtkernel.h>  /* task management */
#include <timer.h>     /* time measurement and control of timer interrupt rate */
#include <RTKeybrd.h>  /* interrupt handler for the keyboard */
#include <rttextio.h>  /* screen window management */
#include <cpumoni.h>   /* CPU load monitor */
#include <rtcom.h>     /* interrupt-driven serial communications */
#include <spooler.h>   /* background printing */

#define MAIN_PRIORITY      3            /* tasks' priorities */
#define CPU_MONI_PRIO  MAIN_PRIORITY + 1
#define CLOCK_PRIO     MAIN_PRIORITY + 1

#define TIME_SLICE      0.1         /* miscellaneous times in seconds */
#define CLOCK_CYCLE     1.0
#define CPU_MONI_CYCLE  0.4

#define DEFAULT_STACK  4096         /* Some tasks may need less,
				       but we play it safe.         */

#define ROW1  3                     /* screen window positions      */
#define ROW2 19
#define ROW3 22
#define ROW4 23

#define COL1  3
#define COL2 27
#define COL3 32
#define COL4 77

Window      *MainWindow;           /* main windows                  */

TaskHandle  CPUHandle,	     /* These task handles are used   */
	    ClockHandle;     /* to address the various tasks  */

/*-----------------------------------*/
void CPULoad(void)
/* This task demonstrates implementation of a cyclic task. It runs in
   a fixed time frame, which in this case does not need to be an exact
   multiple of the timer interrupt interval. */
{
   Window *W;
   float   NextActivation;

   RTKProtect8087();  /* coprocessor is used by this task */
   W = OpenWindow(COL1, ROW3 ,COL2 ,ROW4 , 40);
   SetAttribute(W, 0x74);
   Frame(W, " CPU LOAD ");
   WPutC(W, '\f');
   NextActivation = RTKGetTime() * SecPerTick + CPU_MONI_CYCLE;
   while (True)
   {
      RTKDelayUntil(Ticks(NextActivation));  /* release CPU time for other tasks */
      Wprintf(W, "\rCPU Load : %5.1f \%", PercentCPUNeeded());
      NextActivation = NextActivation + CPU_MONI_CYCLE;
   }
}

/*-----------------------------------*/
void Clock(void)  /* task to display the time-of-day */
{
   Window *W;
   time_t dummy;

   RTKProtect8087();  /* coprocessor is used by Ticks() */
   W = OpenWindow(COL3, ROW3, COL4, ROW4, 70);
   SetAttribute(W, 0x74);
   Frame(W, " Clock ");
   WPutC(W, '\f');
   while (True)
   {
      time(&dummy);
      GotoXY(W, 0, 0);
      Wprintf(W, "DOS time-of-day: %s\rTicks (RTKernel) : %li",
                 asctime(localtime(&dummy)),
                 RTKGetTime());
      RTKDelay(Ticks(CLOCK_CYCLE));
   }
}

/*-------------------------------*/
void NewTimerTick(char *Argument)
{
   float       msecs;
   char        Answer[2];

   RTKStackCheck();
   if (sscanf(Argument, "%f", &msecs) != 1)
   {
      WPutS(MainWindow, "Invalid argument\r\n");
      return;
   }
   if (msecs < 1.0)
   {
      WPutS(MainWindow,
         "Very small values may overload your computer.\r\n"
         "If your computer isn't fast enough, it may crash.");
      do {
	 Wprintf(MainWindow, "\r\nUse value of %3.1f anyhow ? (Y/N): ", msecs);
	 WGetS(MainWindow, Answer, sizeof(Answer));
	 Answer[0] = toupper(Answer[0]);
      } while ((Answer[0] != 'Y') && (Answer[0] != 'N'));
   }
   else
      Answer[0] = 'Y';
   if (Answer[0] == 'Y')
   {
      RTKTerminateTask(&CPUHandle);
      RTKTimeSlice(0);
      SetTimerInterval(msecs / 1000.0);
      RTKTimeSlice(Ticks(TIME_SLICE));
      CPUHandle = RTKCreateTask(CPULoad,  CPU_MONI_PRIO,   DEFAULT_STACK, "CPU Lastanzeige");
   }
   else
      WPutS(MainWindow, "Timer interrupt rate unchanged.\r\n");
}

/*-------------------------------*/
void ListFile(char *FileSpec)
{
   FILE       *file;
   char       s[256];

   RTKStackCheck();
   WPutS(MainWindow, "\r\n");
   if (*FileSpec == 0)
   {
      WPutS(MainWindow, "Missing parameter\r\n");
      return;
   }
   if ((file = fopen(FileSpec, "rt")) == NULL)
      Wprintf(MainWindow, "Error: %s\r\n", strerror(errno));
   else
   {
      while (fgets(s, 256, file) != NULL)
      {
	 WPutS(MainWindow, s);
	 WPutC(MainWindow, '\r');
      }
      fclose(file);
   }
}

/*-------------------------------*/
void ListDir(char *FileSpec)
{
   char command[128];
   int Code;

   RTKStackCheck();
   Wprintf(MainWindow, "Loading %s...\r\n", getenv("COMSPEC"));
   sprintf(command, "/C DIR %s > DIR.TXT", FileSpec);
   if ((Code = RTKExec(getenv("COMSPEC"), command)) != 0)
      Wprintf(MainWindow, "Return code: %i\r\nerrno: %i %s\r\n", Code, errno, strerror(errno));
   else
   {
      ListFile("DIR.TXT");
      remove("DIR.TXT");
   }
}

/*-------------------------------*/
void Help(void)
{
   WPutS(MainWindow,
   "\fRTKernel Multitasking Demonstration.  (C) 1992 On Time Informatik GmbH\r\n"
   "The following commands are available:\r\n"
   "   DIR    [Argument]  - displays a directory of the files specified\r\n"
   "   TYPE   Filename    - types the specified file to the screen\r\n"
   "   TASKS              - displays a list of all tasks on the screen\r\n"
   "   INTS               - displays a statistic of hardware interrupts\r\n"
   "   TICK   millisecs   - sets the timer interrupt interval to millisecs\r\n"
   "   HELP               - displays this text\r\n"
   "   EXIT               - ends the demo program\r\n");
}

/*----------------------------------*/
void Split(char *Head, char *Tail)
{
   int  HeadLen;
   char *S;

   strcpy(Head, Tail);
   HeadLen = strcspn(Head, " ");
   Head[HeadLen] = 0;
   S = Tail + HeadLen;
   while ((*S == ' ') && (*S != 0))
      S++;
   strcpy(Tail, S);
}

/*----------------------------------*/
void UserDialog(void)
{
   char    UserInput[30];
   char    LastInput[30];
   char    Command[30];
   char    Dummy[1];
   char    ListBuffer[1000];

   RTKStackCheck();
   Help();
   LastInput[0] = 0;
   do {
      WPutS(MainWindow, "\r\nCommand: ");
      WGetS(MainWindow, UserInput, sizeof(UserInput));
      if (UserInput[0] == 0) {
	 strcpy(UserInput, LastInput);	// use last command
	 Wprintf(MainWindow, ": %s\r\n", UserInput);
      } else
	 strcpy(LastInput, UserInput);
      strupr(UserInput);
      Split(Command, UserInput);
      if (strcmp(Command, "DIR") == 0)
	 ListDir(UserInput);
      else if (strcmp(Command, "TYPE") == 0)
	 ListFile(UserInput);
      else if (strcmp(Command, "TICK") == 0)
	 NewTimerTick(UserInput);
      else if (strcmp(Command, "TASKS") == 0)
      {
         RTKListTasks(ListBuffer, sizeof(ListBuffer));
	 WPutS(MainWindow, ListBuffer);
      }
      else if (strcmp(Command, "INTS") == 0)
      {
         RTKIRQInfo(ListBuffer, sizeof(ListBuffer));
	 WPutS(MainWindow, ListBuffer);
      }
      else if (strcmp(Command, "HELP") == 0)
	 Help();
      else if (strcmp(Command, "EXIT") == 0)
	 return;
      else
	 WPutS(MainWindow, "Unknown command. HELP displays a list of available commands.\r\n");
   } while (True);    /* exit via EXIT */
}

/*----------------------------------*/
int main(int argc)
{
   /* Most of RTKernel's modules must be initialized before they  */
   /* can be used.                                                */

   RTKernelInit(MAIN_PRIORITY);
   TimerInit();
   CPUMoniInit();
   RTKeybrdInit();
   RTTextIOInit();

   /* This program requires a rather large stack. */

   if (RTKGetTaskStack(RTKCurrentTaskHandle()) < 2500)
   {
      printf("The stack size of this program should be set to at least\n"
	     "4 kilobytes. Please re-compile the program.\n");
      exit(1);
   }

   /* clear screen */
   MainWindow = OpenWindow(0, 0, 79, 24, 0);
   WPutC(MainWindow, '\f');
   CloseWindow(MainWindow);

   /* define windows for the Main Task */
   MainWindow = OpenWindow(COL1, ROW1, COL4 ,ROW2, 300);
   SetAttribute(MainWindow, 0x1F);
   Frame(MainWindow, " Dialog ");
   WPutC(MainWindow, '\f');               /* Clear Screen of RTTextIO */

   /* These two tasks can already start running: */
   CPUHandle    = RTKCreateTask(CPULoad,  CPU_MONI_PRIO,   DEFAULT_STACK,     "CPU Lastanzeige");
   ClockHandle  = RTKCreateTask(Clock,    CLOCK_PRIO,      DEFAULT_STACK,     "Uhranzeige");

   UserDialog();

   RTKTerminateTask(&CPUHandle);   /* this is not really necessary */
   RTKTerminateTask(&ClockHandle); /* RTKernel automatically terminates all tasks */


   CloseWindow(MainWindow);
   MainWindow = OpenWindow(0, 0, 79, 24, 0);
   WPutC(MainWindow, '\f');        /* clear screen */

   CursorXY(0,0);
   return 0;
}

