/* This program will print out exercise lines to the display, and
   then check for correct typing.
*/

/* Ported to the Atari-ST by Randy Hosler on May 16, 1992.
   Ported from unix sources, original author unknown.
   Compiled with HSC 1.33 and MiNT libs.
   cc typist.c libm1.a libm.a
   Looks for *.typ files in d:\sozobon\lib\typist\ or current dir.
                     ( see find_lesson() )
*/

#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <memory.h>
#include <ctype.h>
#include <time.h>
#include <osbind.h>

typedef unsigned char BoolType;
#if !defined(FALSE)
#define FALSE 0
#define TRUE 1
#endif

#if !defined(NULL)
#define NULL 0
#endif

#define CLRSCREEN  Cconws("\033H\033J")
#define BEEP  Cconout('\007')

#define ASCII_ESC 27
#define ASCII_BS  8
#define ASCII_RUBOUT 127

#define STR_SIZE 256
typedef char StrType[STR_SIZE + 1];

#define MAX_LINES 1500
char *cached_lines[MAX_LINES];

int linesInLesson;
int exerciseStart;
int newStart;

FILE *lesson_file;
StrType current_line;

mvcur(r,c)
int r,c;
{
	Cconout('\033');
	Cconout('Y');
	Cconout(r+040);
	Cconout(c+040);
}

wait_user() /* Get any response from user before proceeding */
{
mvcur(23, 0);
Cconws("Press any key to continue...");
Cnecin();
}

void get_end_line() /* Read end of line, should be smarter... */
{
Cnecin();
}

int cleanup() /* cleanup routine, esp. SIGINT */
{
Cconws("\r\n");
exit(0);
}

char *git_lesson() /* Ask user for desired lesson */
{
static StrType response;

mvcur(0,0);
Cconws("  Several lessons are available:\r\n");
Cconws("        Quick QWERTY course (q1 - q5)\r\n");
Cconws("        Long QWERTY course  (r1 - r14)\r\n");
Cconws("        QWERTY touch typing (t1 - t16)\r\n");
Cconws("        Yet Another QWERTY  (v1 - v20)\r\n");
Cconws("        QWERTY Review       (u1 - u13)\r\n");
Cconws("        Dvorak touch typing (d1 - d14)\r\n");
Cconws("        Typing drills       (m1 - m11)\r\n");
Cconws("        Speed drills        (s1 - s4)\r\n");
Cconws("        Calculator keypad   (n1 - n3)\r\n");
mvcur(11,0);
Cconws(" Press return to exit or else type the desired lesson name: ");
gets(response);
if (strlen(response) == 0)
  cleanup();
return response;
}

BoolType get_lesson_line() /* Read line of input from user, in canonical mode */
{
if (feof(lesson_file))
  return FALSE;
if (fgets(current_line, STR_SIZE, lesson_file) == NULL)
  current_line[0] = '\0';
else {
  if (current_line[strlen(current_line) - 1] == '\n')
    current_line[strlen(current_line) - 1] = '\0';
  }
return TRUE;
}

BoolType find_lesson(name) /*locate lesson leave file open so next line begins*/
char *name;
{
StrType fullName;
StrType target;
StrType response;
BoolType done = FALSE;

sprintf(fullName, "d:\\sozobon\\lib\\typist\\%c.typ", name[0]);
sprintf(target, "*%c%s*", toupper(name[0]), &name[1]);
lesson_file = fopen(fullName, "r");
if (lesson_file == NULL) {
  sprintf(fullName, "%c.typ", name[0]);  /* try current dir */
  lesson_file = fopen(fullName, "r");
  if (lesson_file == NULL) {
    sprintf(response, "Can't open file %s\n", fullName);
    Cconws("\r\n");
    Cconws(response);
    wait_user();
    return FALSE;
    }
  }
while (!done) {
  if (!get_lesson_line())
    break;
  if (strncmp(current_line, target, 5) == 0)
    done = TRUE;
  }
if (!done) {
  sprintf(response, "Lesson %s is not available.\n", &name[1]);
  Cconws("\r\n");
  Cconws(response);
  wait_user();
  return FALSE;
  }
linesInLesson = 0;
done = FALSE;
while (!done) {
  if (!get_lesson_line()) {
    done = TRUE;
    break;
    }
  if (current_line[0] == '*') {
    done = TRUE;
    break;
    }
  if (linesInLesson == MAX_LINES) {
    }
  if (cached_lines[linesInLesson] != NULL) {
    free(cached_lines[linesInLesson]);
    cached_lines[linesInLesson] = NULL;
    }
  cached_lines[linesInLesson] = (char *)malloc(strlen(current_line) + 1);
  if (cached_lines[linesInLesson] == NULL) {
    fprintf(stderr, "malloc() failure\n");
    exit(1);
    }
  strcpy(cached_lines[linesInLesson], current_line);
  linesInLesson ++;
  }
return TRUE;
}

void get_exercise(startLine, kind, newStart) /*get length and kind of exercise*/
int startLine;
char *kind;
int *newStart;
{
char *thisLine;
int lineLength;

*newStart = startLine;
*kind = 'I'; /* default... */
/* Determine end of exercise */
while (TRUE) {
  if (*newStart >= linesInLesson)
    break; /* done */

  thisLine = cached_lines[*newStart];
  lineLength = strlen(thisLine);
  if (thisLine[lineLength - 2] == '\\') { /* expected terminator */
    *kind = thisLine[lineLength - 1];
    thisLine[lineLength - 2] = '\0'; /* remove */
    (*newStart) ++;
    break;
    }
  if (thisLine[lineLength - 1] == '\\') /* compatibility with BASIC */
    thisLine[lineLength - 1] = '\0'; /* also remove */
  (*newStart) ++;
  }
}

void displaySpeed(totalChars, elapsed, errs) /* show how fast you were */
int totalChars;
long elapsed;
int errs;
{
  elapsed = elapsed / 200;
  {
  double testTime = (double)elapsed / (double)60.0; /* time in minutes */
  double words = (double)totalChars / 5.0;
  double speed = words / testTime;
  double adjustedSpeed;
  StrType message;

  words -= errs;
  adjustedSpeed = words / testTime;

  mvcur(19, 19);
  sprintf(message, "raw speed      = %f wpm", speed);
  Cconws(message);
  mvcur(20, 19);
  sprintf(message, "adjusted speed = %f wpm", adjustedSpeed);
  Cconws(message);
  mvcur(21, 19);
  sprintf(message, "            with %f%s errors",
    (double)100.0 * errs / totalChars, "%");
  Cconws(message);
  wait_user();
  }
}

void do_drill() /* \D, a drill */
{
int tries;
int errors;
int totalChars;
int i, j;
char *thisLine;
int thisLength;
char ch;
long startTime, endTime;

tries = 0;
do { /* a try */
  totalChars = 0;
  errors = 0;
  tries ++;

  /* display drill pattern */
  for (i = exerciseStart; i < newStart; i ++) {
    mvcur(3 + (i - exerciseStart) * 2, 0);
    Cconws(cached_lines[i]);
    totalChars += strlen(cached_lines[i]) + 1; /* +1 for NULL */
    }

  /* run the drill */
  for (i = exerciseStart; i < newStart; i ++) {
    thisLine = cached_lines[i];
    thisLength = strlen(thisLine);

    mvcur(4 + (i - exerciseStart) * 2, 0);
    for (j = 0; j < thisLength; j ++) {
      ch = Cnecin();
      if (i == exerciseStart && j == 0)
        startTime = clock(); /* timer doesn't run till first stroke */
      if (ch == ASCII_ESC) {
        CLRSCREEN;
        goto ALL_DONE;
        }
      if (ch != thisLine[j]) {
        Cconout('X');
        BEEP;
        errors ++;
        }
      else
        Cconout(ch);
      }
    get_end_line();
    }
  endTime = clock();

  displaySpeed(totalChars, endTime - startTime, errors);
  CLRSCREEN;
  if (errors > 0 && tries < 3) {
    mvcur(0, 0);
    Cconws("Try again...");
    }
  tries ++;
  } while (errors > 0 && tries < 4);
ALL_DONE:;
}

void do_para()
/* P, practice paragraph */
{
int i, j;
char *thisLine;
int thisLength;
char ch;
int totalChars = 0;
int errors = 0;
long startTime, endTime;

/* print out practice text */
for (i = exerciseStart; i < newStart; i ++) {
  mvcur(3 + (i - exerciseStart), 0);
  Cconws(cached_lines[i]);
  totalChars += strlen(cached_lines[i]);
  }

mvcur(3, 0);
for (i = exerciseStart; i < newStart; i ++) {
  mvcur(3 + (i - exerciseStart), 0);
  thisLine = cached_lines[i];
  thisLength = strlen(thisLine);
  for (j = 0; j < thisLength; j ++) {
    ch = Cnecin();
    if (i == exerciseStart && j == 0)
      startTime = clock(); /* timer doesn't run till first stroke */
    if (ch == ASCII_ESC) {
      CLRSCREEN;
      goto ALL_DONE;
      }
    /* Allow backspace */
    if (j != 0 && (ch == ASCII_BS || ch == ASCII_RUBOUT)) {
      j --;
      mvcur(3 + (i - exerciseStart), j);
      Cconout(thisLine[j]);
      mvcur(3 + (i - exerciseStart), j);
      j --;
      continue; /* but notice that errors accumulate */
      }
    if (ch != thisLine[j]) {
      mvcur(3 + (i - exerciseStart), j);
      Cconout(thisLine[j]);
      errors ++;
      }
    else
      mvcur(3 + (i - exerciseStart), j + 1);
    }
    get_end_line();
  }
  endTime = clock();

  displaySpeed(totalChars, endTime - startTime, errors);
ALL_DONE:;
}

void do_type()
/* T, type out a lesson */
{
int i;

CLRSCREEN;
for (i = exerciseStart; i < newStart; i ++) {
  mvcur(1 + (i - exerciseStart), 0);
  Cconws(cached_lines[i]);
  }
wait_user();
CLRSCREEN;
}

void do_instruction() 
/* I, instruction line */
{
CLRSCREEN;
mvcur(0, 0);
Cconws(cached_lines[exerciseStart]);
}

void do_clear() 
/* B, clear screen */
{
CLRSCREEN;
mvcur(1, 29);
Cconws(cached_lines[exerciseStart]);
wait_user();
}

void give_lesson() 
/* dance through lesson file */
{
char kind;

exerciseStart = 0;
while (TRUE) {
  get_exercise(exerciseStart, &kind, &newStart);
  switch (kind) {
    case 'B':
      do_clear();
      break;

    case 'P':
      do_para();
      break;

    case 'T':
      do_type();
      break;

    case 'D':
      do_drill();
      break;

    case 'I':
      do_instruction();
      break;

    default:
      fprintf(stderr, "Unknown exercise near line %d of this lesson\n",
          newStart - 1);
      exit(1);
      break;
    }
  exerciseStart = newStart;
  if (exerciseStart >= linesInLesson)
    break;
  }
}

int main(argc, argv)
int argc;
char *argv[];
{
char *lessonName;
int i;

CLRSCREEN;

for (i = 0; i < MAX_LINES; i ++)
  cached_lines[i] = NULL;

/* Start and format screen.  Open and read exercise file.
   Inquire for lesson to run */
while (TRUE) {
  CLRSCREEN;
  lessonName = git_lesson();
  if (!find_lesson(lessonName))
    continue;
  give_lesson();
  }
}

