/* /////////////////////////////////////////////////////////////////////

    Demo Program for Real-Time Executive
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (c) 1990, 1991, 1992 Michael Podanoffsky.
	All Rights Reserved World Wide.

       Technical questions:  Michael Podanoffsky
			     508/ 454-1620.


    Modified for Borland C++ 3.1
    By: William Isherwood  October 1999
	writech@zebra.net
	334-928-0250

///////////////////////////////////////////////////////////////////// */

#include <dos.h>
#include <bios.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <ctype.h>
#include <malloc.h>
#include <string.h>
#include <conio.h>
#include "rtx.h"

/* /////////////////////////////////////////////////////////////////////
   demo program
///////////////////////////////////////////////////////////////////// */
//#define DEBUG    TRUE

#define DoForEver  		for(;;)
#define NextTask                scheduler()
#define STARTUP_TASKS             4

extern unsigned _stklen = 0x2000;  //added for Borland C

extern int Task_Counts[ STARTUP_TASKS ] = { 0, 0, 0, 0 };




/* /////////////////////////////////////////////////////////////////////
    Define a Screen Area as a Rectangle.
///////////////////////////////////////////////////////////////////// */
typedef struct Rect {
      int              row;
      int              col;
      int              width;
      int              height;
      char far *       filename;
      } Rect;

#define byte  unsigned char

/* /////////////////////////////////////////////////////////////////////
    An example of Queueable Records (See Transaction Processing)
///////////////////////////////////////////////////////////////////// */
typedef struct QueueRecord {
   struct QueueRecord far *next;
   char far *text;
   } QueueRecord;

QueueRecord queue_1 = { NULL, "Name 1" };
QueueRecord queue_2 = { NULL, "Name 2" };

extern int pascal keyboardEventFct(event event_id, void far * arg );

/*
global variables
*/
char task4_char = 64;


/* /////////////////////////////////////////////////////////////////////
   screen stuff
///////////////////////////////////////////////////////////////////// */

#define MAX_COL                  80
#define MAX_ROW                  25
#define DEF_COLOR                0x1E  //0x12

#define MONOCHROME               1
#define COLOR_MODE               0

#define POSITION(r,c)            ( 2 * (((r)) * MAX_COL + ((c))))

static byte far * far screen = far_address(0xB800, 0x0000);
static byte far attribute = DEF_COLOR;


#define VERTICAL_BAR             179               /* screen characters */
#define HORIZONTAL_BAR           196
#define CROSS_HAIRS              197

#define EMPTY_ELEVATOR            32
#define FILLED_ELEVATOR          219
#define PARTLY_FILLED_ELEVATOR   220

/* /////////////////////////////////////////////////////////////////////
   keyboard codes
///////////////////////////////////////////////////////////////////// */

#define LEFT                     0x4b00            /* keyboard codes */
#define RIGHT                    0x4d00
#define UP                       0x4800
#define DOWN                     0x5000
#define PAGEUP                   0x4900
#define PAGEDOWN                 0x5100
#define HOME                     0x4700
#define END                      0x4f00
#define CONTROL_C                0x0003

/* /////////////////////////////////////////////////////////////////////
    Keyboard Interface
///////////////////////////////////////////////////////////////////// */
static int pascal getChar(void )
{
   union REGS in, out;

   in.x.ax = 0;
   int86( 0x16, &in, &out );                 /* wait on keyboard */

   if ( out.h.al != 0 )
      return (out.x.ax & 255);
   else
      return out.x.ax;

}

/* /////////////////////////////////////////////////////////////////////
    Position Cursor
///////////////////////////////////////////////////////////////////// */
static void pascal position_cursor(int row, int col )
{
   union REGS in, out;

   if ( col >= MAX_COL ) {
      row += col / MAX_COL;
      col = col % MAX_COL;
      }

   in.h.ah = 2;
   in.h.bh = 0;
   in.h.dh = (byte )row;
   in.h.dl = (byte )col;
   int86( 0x10, &in, &out );

}

/* /////////////////////////////////////////////////////////////////////
    Get Screen Type
///////////////////////////////////////////////////////////////////// */
static int pascal screentype(void )
{
   union REGS in, out;

   int86( 0x11, &in, &out );

   if ( (( out.x.ax & 0x0030 ) / 16 ) == 2 )
      return COLOR_MODE;
   else
      return MONOCHROME;
}


/*
hault system using out int 21_00
*/
void pascal sys_hault(void)
{
   union REGS r;

   disableSched();
   if (! disablePortIRQ( 1 )) printf("\nShutdown ERROR COM1");
   if (! disablePortIRQ( 2 )) printf("\nShutdown ERROR COM2");
   if (! disablePortIRQ( 3 )) printf("\nShutdown ERROR COM3");
   if (! disablePortIRQ( 4 )) printf("\nShutdown ERROR COM4");
   r.h.ah=0x4C;
   int86( 0x21, &r, &r);    // this restores IRQ hooks (see REGS.ASM)
}                           // then does the required function.


/* /////////////////////////////////////////////////////////////////////
    Display String
///////////////////////////////////////////////////////////////////// */
static void pascal displaystring(int row, int col, char far *string )
{
   unsigned byte far *ptr = screen;
   register byte at = attribute;

   FP_OFF( ptr ) = 2 * ( MAX_COL * row + col );

   while ( *string ) {
      *ptr++ = *string++;
      *ptr++ = at;
      col++;
      }

}

/* /////////////////////////////////////////////////////////////////////
    full line fill
///////////////////////////////////////////////////////////////////// */
static void pascal displayfullline_fill( int r, byte ch )
{
   register int c = MAX_COL;
   unsigned int far *ptr = (unsigned int far *)screen;
   int fill_c = (256 * attribute) | ch;

   FP_OFF( ptr ) = 2 * MAX_COL * r;

   while ( c-- )
      *ptr++ = fill_c;


}

/* /////////////////////////////////////////////////////////////////////
    Clear Screen/ Position Cursor at Home
///////////////////////////////////////////////////////////////////// */
static void pascal clearscreen(void )
{
   register int row;

   for ( row = 0; row <= MAX_ROW; row++ )
      displayfullline_fill( row, ' ' );
      
   position_cursor( 0, 0 );
}

/* /////////////////////////////////////////////////////////////////////
    Convert Time Of Day counter to hour/minute/second format.

///////////////////////////////////////////////////////////////////// */
static void pascal getTimeOfDay(timeofday far * now )
{
   unsigned long time = *SystemClock;

   unsigned long hour = time / (TICKS_PER_HOUR);
   unsigned long minute = ( time % TICKS_PER_HOUR ) / TICKS_PER_MINUTE;
   unsigned long second = ( (time % TICKS_PER_MINUTE)*16 ) / TICKS_PER_SECOND;
			  // modified to fix clock display
   now->hour = (int )hour;
   now->minute = (int )minute;
   now->second = (int )second;

}

/* /////////////////////////////////////////////////////////////////////
    One Second Interrupt Routine
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    This routine is set in main() to be attached to a watch-dog timer.
    It will countdown 18 clock ticks ( essentially one second ) and
    update the time of day.

    Even though the IBM-PC clock is obe-second for every 18.2 ticks,
    this clock stays accurate with the PC because it only reports the
    time.


///////////////////////////////////////////////////////////////////// */
int pascal one_second_interval( void far * argument )
{
   char buffer[80];
   timeofday now;
   static char far time_buffer[] = "__:__:__ am";

   getTimeOfDay( &now );

   if ( now.hour < 12 )
      time_buffer [ 9 ] = 'a';

   else {
      time_buffer [ 9 ] = 'p';
      if ( now.hour > 12 ) now.hour -=12;
      }

   time_buffer [ 0 ] = '0' | now.hour / 10;
   time_buffer [ 1 ] = '0' | now.hour % 10;

   time_buffer [ 3 ] = '0' | now.minute / 10;
   time_buffer [ 4 ] = '0' | now.minute % 10;

   time_buffer [ 6 ] = '0' | now.second / 10;
   time_buffer [ 7 ] = '0' | now.second % 10;

   displaystring( MAX_ROW - 1, MAX_COL - strlen( time_buffer ) - 2, time_buffer );
}


/* ////////////////////////////////////////////////////////////////////////
   Display a vertical elevator bar on screen

//////////////////////////////////////////////////////////////////////// */
static void pascal elevator(int row, int col, int value )
{
   int max;
   char ch = FILLED_ELEVATOR;
   char far * ptr = screen;

   FP_OFF( ptr ) = POSITION(row, col);

   for ( max = 1; max < 9; max += 2 ) {

      if ( max == value)
	 ch =  PARTLY_FILLED_ELEVATOR;

      else if ( max > value )
	 ch = EMPTY_ELEVATOR;

      *ptr = ch;
      ptr -= MAX_COL * 2;
      }

}

///////////////////////////////////////////////////////////////////////////
void pascal showcounts(void )
{
   unsigned long n, c;
   unsigned char far *ptr = (unsigned char far *)screen;

   for ( n = 0; n < STARTUP_TASKS; ++n ) {

      FP_OFF( ptr ) = 2 * (((MAX_ROW/2)+2+n ) * MAX_COL + (MAX_COL/2)+15);

      if ( currentTask == &Tasks[ n ] )  ++Task_Counts[ n ];

      c = Task_Counts[ n ];

      while ( c ) {

	 *ptr = '0' | (c % 10 );
	 ptr -= 2;
	 c /= 10;

	 }
      }

      // debugging aid
      // clear a display zone each update
//      FP_OFF( ptr ) = 2 * (((MAX_ROW/2)+1 ) * MAX_COL + (MAX_COL/2)+5);
//      for (n=0; n<5; ++n){
//	*ptr=' ';
//	ptr -=2;
//	}
//      FP_OFF( ptr ) = 2 * (((MAX_ROW/2)+1 ) * MAX_COL + (MAX_COL/2)+5);
//      c=Tcnt;
//      while ( c ) {
//	 *ptr = '0' | (c % 10 );
//	 ptr -= 2;
//	 c /= 10;
//	 }

      //now display task 4 val
      FP_OFF( ptr ) = 2 * (((MAX_ROW/2)+2 ) * MAX_COL + (MAX_COL/2)+5);
	*ptr = task4_char;

      comPutChar();

}


/* ////////////////////////////////////////////////////////////////////////
   Type Out a file (task 2 and 3 share this code)

//////////////////////////////////////////////////////////////////////// */
int pascal typeout_file(void far * argument )
{
   int row = 0;                                      /* relative coord */
   int chars_read;
   Rect far * rect = argument;
   char buffer[ 80 ];
   int done = FALSE;

   FILE far * file = fopen(rect->filename, "ra");

   /* // if file exists ... /////////////////////////////////////////// */
   while ( file ) {

      while (!done)  {

       char far * ptr;

       disableSched();
	 ptr = fgets(buffer, sizeof(buffer), file );
       enableSched();

	 if ( ptr == NULL ) break;

	 ptr = strchr( buffer, '\n' );
	 if ( *ptr == '\n' ) *ptr = '\0';          /* eliminate \n */

	 buffer[ rect->width ] = '\0';             /* quick cropping */

	 if ( row >= rect->height ) {

	    union REGS in, out;

	    in.h.ah = 6;
	    in.h.al = 1;
	    in.h.ch = rect->row;
	    in.h.cl = rect->col;
	    in.h.dh = rect->row + rect->height;
	    in.h.dl = rect->col + rect->width - 1;
	    in.h.bh = attribute;
	    in.h.bl = 0;
	    int86( 0x10, &in, &out );               /* scroll rect up */

	    row = rect->height;
	    }

	 displaystring(rect->row + row, rect->col, buffer );
	 ++row;
	 }

	if (strcmp(rect->filename,"regs.asm")==0)
	   {
	   fclose(file);
	   break;
	   }
	else
	   rewind(file);
      }
}

/* ////////////////////////////////////////////////////////////////////////
   Task 1 code.  Just displays priorities as an elevator.
//////////////////////////////////////////////////////////////////////// */
int pascal monitor_tasks(void far * argument )
{
   int input;
   int on_task = 1;
   int n;
   int done = 0;

   /* // elevators displayed in columns: 5, 8, 11, 14 ////////////////// */
   while (!done) {

      for ( n = 0; n < STARTUP_TASKS; ++n )
	 elevator ( 5, 3*n + 5, (Tasks[ n ].priority+31)/32 );

      position_cursor(7, 3*on_task + 5 );

      switch ( input = getChar()) {

	 case 'i':
	 case 'I':

	    //input = getComBuffer(1);
	    //input = getComBuffer(3);
	    init_SerialPort( 1, b9600, pN, d8, s1 );
	    //enablePortIRQ( 1 );

	    init_SerialPort( 3, b9600, pN, d8, s1 );
	    //enablePortIRQ( 3 );
	    break;

	 case 'q':
	 case 'Q':
	    queueMessage(findTask("Spooler"), "regs.asm");
	    break;

	 case '2':
	 case '3':
	 case '4':
	    on_task = input - '1';
	    break;

	 case 's':
	 case 'S':
	    ++Tasks[ on_task ].time_slice_interval;
	    break;

	 case 'f':
	 case 'F':
	    if ( Tasks[ on_task ].time_slice_interval )
	       --Tasks[ on_task ].time_slice_interval;
	    break;

	 case 'n':
	 case 'N':
	    Tasks[ on_task ].time_slice_interval = TIME_SLICE;
	    break;

	 case LEFT:
	    if ( on_task > 1 ) --on_task;
	    break;

	 case RIGHT:
	    if ( on_task < 3 ) ++on_task;
	    break;

	 case UP:
	 case PAGEUP:
	    if ( Tasks[ on_task ].priority < 255 )
	       setPriority( &Tasks[ on_task ], Tasks[ on_task ].priority + 32 );

	    if ( Tasks[ on_task ].priority > 255 )
	       setPriority( &Tasks[ on_task ], 256 );
	    break;

	 case DOWN:
	 case PAGEDOWN:
	    if ( Tasks[ on_task ].priority > 0 )
	       setPriority( &Tasks[ on_task ], Tasks[ on_task ].priority - 32 );

	    if ( Tasks[ on_task ].priority < 0 )
	       setPriority( &Tasks[ on_task ], 0 );
	    break;

	 case CONTROL_C:
	    done = TRUE;
	    break;

	 } /* end switch */
      } /* while !done */
   sys_hault();
}

/* ////////////////////////////////////////////////////////////////////////
   Task 4 just quits.  (Demo of a task quiting !)
//////////////////////////////////////////////////////////////////////// */
int pascal task_4(void far * argument ) {
   //unsigned long c = 0;
   //while (c < 5000000) {
   //++c;
//   DoForEver {
   ++task4_char;
//   NextTask;
//   }
 }

/*******************************************************************
   Q_Event Task
*******************************************************************/
int pascal Q_Event(void far *arg)
{
   Rect rect2;

   Q_Item far *queueItem;

DoForEver
   {
   queueItem = currentTask->queue_event;
   rect2.row = 0;
   rect2.col = 1 + MAX_COL/ 2;
   rect2.width = MAX_COL - rect2.col;
   rect2.height = MAX_ROW/2 - 1 - rect2.row;
   rect2.filename = queueItem->text;

   typeout_file( &rect2 );
   // putch(0x07);
   currentTask->startup_event = Q_EVENT;  //block
   NextTask; //end this early
   }
}

/*******************************************************************
comACK  -  simple test com IRS
*******************************************************************/
void pascal comAck( void )
{
//    ++task4_char;
}

void pascal comPutChar( void )
{
  int inChar;
    //inChar = get_8250(1);
    //put_8250(1,(getComBuffer(1) & 0xff));
    do {
    if ((inChar = getComBuffer(1)) > 0) {
	put_8250(1, (inChar & 0xff));
	task4_char='A';
	}
    if ((inChar = getComBuffer(3)) > 0) {
	put_8250(3, (inChar & 0xff));
	task4_char='B';
	}
    if ((inChar = getComBuffer(4)) > 0) {
	put_8250(4, (inChar & 0xff));
	task4_char='C';
	}
    } while (inChar > 0);
}

/*
void pascal comPutChar3( void )
{
  int inChar;
    //inChar = get_8250(1);
    //put_8250(1,(getComBuffer(1) & 0xff));
    //if ((inChar = getComBuffer(1)) > 0) put_8250(1, (inChar & 0xff));
    //if ((inChar = getComBuffer(2)) > 0) put_8250(2, (inChar & 0xff));
    task4_char='D';
    if ((inChar = getComBuffer(3)) > 0) {
       task4_char='E';
       put_8250(3, (inChar & 0xff));
       }
}
*/


/* ////////////////////////////////////////////////////////////////////////
   main()
   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   a) initialize screen.
   b) initialize tasking system.
   c) create tasks.
   d) let scheduler start after initialization.


//////////////////////////////////////////////////////////////////////// */
main()
{
   int n, timer_id;
   union REGS in, out;
   Rect rect2, rect3;
   char far * ptr;

   /* // init com ports ////////// */
   if (! init_SerialPort( 1, b9600, pN, d8, s1 )) putchar(7);
   else  n = enablePortIRQ( 1 ); 

   if (! init_SerialPort( 3, b9600, pN, d8, s1 ))
      {
      putchar(7);
      putchar(7);
      }
   else  n = enablePortIRQ( 3 );

   if (! init_SerialPort( 4, b9600, pN, d8, s1 ))
      {
      putchar(7);
      putchar(7);
      putchar(7);
      }
   else  n = enablePortIRQ( 4 );


   //if (! crlf_8250(1)) exit(0);
   //if (! put_8250(1,65)) exit(0);
   //if (! disablePortIRQ( 1 )) exit(0);
   //if (! (n = getComBuffer(2))) exit(0);


   /* // get screen type /////////////////////////////////////////////// */
   FP_SEG( screen ) = (screentype() == MONOCHROME) ? 0xB000 : 0xB800;
   ptr = screen;


   /* // clear screen ////////////////////////////////////////////////// */

   in.h.ah = 1;
   in.h.bh = 0;
   in.x.cx = 0x000D;
   int86( 0x10, &in, &out );               /* full size cursor */

   clearscreen();


   /* // debug info //////////////////////////////////////////////////// */

   #ifdef DEBUG
   printf( "%d %d %d %d %d %d %d",
	       LAST_EVENT,
	       TOTAL_USER_EVENT_ALLOC,
	       FIRST_SYS_EVENT,

	       LAST_SYSTEM_EVENT,
	       TOTAL_SYS_EVENT_ALLOC,
	       MAX_EVENT_ALLOC,
	       TOTAL_EVENTS  );
	       exit(0);
   #endif

   /* // draw screen for demo ////////////////////////////////////////// */

   FP_OFF( ptr ) = POSITION( 0, MAX_COL/ 2);    /* middle of screen */

   for ( n = 0; n < MAX_ROW; ++n ) {            /* draw vertical bar down middle */

      *ptr = VERTICAL_BAR;
      ptr += MAX_COL * 2;
      }

   FP_OFF( ptr ) = POSITION( MAX_ROW/2, 0);     /* middle of screen */

   for ( n = 0; n < MAX_COL; ++n ) {            /* draw horizontal line across */

      *ptr = HORIZONTAL_BAR;
      ptr += 2;
      }

   FP_OFF( ptr ) = POSITION( MAX_ROW/2, MAX_COL/2);
   *ptr = CROSS_HAIRS;

   displaystring(6, 5, "Priorities" );
   displaystring(7, 5, "1  2  3  4" );
   displaystring(1, 0, "255 _" );
   displaystring(5, 0, "  0 _" );


   /* /// init real time executive ///////////////////////////////////// */

   initTaskSystem();

   defineEventFct( KEYBOARD_WAIT, NULL /*keyboardEventFct*/, NULL );



   rect2.row = 0;
   rect2.col = 1 + MAX_COL/ 2;
   rect2.width = MAX_COL - rect2.col;
   rect2.height = MAX_ROW/2 - 1 - rect2.row;
   rect2.filename = "regs.asm";

   rect3.row = 1 + MAX_ROW/ 2;
   rect3.col = 0;
   rect3.width = MAX_COL/ 2 - 1;
   rect3.height = MAX_ROW - rect3.row;
   rect3.filename = "rtx.c";

   defineTask(monitor_tasks,   NO_ARGUMENT,   8192, 1000, "name1" );
   //defineTask(typeout_file,    &rect2,        8192,  96, "name2"  );
   defineTask(Q_Event,         NO_ARGUMENT,   8192,  96, "Spooler");
   findTask("Spooler")->startup_event = Q_EVENT;

   defineTask(typeout_file,    &rect3,        8192,  96, "name3"  );
   defineTask(task_4,          NO_ARGUMENT,   8192,  96, "name4"  );



   /* /// how to set-up a timer //////////////////////////////////////// */
   timer_id = setTimer(PERIODIC, ONE_SECOND, one_second_interval, NULL );

   /* /// the real time executive starts with scheduler() ////////////// */
   scheduler();
}

