#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <time.h>
#include <graph.h>

typedef unsigned char byte;
typedef unsigned int uint;

static int lpt_base_addresses[2] = {0x378, 0x278};
static int lpt_port = 0;

#define DATA 0
#define STATUS 1
#define CTRL 2

volatile int timer  = 0;
void (__interrupt * dos_old_timer)();
void __interrupt new_timer() { timer++; dos_old_timer(); }

int manual = 0;

/* Ram-to-screen */

#define R2SVALS 5

typedef struct {
   clock_t t;
   unsigned char v[R2SVALS];
} r2sent_t;

int ram2scr_base = 0;
int ram2scr_width = 16;
int ram2scr_height = 16;
r2sent_t *ram2scr_ents = NULL;

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

void lsleep()
{
   int t0 = timer;
   while (timer < t0 + 10);
//   clock_t t0 = clock();
//   while (clock() <= t0+CLOCKS_PER_SEC/10);

//   time_t t = time(NULL);
//   while (time(NULL) <= t+1);
if (manual)
   getch();

}

void msleep()
{
   static int whatever=0;
   int i;
   for (i=0 ; i<100 ; i++)
      whatever = whatever*2 + 1 + rand();
}

static void lpt_write(int reg, byte data)
{
   outp(lpt_base_addresses[lpt_port] + reg, data);
}

static byte lpt_read(int reg)
{
   return inp(lpt_base_addresses[lpt_port] + reg);
}


void set_direction(int is_output)
{
   if (is_output)
      lpt_write(CTRL, lpt_read(CTRL) & ~(1<<5));
   else
      lpt_write(CTRL, lpt_read(CTRL) | (1<<5));
}

void set_ctrls(int c1, int c14, int c16, int c17, int is_output)
{
   byte c = lpt_read(CTRL);
   if (c1)
      c &= ~1;
   else
      c |= 1;
   if (c14)
      c &= ~2;
   else
      c |= 2;
   if (!c16)
      c &= ~4;
   else
      c |= 4;
   if (c17)
      c &= ~8;
   else
      c |= 8; 
   if (is_output)
      c &= ~(1<<5);
   else
      c |= (1<<5);
   lpt_write(CTRL, c);
   msleep();   
}

void set_output(int val)
{
   lpt_write(DATA, val);
   msleep();
}

void write_to_address(int lo, int hi, int data)
{
   // Clear the whole thing
   set_output(0);
   set_ctrls(1, 1, 1, 1, 1);

   // Low order part of address
   set_output(lo);
   set_ctrls(1, 1, 0, 1, 1);

   // High order part of address
   set_output(hi);
   set_ctrls(1, 1, 0, 0, 1);

   // The two latches are now enabled and latched!
   // The 245 will already be in A->B mode (pin 14=1)

   // Put data onto bus
   set_output(data);

   // Strobe the WE
   set_ctrls(0, 1, 0, 0, 1);
   set_ctrls(1, 1, 0, 0, 1);

   // Disable latched addresses, which also terminates 245
   set_ctrls(1, 1, 1, 1, 1);
   set_output(0);
}

void debug_write_to_address(int lo, int hi, int data)
{
   // Clear the whole thing
   set_output(0);
   set_ctrls(1, 1, 1, 1, 1);
printf("clear\n"); getch();

   // Low order part of address
   set_output(lo);
   set_ctrls(1, 1, 0, 1, 1);
printf("addr low\n"); getch();

   // High order part of address
   set_output(hi);
   set_ctrls(1, 1, 0, 0, 1);
printf("addr hi\n"); getch();

   // The two latches are now enabled and latched!
   // The 245 will already be in A->B mode (pin 14=1)

   // Put data onto bus
   set_output(data);
printf("data on bus\n"); getch();
   // Strobe the WE
   set_ctrls(0, 1, 0, 0, 1);
printf("in strobe\n"); getch();
   set_ctrls(1, 1, 0, 0, 1);
printf("strobed\n"); getch();
   // Disable latched addresses, which also terminates 245
   set_ctrls(1, 1, 1, 1, 1);
   set_ctrls(1, 0, 1, 1, 1);
}

int read_from_address(int lo, int hi)
{
   int data;

   // Clear the whole thing
   set_output(0);
   set_ctrls(1, 1, 1, 1, 1);

   // Low order part of address
   set_output(lo);
   set_ctrls(1, 1, 0, 1, 1);

   // High order part of address
   set_output(hi);
   set_ctrls(1, 1, 0, 0, 1);

   // The two latches are now enabled and latched!

   // We turn the port to input mode and simultaneously
   // flip the 245 to B->A mode, which also does OE on the SRAM.
   set_ctrls(1, 0, 0, 0, 0);

   // Read the data
   data = lpt_read(DATA);

   // Now we can reset all pins again.
   set_ctrls(1, 0, 1, 1, 1);

   return data;
}

int debug_read_from_address(int lo, int hi)
{
   int data;

   // Clear the whole thing
   set_output(0);
   set_ctrls(1, 1, 1, 1, 1);
printf("read clear\n");getch();

   // Low order part of address
   set_output(lo);
   set_ctrls(1, 1, 0, 1, 1);
printf("read addr low\n");getch();

   // High order part of address
   set_output(hi);
   set_ctrls(1, 1, 0, 0, 1);
printf("read addr hi\n");getch();

   // The two latches are now enabled and latched!

   // We turn the port to input mode and simultaneously
   // flip the 245 to B->A mode, which also does OE on the SRAM.
   set_ctrls(1, 0, 0, 0, 0);
printf("read data available\n");getch();

   // Read the data
   data = lpt_read(DATA);

   // Now we can reset all pins again.
   set_ctrls(1, 0, 1, 1, 1);
printf("read returned\n");getch();

   return data;
}

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

#define CHIP_SIZE 32768  /* Size of both RAM and MC chips */

void test_address_modulus(uint size)
{
   uint i;
   byte d;
   int errors = 0;

   printf("Writing address moduli...\n");
   for (i=0 ; i<size ; i++) {
      if (i % 4000 == 0 || i+1 == size)
         printf("Address %04x (%ld%% done)...\n", i, 100*(long)(i+1)/size);
      write_to_address(i%256, i/256, i%256);
   }

   printf("Reading back address moduli...\n");
   for (i=0 ; i<size ; i++) {
      if (i % 4000 == 0 || i+1 == size)
         printf("Address %04x (%ld%% done)...\n", i, 100*(long)(i+1)/size);
      if (i%256 != (d = read_from_address(i%256, i/256))) {
         printf("VERIFICATION ERROR: Address %04x should read %02x, but is %02x!\n",
            i, i%256, d);
         if (++errors >= 20) {
            printf("More than %d errors. Aborting test!\n", errors);
            break;
         }
      }
   }

   if (errors)
      printf("Test FAILED.\n");
   else
      printf("Test SUCCESSFUL.\n");
}

void mc_test_16_modulus(uint size)
{
   uint i;
   signed int v;
   byte d;
   int errors = 0;

   v = -1;
   printf("Writing 16-bit values...\n");
   for (i=0 ; i<size ; i++) {
      if (i % 4000 == 0 || i+1 == size)
         printf("Address %04x (%ld%% done)...\n", i, 100*(long)(i+1)/size);
      if (i % 2 == 0) {
         // even address, increase v and write out high byte
         v++;
         write_to_address(i%256, i/256, (int)v/256);
      }
      else {
         write_to_address(i%256, i/256, (int)v%256);
      }
   }

   v = -1;
   printf("Reading back 16-bit values...\n");
   for (i=0 ; i<size ; i++) {
      if (i % 4000 == 0 || i+1 == size)
         printf("Address %04x (%ld%% done)...\n", i, 100*(long)(i+1)/size);
      d = read_from_address(i%256, i/256);
      if (i % 2 == 0) {
         v++;
         if (d != (int)(v/256)) {
            printf("VERIFICATION ERROR: Address %04x should read %02x, but is %02x!\n",
               i, (int)v/256, d);
            errors++;
         }
      }
      else {
         if (d != (int)(v%256)) {
            printf("VERIFICATION ERROR: Address %04x should read %02x, but is %02x!\n",
               i, (int)v%256, d);
            errors++;
         }
      }
           
      if (errors >= 20) {
         printf("More than %d errors. Aborting test!\n", errors);
         break;
      }
   }

   if (errors)
      printf("Test FAILED.\n");
   else
      printf("Test SUCCESSFUL.\n");
}

void test_alternating(uint size)
{
   uint i, pass;
   byte d, e;
   const byte b = (0x80 | 0x20 | 0x08 | 0x02);
   int errors = 0;

   for (pass=0 ; pass<=1 ; pass++) {
      printf("%s pass\n", pass ? "Odd" : "Even");
   
      printf("Writing alternating bits...\n");
      for (i=0 ; i<size ; i++) {
         if (i % 4000 == 0 || i+1 == size)
            printf("Address %04x (%ld%% done)...\n", i, 100*(long)(i+1)/size);
         if ( (i%2) ^ pass )
            e = b;
         else
            e = ~b;
         write_to_address(i%256, i/256, e);
      }

      printf("Reading back alternating bits...\n");
      for (i=0 ; i<size ; i++) {
         if (i % 4000 == 0 || i+1 == size)
            printf("Address %04x (%ld%% done)...\n", i, 100*(long)(i+1)/size);
         if ( (i%2) ^ pass )
            e = b;
         else
            e = ~b;
         if (e != (d = read_from_address(i%256, i/256))) {
            printf("VERIFICATION ERROR: Address %04x should read %02x, but is %02x!\n",
                 i, e, d);
            if (++errors >= 20) {
               printf("More than %d errors. Aborting test!\n", errors);
               break;
            }
         }
      }
   }

   if (errors)
      printf("Test FAILED.\n");
   else
      printf("Test SUCCESSFUL.\n");
}

void singles(int debug_mode)
{
   char line[100];
   char *p;
   uint addr, val;

   puts("");
   puts("You are now in individual byte mode.");
   puts("Write a hex address to retrieve that value,");
   puts("or a hex address follows by a '=' and a hex value to write.");
   puts("'q' quits this mode.");
   puts("");

   while (fgets(line, 100, stdin)) {
      if (tolower(line[0]) == 'q')
         break;
      addr = strtol(line, NULL, 16);
      p = strchr(line, '=');
      if (!p)
         printf("[0x%04x] = %02x\n", addr,
            debug_mode ? debug_read_from_address(addr%256, addr/256) : read_from_address(addr%256, addr/256));
      else {
         val = strtol(p+1, NULL, 16);
         if (debug_mode)
            debug_write_to_address(addr%256, addr/256, val);
         else
          write_to_address(addr%256, addr/256, val);
         printf("[0x%04x] = %02x\n", addr,
            debug_mode ? debug_read_from_address(addr%256, addr/256) : read_from_address(addr%256, addr/256));
      }
   }
}

static void write_zeros(long start, long count)
{
   long i;

   for (i=start ; i<start+count ; i++) {
      if (i % 100 == 0)
         printf("%d%%...\r", 100*(i-start)/(count-start+1));
      write_to_address(i%256, i/256, 0);
   }

   printf("Transfer done!\n");
}

static void write_from_file(char *filename, long start, long count)
{
   FILE *fp;
   long i, j;
   byte c, r;

   if (!(fp = fopen(filename, "rb"))) {
      printf("File %s cannot be opened. Skipping.\n", filename);
      return;
   }

   for (i=start ; i<start+count ; i++) {
      if (i % 100 == 0)
         printf("%d%%...\r", 100*(i-start)/(count-start+1));
      if (EOF == (j = fgetc(fp))) {
         printf("File %s doesn't provide enough data. Skipping.\n", filename);
         fclose(fp);
         return;
      }
      c = (byte)j;
      write_to_address(i%256, i/256, (int)c);
      if (c != (r = (byte)read_from_address(i%256, i/256))) {
         printf("Address %d should be %d, was %d!\n", i, c, r);
         fclose(fp);
         return;
      }
      if (i<4)
      printf("\n%d = %x\n", i, c);
   }

   fclose(fp);
   printf("Transfer done!\n");
}


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

static void ram2scr()
{
   int i, x, y;
   byte d;
   r2sent_t *r;
   char c[2];

   _clearscreen(_GCLEARSCREEN);

   if (ram2scr_ents)
      free(ram2scr_ents);
   ram2scr_ents = calloc(sizeof(r2sent_t), ram2scr_width*ram2scr_height);

   while (1) {
      if (kbhit())
         break;

      /* Obtain updated values from RAM */
      for (x=0 ; x<ram2scr_width ; x++) {
         for (y=0 ; y<ram2scr_height ; y++) {
            i = y*ram2scr_width + x;
            r = &ram2scr_ents[i];
            d = read_from_address(i%256, i/256);
            if (d==0x80)
               continue;
            if (r->t == 0) {
               r->t = clock();
               memset(r->v, d, R2SVALS);
            }
            else {
               r->v[R2SVALS-1] = d;
            }
         }            
      }

      /* Paint the screen */
      for (x=0 ; x<ram2scr_width ; x++) {
         for (y=0 ; y<ram2scr_height ; y++) {
            i = y*ram2scr_width + x;
            r = &ram2scr_ents[i];
            c[0] = r->v[R2SVALS-1];
            c[1] = '\0';
            _settextposition(y+1, x+1);
            _outtext(c);
         }
      }

   }

   free(ram2scr_ents);
   ram2scr_ents = NULL;
}

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

typedef enum {
   M_MAIN,
   M_MC,
   M_RAM,
   M_PAR,
   M_SCREEN,
   M_QUIT
} menu_t;

static char getmenuchar()
{
   char c;
   printf("> ");
   c = getch();
   printf("%c\n", c);
   return c;
}

static void presskey()
{
   puts("Press any key when ready.\n");
   (void)getch();
}

static void get_filename(char *buf, const char *def)
{
   int x;
   printf("\nEnter filename (or Enter for %s):\n", def);
   fgets(buf, 1000, stdin);
   if (strlen(buf) <= 2) {
      strcpy(buf, def);
      goto done;
   }
   if (buf[strlen(buf)-1] == '\n')
      buf[strlen(buf)-1] = '\0';
done:
   puts("\n");
   printf("%s it is %d.\n", buf, x);
}                            

static void enforce_host_toggle(int set_to_ram)
{
   if (set_to_ram == 1)
      puts("\n>>>>> Please set host mode to RAM (switch ON) <<<<<\n");
   else if (set_to_ram == 0)
      puts("\n>>>>> Please set host mode to MICROCODE (switch OFF) <<<<<\n");
   else {
      puts("\n>>>>> Please set host mode switch appropriately <<<<<");
      puts(">>>>>       OFF for MICROCODE, ON for RAM       <<<<<\n");
   }
   presskey();      
}

int menu_main(menu_t menu)
{
   puts("");
   puts("===== Zusie Monitor Main Menu =====");
   puts("");
   puts("1. Microcode");
   puts("2. RAM and Runtime monitoring");
   puts("4. Host communications tests");
   puts("0. Quit");

   switch (getmenuchar()) {
      case '1':
         return M_MC;
      case '2':
         return M_RAM;
      case '4':
         return M_PAR;
      case '0':
         return M_QUIT;
   }

   puts("\a");
   return menu;
}

int menu_parallel_port_tests(menu_t menu)
{
   puts("");
   puts("===== Communications Testing Menu =====");
   puts("");
   puts("1. Memory test: Write address modulus");
   puts("2. Memory test: Write alternating bits");
   puts("3. Memory test: Write 16-bit modulus");
   puts("4. Read/write individual bytes from memory");
   puts("5. Read/write individual bytes from memory with debug");
   puts("0. Back to main menu");

   switch (getmenuchar()) {
      case '1':
         enforce_host_toggle(-1);
         test_address_modulus(CHIP_SIZE);
         return M_PAR;
      case '2':
         enforce_host_toggle(-1);
         test_alternating(CHIP_SIZE);
         return M_PAR;
      case '3':
         enforce_host_toggle(-1);
         mc_test_16_modulus(CHIP_SIZE);
         return M_PAR;
      case '4':
         singles(0);
         return M_PAR;
      case '5':
         singles(1);
         return M_PAR;
   }

   return M_MAIN;
}

int menu_mc_main(menu_t menu)
{
   char filename[1024];

   puts("");
   puts("===== Microcode Menu =====");
   puts("");
   puts("1. Upload microcode from file");
   puts("2. Reset microcode to all zeros");
   puts("0. Back to main menu");

   switch (getmenuchar()) {
      case '1':
         enforce_host_toggle(0);
         get_filename(filename, "MC.BIN");
         write_from_file(filename, 0, CHIP_SIZE);
         return M_MC;
      case '2':
         enforce_host_toggle(0);
         write_zeros(0, CHIP_SIZE);
         return M_MC;
   }

   return M_MAIN;
}

int menu_ram_main(menu_t menu)
{
   char filename[1024];

   puts("");
   puts("===== RAM Menu =====");
   puts("");
   puts("1. Reset microcode to all zeros");
   puts("2. Upload a program (RAM image)");
   puts("3. Map RAM range to screen");
   puts("0. Back to main menu");

   switch (getmenuchar()) {
      case '1':
         enforce_host_toggle(1);
         write_zeros(0, CHIP_SIZE);
         return M_RAM;
      case '2':
         enforce_host_toggle(1);
         get_filename(filename, "TEST1.ZAB");
         write_from_file(filename, 0, CHIP_SIZE/10);  //XXXXXXXXXXXXXXXXXXXX
         return M_RAM;
      case '3':
         enforce_host_toggle(1);
         return M_SCREEN;
   }

   return M_MAIN;
}

int menu_ram_screen(menu_t menu)
{
   char filename[1024];

   puts("");
   puts("===== RAM To Screen Settings =====");
   puts("");
   printf("1. Set base address [0x%04x]\n", ram2scr_base);
   printf("2. Set width [%d]\n", ram2scr_width);
   printf("3. Set height [%d]\n", ram2scr_height);
   printf("9. Run! (q to quit)\n");
   printf("0. Back to main menu\n");

   switch (getmenuchar()) {
      case '1':
         puts("\nEnter base address:");
         scanf("%x", &ram2scr_base);
         puts("\n");
         return M_SCREEN;
      case '2':
         puts("\nEnter width:");
         scanf("%x", &ram2scr_width);
         puts("\n");
         return M_SCREEN;
      case '3':
         puts("\nEnter height:");
         scanf("%x", &ram2scr_height);
         puts("\n");
         return M_SCREEN;
      case '9':
         ram2scr();
         return M_SCREEN;
      case '0':
         return M_RAM;
   }

   return M_MAIN;
}

typedef int (* menufn_t)(menu_t);

static menufn_t menufns[] = {
   menu_main,
   menu_mc_main,
   menu_ram_main,
   menu_parallel_port_tests,
   menu_ram_screen,
   NULL
};

int main(int argc, char **argv)
{
   unsigned long i;
   byte c, d;
   menu_t menu = M_MAIN;

   // Install high precision timer
   dos_old_timer = _dos_getvect(0x1C);
   _dos_setvect(0x1C, new_timer);

   // Make port output by default
   set_direction(1);

   // Print a fancy banner
   puts("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
   puts("  ZZZZZZZZZZ  UU     UU    SSSS    IIII  EEEEEEEE");
   puts("  Z      ZZ   UU     UU   SS  SS    II   EE      ");
   puts("        ZZ    UU     UU  SS    SS   II   EE      ");
   puts("   +   ZZ     UU     UU   SS        II   EE      ");
   puts("      ZZ      UU     UU    SSSS     II   EEEEEE  ");
   puts("     ZZ  -    UU     UU       SS    II   EE      ");
   puts("    ZZ        UU     UU  SS    SS   II   EE      ");
   puts("   ZZ      Z   UUU UUU    SS  SS    II   EE      ");
   puts("  ZZZZZZZZZZ    UUUUU      SSSS    IIII  EEEEEEEE");
   puts("\n");
   puts("            Zusie Host Control Monitor");
   puts("               By Fredrik Andersson");
   puts("\n");

   // Run menu system until exit
   while ((menu = menufns[menu](menu)) != M_QUIT) ;

   // Don't forget to always re-install the OS timer
   _dos_setvect(0x1C, dos_old_timer);

   return 0;
}
                               
                      
