#include "exec/types.h"

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

#include "hp11/hp11.h"
#include "hp11/amiga/amiga.h"
#include "hp11/ins.h"
#include "hp11/io.h"
#include "hp11/kbd.h"
#include "hp11/codes.h"
#include "hp11/prog_codes.h"

#define MAXRUN 4 /* Length of time running is displayed */

#define FOREVER for (;;)

int comma;

static char *stpich(char *p, int c) /* insert character c at front of string p */
{
   movmem(p, p + 1, strlen(p) + 1);
   *p = c;

   return(p);
}

int GetKey() /* Read a key & wait for its release */
{
   int key;

   key = PollKey(TRUE);
   RelKey();

   return(key);
}

enum KeyTypes ReadKey(code) /* Read a complete key sequence, & return
 its type, intrsuction or action. */
register WORD *code;
{
   register struct Key *curtkey;
   register int key, offset;
   register BOOL noKey; /* if an invalid sequence is returned, don't read a new key,
      reuse the one which caused the error. This is set to false when that happens */
   register enum KeyTypes ret;

   noKey = TRUE; /* no key read */

   FOREVER {
      offset = 0; /* f or g not pressed */

      FOREVER { /* This loop reads a key from the main, f or g shifted keyboards.
	 Further refinements (eg sto) are done algorithmically, to save space */
	 if (noKey) key = PollKey(TRUE); /* obtain next key */
	 Dispf(FALSE); Dispg(FALSE);
	 noKey = TRUE;
	 if (key == 31) { /* f pressed, toggle its status */
	    offset = (offset == NUMKEYS) ? 0 : (Dispf(TRUE), NUMKEYS);
	    RelKey();
	 }
	 else if (key == 32) { /* g */
	    offset = (offset == NUMKEYS + NUMKEYS) ? 0 : (Dispg(TRUE), NUMKEYS + NUMKEYS);
	    RelKey();
	 }
	 else break;/* got a key, exit from loop */
      }
      if (User && key < 5) offset ^= NUMKEYS; /* Toggle f for first five keys. This
       doesn't affect g because the bit patterns are exclusive (42 & 84 = 0) */

      Dispf(FALSE); Dispg(FALSE);

      curtkey = mainKbd + offset + key; /* find address of (eventually shifted) key */

      switch (curtkey->Sort) {
	 case Action:
	    *code = curtkey->Act;
	    return(Action);
	 case Instruction:
	    *code = curtkey->Code;
	    return(Instruction);
	 case Prefix: /* Key is a prefix, execute corresponding routine */
	    RelKey();
	    ret = (*(curtkey->Suffix))(code);
	    if (ret != Invalid) return(ret); /* if successful */

	    key = *code; /* else, invalid keycode returnedin code field for reuse */
	    noKey = FALSE; /* a key is already available */
	    break;
	 case Invalid: /* An inavlid f or g sequence was entered, retry it with
	    the f or g prefix stripped. Therefore all obtainable main keyboard sequences
	    must exist, otherwise the program enters an infinite loop retrying constantly
	    the same nonexistent keycode */
	    key %= NUMKEYS;
	    noKey = FALSE;
	    break;
      }
   }
}

/* Return position n on the liquid cristal display in string t */
int scrpos(t, n)
char *t;
register int n;
{
   register char *s = t;
   register int pos;

   pos = 0;
   while (pos <= n && *s) { /* go on till end of string or beyond position n on display */
      if (*s != '.' && *s != ',') pos++; /* . & , take no space on the display */
      s++;
   }
   return((int)((s - t) - 1 - (pos - n))); /* pos - n  is there to take care of the overshoot. If
   n is beyond the end of the string, the position returned may well be wildly beyond the
   actual end of the string */
}

/* Return the length taken up on the screen by the string */
int scrlen(s)
register char *s;
{
   register int cnt = 0;

   while (*s) {
      if (*s != '.' && *s != ',') cnt++; /* . & , take no space on the display */
      s++;
   }

   return(cnt);
}

/* format string s in hp11 display format (without exponent) so that it takes
 n spaces in the display. s isn't modified */
static char *CvtStd(char *s, int n)
{
   static char buf[20];
   register char *p;
   register int i, nb;
   register int digit_separator = comma ? '.' : ','; /* separator according to current setting */

   strcpy(buf, s); /* copy string to safe work buffer */

   if ((p = strchr(buf, '.')) == NULL) { /* find position of . */
      p = buf + strlen(buf);
      if (!entering) *p = comma ? ',' : '.';
      *(p + 1) = '\0';
   }
   else if (comma) *p = ','; /* Replace . by , if necessary */

   while ((p -= 3) - buf > 1) /* Add , (or .) to string every 3 digits */
      stpich(p, digit_separator);

   nb = n - scrlen(buf);
   for (i = 1; i <= nb; i++) strcat(buf, " "); /* pad with spaces to required screen length */
   buf[scrpos(buf, n) + 1] = '\0'; /* cut at n characters */

   return(buf);
}

/* format string s in hp11 display format (with exponent) */
static char *CvtExpo(char *s, char *e)
{
   if (strlen(e) > 3) { /* deal with roundoff towards 1e100 when nb too big */
      e = " 99"; /* exponent is 99 */
      strncpy(s + 1, "9.999999999", strlen(s + 1)); /* mantissa is enough 9's */
   }

   return(strcat(CvtStd(s, 8), e));
}

/* convert x to scientific format with n digits. Returns it in a static buffer (from CvtStd) */
static char *Scient(double x, int n)
{
   char buf[20];
   register char *pe;

   sprintf(buf, "% .*E", n, x); /* Scientific format with n digits */
   pe = strchr(buf, 'E'); /* split string into mantissa & exponent */
   *pe++ = '\0';
   /* if (*pe == '+') *pe = ' '; A + is displayed as a space by the Display routine anyway */

   return(CvtExpo(buf, pe));
}

/* Convert x to fix n format */
static char *Fixed(double x, int n)
{
   char buf[80];

   sprintf(buf, "% .*f", n, x);

   return(CvtStd(buf, 11));
}

/* Eng n format */
static char *Engin(double x, int n)
{
   char expbuf[10], buf[80];
   register char *pe;
   double mantissa;
   register int exponent, dif;

   sprintf(buf, "%.*E", n, x); /* print enough digits */
   *(pe = strchr(buf, 'E')) = '\0';
   mantissa = atof(buf); /* get mantissa & exponent */
   exponent = atoi(pe + 1);

   /* Round exponent down to a multiple of 3 */
   dif = exponent % 3;
   if (dif < 0) dif += 3;
   exponent -= dif; /* calculate new exponent & mantissa */
   mantissa *= pow(10.0, (double)dif);

   /* Convert them to string */
   sprintf(buf, "% .*f", (n - dif > 0) ? n - dif : 0, mantissa);
   sprintf(expbuf, "%c%02d", (exponent < 0) ? '-' : ' ', iabs(exponent)); /* pad exponent with 0's, hence %02d not %2d */

   return(CvtExpo(buf, expbuf));
}

/* Display current trig mode */
static void DispAngle(void)
{
   switch (Angles) {
      case grad:DispG(TRUE);
      case rad:DispRAD(TRUE);
      case deg:break;
   }
}

/* Display current x value in normal mode, running in run mode */
void Disp()
{
   static int runcnt = MAXRUN;
   static BOOL runon;

   if (running) { /* Flash running on and off every MAXRUN calls */
      if (fast) { /* Display Running only once in fast mode */
	 if (!runon) {
	    Display("  Running");
	    runon = TRUE; /* Running displayed */
	 }
      }
      else if (runcnt-- == 0) Display("");
      else if (runcnt <= -MAXRUN) {
	 runcnt = MAXRUN;
	 Display("  Running");
      }
   }
   else {
      runon = FALSE; /* Running not displayed */
      if (entering) /* Display number entry strings */
	 if (expo) Display(CvtExpo(strx, expx)); /* with exponent */
	 else Display(CvtStd(strx, 11));
      else
	 Display(NbStr(X));

      DispAngle();
      if (User) DispUSER(TRUE);
   }
}

char *NbStr(x)
double x;
{
   switch (Mode) { /* Display x according to display mode */
      case fix:if ((fabs(X) >= minfix / 2.0 || X == 0.0) && fabs(X) < 1E10) {
		  /* Number can be displayed in fix mode */
		  return(Fixed(X, Digits));
	       }
	       /* fall through for call to Scient */
      case sci:return(Scient(X, Digits));
      case eng:return(Engin(X, Digits));
   }
}

/* Display Error n, & wait for a key to be pressed */
void Error(n)
int n;
{
   register char *buf;

   entering = FALSE; /* end of digit entry */
   error = TRUE; /* an error has occured */
   buf = "  Error  ";
   buf[8] = n; buf[9] ='\0';

   if (!running) RelKey();
   Display(buf);
   GetKey();

}

/* Display current program line */
void DisplayLine()
{
   register int c1 = keycodes[Prog[PC]].c1, c2 = keycodes[Prog[PC]].c2,
		c3 = keycodes[Prog[PC]].c3;
   char _buf[20], _insbuf[20];
   register char *buf = _buf, *insbuf = _insbuf;
   register int point = comma ? ',' : '.'; /* separator according to current setting */

   sprintf(buf, " %03d-", PC); /* prepare program line */

   /* Prepare instruction buffer */
   if (PC == 0) insbuf[0] = '\0'; /* nothing at line 0 */
   else switch (keycodes[Prog[PC]].Type) { /* there are 6 methods for displaying a line */
      case ONECODE: sprintf(insbuf, "%6d", c1); break; /*     nn eg SIN or 9 */
      case TWOCODE: sprintf(insbuf, "%3d%3d", c1, c2); break; /*  nn nn eg g LOG */
      case TWOCODE_9: sprintf(insbuf, "%4d%2d", c1, c2); break; /*  nn n eg STO 5*/
      case TWOCODE_PT: sprintf(insbuf, "%4d %c%1d", c1, point, c2); break; /* nn .n eg RCL .6 */
      case THREECODE: sprintf(insbuf, "%2d,%2d,%2d", c1, c2, c3); break; /* nn,nn,nn eg f HYP SIN */
      case THREECODE_PT: sprintf(insbuf, "%2d,%2d, %c%1d", c1, c2, point, c3); break; /* nn,nn, .n eg STO + .0 */
   }

   Display(strcat(buf, insbuf));

   DispAngle();
   if (User) DispUSER(TRUE);
}

