/*   Virtual Nascom, a Nascom II emulator.

     Copyright (C) 2000,2009  Tommy Thorn

     Z80 emulator portition Copyright (C) 1995,1998 Frank D. Cringle.

     NasEmu is free software; you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
     02111-1307, USA.


   A Nascom consists of:

    - a Z80 CPU,
    - an UART,
    - a bitmapped keyboard,
    - memory:
        0000 - 07ff  2 KB ROM monitor,
        0800 - 0bff  1 KB screen memory,
        0c00 - 0fff  1 KB workspace
        1000 - dfff       memory
        e000 - ffff  8 KB of MS Basic

  With the Z80 emulator in place the first thing to get working is the
  screen memory.  The "correct" way to simulate screen memory is to
  trap upon writes, but that would be slow.  We do it any just to get
  started.
*/
/*
Download SDL1.2
Simple DirectMedia Layer - Homepage
https://www.libsdl.org/
Simple DirectMedia Layer is a cross-platform development library designed to provide
SDL officially supports Windows, Mac OS X, Linux, iOS, and Android.
‎SDL version 2.0.5 (stable) · ‎SDL Wiki · ‎SDL 1.2 · ‎License

download SDL1.2
install
./configure && make && 
sudo make install
*/


#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include "simz80.h"
#include "nascom.h"
#include <SDL.h>

#define FONT_H_PITCH 16
#define FONT_H       15
#define FONT_W        8



/* macro for file header */
#define  _Z80HEADER  ( const char * ) "Z80ASM\032\n"

extern uint8_t nascom_font_raw[];

static SDL_Surface *screen;
static struct font {
    SDL_Surface *surf;
    int w, h, h_pitch;
} nascom_font;

FILE *serial_out, *serial_in;
int tape_led = 0;
int serial_input_available = 0;
int  vflag = 0;

static unsigned framebuffer_generation;

static void RenderItem(struct font *font, int idx, int x, int y)
{
    auto SDL_Rect dest = { x, y, font->w, font->h };
    SDL_Rect clip = { 0, idx * font->h_pitch, font->w, font->h };
    SDL_BlitSurface(font->surf, &clip, screen, &dest);
}

void RenderLetters(struct font *font, char *s, int x, int y)
{
    for (; *s; ++s, x += font->w)
        RenderItem(font, *s, x, y);
}

int mysetup(int argc, char **argv)
{
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
        return 1;
    }


    atexit(SDL_Quit);


    screen = SDL_SetVideoMode(48 * FONT_W, 16 * FONT_H, 8, SDL_SWSURFACE);
    if (screen == NULL) {
        fprintf(stderr, "Unable to set video: %s\n", SDL_GetError());
        return 1;
    }

    /* Set the window caption */
    SDL_WM_SetCaption("Nascom II", "Nascom II");

#if 0
    /* Populate the palette */
    SDL_Color colors[256];

    colors[0].r = colors[0].g = colors[0].b = 0;
    colors[255].r = colors[255].b = 0;
    colors[255].g = 255;

    /* Set palette */
    if (!SDL_SetColors(screen, colors, 0, 256)) {
        fprintf(stderr, "Unable to create framebuffer palette: %s\n",
                SDL_GetError());
        screen = 0; //XXX should free it
        return 1;
    }
#endif

    /* Load font */
    nascom_font.surf =
        SDL_CreateRGBSurfaceFrom(
                nascom_font_raw,
                8 /* width */,
           256*16 /* height */,
                1 /* depth */,
                1 /* pitch */,
                0 /* Rmask */,
                1 /* Gmask */,
                0 /* Bmask */,
                0 /* Amask */);
    nascom_font.w = FONT_W;
    nascom_font.h = FONT_H;
    nascom_font.h_pitch = FONT_H_PITCH;

    if (!nascom_font.surf) {
        perror("Couldn't load the font\n");
        return 1;
    }

    nascom_font.surf = SDL_DisplayFormat(nascom_font.surf);

    return 0;
}



/* */

unsigned char keym[9] = {
    0,  /* ? ? ? Shift ? ? ? ? */
    0,  /* ?!TXF5BH  ! = Up*/
    0,  /* ?!YZD6NJ  ! = Left*/
    0,  /* ?!USE7MK  ! = Down */
    0,  /* ?!IAW8,L  ! = Right */
    0,  /* ??OQ39.; */
    0,  /* ?[P120/: */
    0,  /* ?]R C4VG */
    0   /* ? ? CR - Newline BS */
};

unsigned char keyp = 0;
unsigned char port0;



/* 
 * original routine
  
void load_nascom(char *file)
{
  FILE *f = fopen(file, "r");
  int a, b1, b2, b3, b4, b5, b6, b7, b8;
  int count = 0;
  int ch;

  if (!f) {
    perror(file);
    exit(1);
  }

  if (vflag) printf("Loading %s", file);
  for (;!feof(f);) {
    if (fscanf(f, "%x %x %x %x %x %x %x %x %x",
	       &a, &b1, &b2, &b3, &b4, &b5, &b6, &b7, &b8)
	== 9) {
      RAM(a)   = b1;
      RAM(a+1) = b2;
      RAM(a+2) = b3;
      RAM(a+3) = b4;
      RAM(a+4) = b5;
      RAM(a+5) = b6;
      RAM(a+6) = b7;
      RAM(a+7) = b8;
      count += 8;
    }

    do 
      ch = fgetc(f); 
    while (ch != -1 && ch != '\n');
    if (ch == -1)
      break;
  }
  fclose(f);
  if (vflag) printf(". Successfully loaded %d bytes\n", count);
}
*/

/*
 * ==================================================================
 * Try and add ability to read .z80 files.
 */

/* reads header of a file and tests if it's Z80 ASM file, reads address */
/* return value: 0=OK, 1=this is not a z80 asm file */
int read_header(FILE *stream,unsigned short *address, int *len)
{
 unsigned char tmp[9];
 unsigned char c[2];
 unsigned a,b;
 int ret=0;

 b=strlen(_Z80HEADER);
 tmp[b]=0;
 a=0;
 if ((fread(tmp,1,b,stream))!=b) ret=1;
 else if (strcmp(tmp,_Z80HEADER)) ret=1;
 else if (fread(c,1,2,stream)!=2) ret=1;
 else
 { *address=(c[1]<<8)|c[0]; a=b+2; }
 fseek(stream,0,SEEK_END);
 b=ftell(stream);
 fseek(stream,a,SEEK_SET);
 *len=b-a;
 return ret; 
}

unsigned  dma_write(unsigned short offset, unsigned count, FILE *from)
{

/* return  fread(memory+offset,1,count+offset<65536U?count:65536U-offset,from); */
   return  fread(ram+offset,1,count+offset<65536U?count:65536U-offset,from);

}



unsigned  dma_read(unsigned short offset, unsigned count, FILE *to)
{
/* return  fwrite(memory+offset,1,count+offset<65536U?count:65536U-offset,to); */
   return  fwrite(ram+offset,1,count+offset<65536U?count:65536U-offset,to);
}


int load_nascom_new(char *file) {
   unsigned short start;
   
   int x ; /* length */
   printf("load_nascom_new(char *file)");
   FILE *stream = fopen( file,"rb");
   
   if (!stream) {
	   stream=0;
	   /* error_msg(file,"Can't read file"); */
       printf("error load_nascom_new(char *file)");

	   return 0;
   }
   if (read_header(stream,&start,&x)) {
     fclose(stream);
     printf("error read hearer load_nascom_new(char *file)");

	 return 0; /* error */
   } else {
     dma_write(start,x,stream);
     fclose(stream);
     stream=0;
   }
   return 1  ;
}



/*
 * 
 * 
:0E 1000 00 210C10E5CD5F10F1210100C96865 DB
:0E 100E 00 6C6C6F20646F75670A00DDE5DD21 F4
:07 101C 00 0000DD39DDE1C9 30
:0E102300DDE5DD210000DD39DD7E04F533CD95
:0E103100181033DDE1C9DDE5DD210000DD39F9
:0E103F00DD6E06DD6607E5DD6E04DD6605E5A7
:0E104D00210000E5212310E5CDE611F1F1F1BF
:0D105B00F1DDE1C9DDE5DD210000DD392119
:0E1068000400392323E5DD6E04DD6605E52175
:0E1076000000E5212310E5CDE611F1F1F1F1C6
*
* 
*/


int load_nascom_ihx(char *file) {
   unsigned short start;
   
   int x ; /* length */
   uint hex_read, hex_len, hex_addr, hex_cmd ;
   uint hex_count, hex_data, hex_check ;
  
   FILE *stream = fopen( file,"rb");
   printf("load_nascom_ihx(char *file)");

   
   if (!stream) {
	   stream=0;
	   /* error_msg(file,"Can't read file"); */
       printf("error load_nascom_ihx(char *file)");

	   return 0;
   }
   while ( !feof( stream ) ) {
     
   /*:0E 100E 00 6C6C6F20646F75670A00DDE5DD21 F4 */
      
     hex_read = fscanf(stream,":%2x%4x%2x",&hex_len,&hex_addr,&hex_cmd);
     if (hex_read ){
       printf( "\n%x  %2d, %2d, %x  : ", hex_read, hex_len, hex_addr, hex_cmd );
      
     
       for( hex_count= 0 ; hex_count < hex_len ; hex_count++ ){
	     hex_read = fscanf(stream, "%2x",&hex_data ); 	  
         printf(" %02X", hex_data );
         RAM( hex_addr ) = hex_data ; 
         hex_addr ++;
       }    
       hex_read = fscanf(stream, "%2x\n",&hex_check);
     }
   }
   fclose(stream);
   stream=0;


   printf(" exit from load_nascom_ihx()\n");
   /* hex_read = getchar() ; */
   return 1  ;
}


/*
 * ====================================================
 */
  


void load_nascom(char *file)
{
  FILE *f ;	
	
  if  ( ! load_nascom_new( file) ) {	
	
  f = fopen(file, "r");
  int a, b1, b2, b3, b4, b5, b6, b7, b8;
  int count = 0;
  int ch;

  if (!f) {
    perror(file);
    exit(1);
  }

  if (vflag) printf("Loading %s", file);
  for (;!feof(f);) {
    if (fscanf(f, "%x %x %x %x %x %x %x %x %x",
	       &a, &b1, &b2, &b3, &b4, &b5, &b6, &b7, &b8)
	== 9) {
      RAM(a)   = b1;
      RAM(a+1) = b2;
      RAM(a+2) = b3;
      RAM(a+3) = b4;
      RAM(a+4) = b5;
      RAM(a+5) = b6;
      RAM(a+6) = b7;
      RAM(a+7) = b8;
      count += 8;
    }

    do 
    ch = fgetc(f); 
    while (ch != -1 && ch != '\n');
    if (ch == -1)
      break;
    }
    fclose(f);
    if (vflag) printf(". Successfully loaded %d bytes\n", count);
  }
}


#ifdef comment

void load_nascom(const char *file)
{
    FILE *f = fopen(file, "r");
    int a, b1, b2, b3, b4, b5, b6, b7, b8;
    int count = 0;
    int ch;

    if (!f) {
        perror(file);
        exit(1);
    }

    if (verbose)
        printf("Loading %s", file);

    for (; !feof(f) ;) {
        if (fscanf(f, "%x %x %x %x %x %x %x %x %x",
                   &a, &b1, &b2, &b3, &b4, &b5, &b6, &b7, &b8) == 9) {
            RAM(a)   = b1;
            RAM(a+1) = b2;
            RAM(a+2) = b3;
            RAM(a+3) = b4;
            RAM(a+4) = b5;
            RAM(a+5) = b6;
            RAM(a+6) = b7;
            RAM(a+7) = b8;
            count += 8;
        }

        do
            ch = fgetc(f);
        while (ch != -1 && ch != '\n');

        if (ch == -1)
            break;
    }

    fclose(f);
    if (verbose)
        printf(". Successfully loaded %d bytes\n", count);

    if (count == 2048) {
        FILE *f;
        f = fopen("blob", "w");
        fwrite((const void *) ram, 1, 2048, f);
        fclose(f);
    }
}
#endif



static char * kbd_translation[] = {
/* 0 */  "xxzzzxxx",
/* 1 */  "xzTXF5BH",
/* 2 */  "xzYZD6NJ",
/* 3 */  "xzUSE7MK",
/* 4 */  "xzIAW8,L",
/* 5 */  "xzOQ39.;",
/* 6 */  "x[P120/'",
/* 7 */  "x]R C4VG",
/* 8 */  "xzxxx-\r\010"
};

int reset = 0;

void mainloop(void)
{
    int i = -1, bit = 0;
    unsigned last_generation = 0;

    for (;;) {
        SDL_Event event;

        while (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_MOUSEMOTION:
                /*printf("Mouse moved by %d,%d to (%d,%d)\n",
                  event.motion.xrel, event.motion.yrel,
                  event.motion.x, event.motion.y);*/
                break;
            case SDL_MOUSEBUTTONDOWN:
                /*printf("Mouse button %d pressed at (%d,%d)\n",
                  event.button.button, event.button.x, event.button.y);*/
                break;
            case SDL_KEYDOWN:
            case SDL_KEYUP:
                if (event.key.keysym.sym == '\\' && event.type == SDL_KEYDOWN) {
                    reset = 1;
                    break;
                }

                if (event.key.keysym.sym == 27)
                    exit(0);

                if (event.key.keysym.sym < 128) {
                    int ch = toupper(event.key.keysym.sym);
                    for (i = 0; i < 9; ++i)
                        for (bit = 0; bit < 8; ++bit)
                            if (kbd_translation[i][7-bit] == ch) {
                                goto found;
                            }
                    i = -1;
found:;
                } else {
                    switch (event.key.keysym.sym) {
                    case SDLK_LCTRL:   i = 0, bit = 3; break;
                    case SDLK_LSHIFT:
                    case SDLK_RSHIFT:  i = 0, bit = 4; break;
                    case SDLK_RCTRL:   i = 0, bit = 5; break;
                    case SDLK_UP:      i = 1, bit = 6; break;
                    case SDLK_LEFT:    i = 2, bit = 6; break;
                    case SDLK_DOWN:    i = 3, bit = 6; break;
                    case SDLK_RIGHT:   i = 4, bit = 6; break;
                    case SDLK_RMETA:
                    case SDLK_LMETA:
                    case SDLK_RALT:
                    case SDLK_LALT:    i = 5, bit = 6; break;
                    case SDLK_KP_ENTER:i = 8, bit = 6; break;
                    case SDLK_END: {
                        /* Undocumented hack */
                        FILE *f;
                        f = fopen("screendump", "w");
                        fwrite((const void *) (ram+0x800), 1, 1024, f);
                        fclose(f);
                        if (verbose) printf("Screen dumped\n");
                        break;
                    }
                    default:
                        //printf("%d? ", event.key.keysym.sym);
                        //printf(" keysym %s\n", SDL_GetKeyName(event.key.keysym.sym));
                        ;
                    }
                }

                if (i != -1) {
                    if (event.type == SDL_KEYDOWN)
                        keym[i] |= 1 << bit;
                    else
                        keym[i] &= ~(1 << bit);
                }
                break;
            case SDL_QUIT:
                //printf("Quit\n");
                return;
            default:
                //printf("Unknown event: %d\n", event.type);
                break;
            }
        }

        /* Only update the screen if the framebuffer has been written
           since last update */
        if (last_generation != framebuffer_generation) {
            int x, y;
            unsigned p = 0x800 + 10;
            last_generation = framebuffer_generation;

            for (y = 1; y < 16; ++y, p += 64) {
                for (x = 0; x < 48; ++x)
                    RenderItem(&nascom_font, RAM(p + x), x * FONT_W, y * FONT_H);
            }

            // Nascom is strange in that the last line is the first line!
            for (x = 0; x < 48; ++x)
                RenderItem(&nascom_font, RAM(p + x), x * FONT_W, 0);

            SDL_UpdateRect(screen, 0, 0, screen->w, screen->h);
            // SDL_Flip(screen); either seem to work
        }

        SDL_Delay(1000 / 30); // 30 fps
    }
}

int sim_delay()
{
    if (reset) {
        reset = 0;
        return 1;
    }

    SDL_Delay(1);

    return 0;
}

void simulate(void *dummy)
{
    simz80(pc, 900, sim_delay);
}

static void
usage(void)
{
    fprintf(stderr,
 "This is Virtual Nascom.  Usage: %s {flags} {commands}\n"
 "           -i <file>       take serial port input from file (if tape led is on)\n"
 "           -m <file>       use <file> as monitor (default is nassys3.nal)\n"
 "           -v              be verbose\n"
            ,progname);
    exit (1);
}

int main(int argc, char **argv)
{
    int c;

    serial_out = fopen("serialout.txt", "w+");

    if (!serial_out)
        exit(3);

    if (mysetup(argc, argv))
        return 1;

    monitor = "nassys3.nal";
    progname = argv[0];


#ifdef MMU
    for (c=0; c<MEMSIZE/4; ++c) pagetable[c]=ram+(c<<12);
#endif

    while ((c = getopt(argc, argv, "i:m:v")) != EOF)
        switch (c) {
        case 'i':
            serial_in = fopen(optarg, "r");
            //printf("serial input %s -> %p\n", optarg, serial_in);
            serial_input_available = !feof(serial_in);
            break;
        case 'm':
            monitor = optarg;
            break;
        case 'v':
            verbose = 1;
            break;
        case '?':
            usage();
        }

    if (verbose)
        puts("Virtual Nascom, a Nascom 2 emulator version " VERSION "\n"
             "Copyright (C) 2000-2009 Tommy Thorn.\n"
             "Uses software from \n"
             "Yet Another Z80 Emulator version " YAZEVERSION
             ", Copyright (C) 1995,1998 Frank D. Cringle.\n"
             "VirtualNascom comes with ABSOLUTELY NO WARRANTY; for details\n"
             "see the file \"COPYING\" in the distribution directory.\n");

    load_nascom(monitor);
    load_nascom("basic.nal");
  
  /* load c program built using SDCC */
  /* load_nascom_ihx( "../sdcc/test.ihx" ); /* */
  /* load_nascom_ihx( "../sdcc/test3.ihx" ); /* */
  /* load_nascom(monitor); /* */
  /* load_nascom("basic.nal"); */
  /* load_nascom("doug.z80"); /* */
  load_nascom_ihx( "test.ihx" ); /* */



    for (; optind < argc; optind++)
        load_nascom(argv[optind]);

    SDL_CreateThread((int (*)(void *))simulate, NULL);
    mainloop();

    exit(0);
}

/*
 * 1.7 Input/output port addressing
 *
 *     Output Bit
 * P0  7 Not available          7 Unused
 *     6 Not used               6 Keyboard S6
 *     5 Unused                 5 Keyboard S3
 *     4 Tape drive LED         4 Keyboard S5
 *     3 Single step            3 Keyboard S4
 *     2 Unused                 2 Keyboard S0
 *     1 Reset keyb'd count     1 Keyboard S2
 *     0 Clock keyb'd count     0 Keyboard S1
 */

#define P0_OUT_TAPE_DRIVE_LED 16
#define P0_OUT_SINGLE_STEP     8
#define P0_OUT_KEYBOARD_RESET  2
#define P0_OUT_KEYBOARD_CLOCK  1

/*
 * P1  0 - 7 Data to UART       0 - 7 Data from UART
 *     (Serial port)            (Serial port)
 *
 * P2  0 - 7 Not assigned       7 Data received from UART
 *                              6 UART TBR empty
 *                              5 Not assigned
 *                              4 Not assigned
 *                              3 F error on UART
 *                              2 P error on UART
 *                              1 O error on UART
 *                              0 Not assigned
 */

#define UART_DATA_READY 128
#define UART_TBR_EMPTY   64
#define UART_F_ERROR      8
#define UART_P_ERROR      4
#define UART_O_ERROR      2

/*
 * P3  Not assigned             Not assigned
 *
 * P4  PIO port A data input and output
 *
 * P5  PIO port B data input and output
 *
 * P6  PIO port A control
 *
 * P7  PIO port B control
 */

void out(unsigned int port, unsigned char value)
{
    unsigned int down_trans;

    if (0) fprintf(stdout, "[%02x] <- %02x\n", port, value);

    switch (port) {
    case 0:
        /* KBD */
        down_trans = port0 & ~value;
        port0 = value;

        if ((down_trans & P0_OUT_KEYBOARD_CLOCK) && keyp < 9)
            keyp++;
        if (down_trans & P0_OUT_KEYBOARD_RESET)
            keyp = 0;

#if 0
        if (tape_led != !!(value & P0_OUT_TAPE_DRIVE_LED))
            fprintf(stderr, "Tape LED = %d\n", !!(value & P0_OUT_TAPE_DRIVE_LED));
#endif
        tape_led = !!(value & P0_OUT_TAPE_DRIVE_LED);
        break;

    case 1:
        fputc(value, serial_out);
        break;

    default:
        if (verbose)
            fprintf(stdout, "OUT [%02x] <- %02x\n", port, value);
    }
}

int in(unsigned int port)
{
    if (0) fprintf(stdout, "<- [%02x]\n", port);

    switch (port) {
    case 0:
        /* KBD */
        /* printf("[%d]", keyp); */
        return ~keym[keyp];
    case 1:
        if (serial_input_available & tape_led) {
            char ch = fgetc(serial_in);
            serial_input_available = !feof(serial_in);
            return ch;
        }
        else
            return 0;
    case 2:
        /* Status port on the UART */
        return UART_TBR_EMPTY |
            (serial_input_available & tape_led ? UART_DATA_READY : 0);
    default:
        if (verbose)
            fprintf(stdout, "IN <- [%02x]\n", port);
        return 0;
    }
}

void slow_write(unsigned int a, unsigned char v)
{
    if (INSCREEN(a)) {
        unsigned int y = (a-0x800) / 64;
        unsigned int x = (a-0x800) % 64;
        /* fprintf(stdout, "putbyte %04x %02x '%c'\n", a, v, v); */
        if (10 <= x && x < 58 && ' ' <= v) {
            if (y == 15)
                y = 0;
            else
                ++y;

            //xputch(x-10, y, v);
            //fprintf(stderr, "\033[%d;%dH%c", 1+y, 1+x-10, v);
            framebuffer_generation++;
        }
    }

    if (0x800 <= a && a <= 0xE000)
        RAM(a) = v;
}
