/* 
   Pcm: a PC eMulator
   Copyright (C) 1992 Electronetics, Inc.  All rights reserved.

   This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Contains code for accessing the host window system.
 * Currently supports X11 (using Xlib) only.  Partial
 * support for a non-graphics mode may work for text.
 */

#include "sim.h"

short last_attr[80*25];
int xwin;

#ifdef TTY

#include <sys/ioctl.h>
#include <ctype.h>
#include <errno.h>

int next_row, last_col = -1;

scroll_window(start_line, lcount, attr)
unsigned char *start_line;
int lcount, attr; {
  putchar('\n');
}

init_window() {
  setbuf(stdin,NULL);
  setbuf(stdout,NULL);
  putchar('\n');
  return 0;
}

clear_window() {
  
}

refresh_cursor(count)
int count; {

}

int asc2scan[128] = { /* Scan codes for keyboard */
0,0,0,0,0,0,0,0,0x0e,0,0x1c,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0x39,0x82,0xa8,0x84,0x85,0x86,0x88,0x28,0x8a,0x8b,0x89,0x8d,0x33,0x0c,0x34,0x35,
0x0b,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0xa7,0x27,0xb3,0x0d,0xb4,0xb5,
0x83,0x9e,0xb0,0xae,0xa0,0x92,0xa1,0xa2,0xa3,0x97,0xa4,0xa5,0xa6,0xb2,0xb1,0x98,
0x99,0x90,0x93,0x9f,0x94,0x96,0xaf,0x91,0xad,0x95,0xac,0x1a,0x2b,0x1b,0x87,0x8c,
0x29,0x1e,0x30,0x2e,0x20,0x12,0x21,0x22,0x23,0x17,0x24,0x25,0x26,0x32,0x31,0x18,
0x19,0x10,0x13,0x1f,0x14,0x16,0x2f,0x11,0x2d,0x15,0x2c,0x9a,0xab,0x9b,0xa9,0x53
};

pcm_dispatch() {
  unsigned int nbytes;
  unsigned char c, code, cntl, shift;
  ioctl(0, FIONREAD, &nbytes);
  if (nbytes == 0) return;
  if (read(0,(char *)&c,1) <= 0) {
    pcmexit("Fatal error reading stdin\n");
  }  
  if (c >= 0x80) {
    return;
  }
  cntl = shift = 0;
  code = asc2scan[c];
  if (c < 0x20 && !code) {
    cntl = 1;
    c += 0x40;
    code = asc2scan[c];
  }
  if (code  & 0x80) {
    shift = 1;
    code &= 0x7f;
  }
  if (cntl)
    enterkey(29);  /* Control press */
  if (shift)
    enterkey(42);  /* Shift press */
  enterkey(code);
  enterkey(code | 0x80);
  if (shift)
    enterkey(42 | 0x80);  /* Shift release */
  if (cntl)
    enterkey(29 | 0x80); /* Control release */
}

put_char(row, col, char_att, forcep)
int row, col;
unsigned short char_att;
int forcep; {
  unsigned char the_char, the_att;
  the_char = char_att >> 8;
  the_att = char_att & 0xff;
  switch(the_att) {
  case 0: case 0x8: case 0x80: case 0x88:
    the_char = ' '; /* Nothing to print */
  break;
  }
  if (the_char == 0 || !isprint(the_char)) the_char = ' ';
/*printf("putchar '%c' at %d %d\n",the_char,row,col);*/
  if (next_row != row) {
    putchar('\n');
    next_row = row;
    last_col = -1;
  }
  while (col <= last_col--) {
    putchar('\b');
  } 
  putchar(the_char);
  last_col = col;
}

window_exit() {
  putchar('\n');
}

#else
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/keysym.h>

#include "pcm.xbm"

Display *display;
int screen;
Window win;
Pixmap pmap;
int event_mask;
GC norm_gc;
GC cursor_gc;
GC bold_gc;
Font norm_fid;
Font bold_fid;
int window_exit();
int white;
int black;
int invert;

#include <sys/time.h>

char   *fontfilename = "pcmfont";
char   *boldfontfilename = "pcmbold";
char   pfname[256];

static char header_msg[] = "PCM v0.6";
extern unsigned char *vidbase, *start_line;

short pc_cursor_image[] = { 
  0x0, 0x0, 0x0, 0x0,
  0x0, 0x0, 0x0, 0x0,
  0x0, 0x0, 0xffff, 0xffff,
  0x0, 0x0, 0x0, 0x0
  };

short pc_cursor_clear[] = {
  0x0, 0x0, 0x0, 0x0,
  0x0, 0x0, 0x0, 0x0,
  0x0, 0x0, 0x0, 0x0,
  0x0, 0x0, 0x0, 0x0
};

short underline_image[] = { 
  0x0, 0x0, 0x0, 0x0,
  0x0, 0x0, 0x0, 0x0,
  0x0, 0x0, 0x0, 0x0,
  0x0, 0xffff, 0x0, 0x0
  };

static short icon_image[] = {
#include "pcm.icon"
};

static int cursor_x, cursor_y;
static unsigned short save_char = 0x2007; /* Normal space */
static char c_owrite;  /* True if old cursor has been overwritten */
char do_refresh;  /* True if cursor should be redrawn */
unsigned char pc_scan[256]; /* Translates X keycodes to PC scan codes */

init_window(keys)
FILE *keys; {
  int border_width = 4;
  int i, j;
  unsigned int width, height; /* window dimens in pixels */
  Pixmap icon_pixmap;
  XSizeHints size_hints;
  XFontStruct *font_info;
  char buf[128];

  while (fgets(buf, 127, keys)) {
    if (buf[0] == '#') continue;
    if (sscanf(buf,"%d %d",&i,&j) == 2) {
      pc_scan[i] = j;
    }    
  }
  fclose(keys);
/*
 * Connect to X server
 */
  if ((display = XOpenDisplay(NULL)) == NULL) {
    fprintf(stderr,"Unable to connect to X server\n");
    return 1;
  }
  screen = DefaultScreen(display);
/*
 * Open window (0, 0 default position)
 */
  height = 25 * 16;
  width  = 80 * 8;
  white = WhitePixel(display, screen);
  black = BlackPixel(display, screen);
  if (invert) {
    i = white;
    white = black;
    black = i;
  }

  win = XCreateSimpleWindow(display, 
          RootWindow(display, screen), 0, 0, width, height, 
          border_width, black, black);
  XSetWindowBackground(display, win, white);
/*
 * Create icon
 */
  icon_pixmap = XCreateBitmapFromData(display, win, pcm_bits,
     pcm_width, pcm_height);          
/*
 * Set hints
 */
  size_hints.flags = PSize | PMinSize;
  size_hints.width = width;
  size_hints.height = height;
  size_hints.min_width = width;
  size_hints.min_height = height;
  XSetStandardProperties(display, win, header_msg, "pcm",
    icon_pixmap, NULL, 0, &size_hints);
/*
 * Event mask
 */
  event_mask = ExposureMask | KeyPressMask | KeyReleaseMask |
    ButtonPressMask | StructureNotifyMask;
  XSelectInput(display, win, event_mask);

/*
 * Map window
 */
  XMapWindow(display, win);   
/*
 * Create Pixmap for fast redrawing of window contents
 */
  pmap = XCreatePixmap(display, win, width, height, 
    DefaultDepth(display,screen));
  if (pmap == NULL) {
    fprintf(stderr,"cannot create pixmap!\n");
    return 1;
  }
/*
 * Set graphic contexts for normal/bold fonts
 */
  load_font("pcmfont-16", &font_info);
  get_GC(&norm_gc, font_info, 1);
  get_GC(&cursor_gc, font_info, 1);
  norm_fid = font_info->fid;
  load_font("pcmbold-16", &font_info);
  bold_fid = font_info->fid;
  get_GC(&bold_gc, font_info, 1);

  XSetFunction(display, cursor_gc, GXxor);
  
  XClearWindow(display, win);

  XCopyArea(display, win, pmap, norm_gc, 0, 0, width, height, 0, 0);

  pcm_dispatch();

  xwin = 1;
  clear_window();

  XFlush(display);

  return 0;
}

/*
 * Paint the character/attribute onto the screen at the given row, col
 * 0,0 is upper left.  As an optimization, only send the character if
 * it is different from what was previously painted at this location. 
 */

char gcstatus[2];

put_char(row, col, char_att, forcep)
unsigned int row, col;
unsigned short char_att; 
int forcep; {
  unsigned char the_char, the_att, uline = 0;
  GC *the_gc;

  int  i = 0, invertp = 0; 

  if (row > 25 || col > 80) {
    printf("bad put_char(%d,%d)\n",row,col);
    return;
  }
  if (!forcep && last_attr[80*row+col] == char_att) {
    return;
  }
  last_attr[80*row+col] = char_att;
  the_char = char_att >> 8;
  the_att = char_att & 0xff;

  the_gc = &norm_gc;
  if (the_att & 8) {
    i++;
    the_gc = &bold_gc;
  }

  switch(the_att) { /* Look at attribute */
  case 0: case 0x8: case 0x80: case 0x88:
    the_char = ' ';  /* Nothing to print */
  break;
  case 1: case 0x9: case 0x81: case 0x89:
    uline = 1;
  break;
/*  case 0: */
  case 7:  break;            /* Normal char */
  case 0x70: case 0x78: case 0xf0: case 0xf8:
    invertp = 1;
  break;
  }

  if (gcstatus[i] != invertp) {
    if (invertp) {
      XSetFunction(display, *the_gc, GXcopyInverted);
    }
    else {
      XSetFunction(display, *the_gc, GXcopy);
    }
    gcstatus[i] = invertp;
  }
  if (the_char == 0) the_char = ' '; /* Kludge for SunView */
/*
 * Use XDrawImageString here, since it pays attention to
 * unset bits in the source and uses the background pixel value.
 */
  XDrawImageString(display, win, *the_gc, 8*col, 16*row+11, &the_char, 1);
  XDrawImageString(display, pmap, *the_gc, 8*col, 16*row+11, &the_char, 1);
  XFlush(display);
  c_owrite |= (CUR_X == cursor_x && CUR_Y == cursor_y);
}
/*
 * Routines for managing the cursor
 */

extern unsigned char *vidbase;
cursor_on() {
  cursor_x = CUR_X;
  cursor_y = CUR_Y;
  XFillRectangle(display, win, cursor_gc, 8*cursor_x, 16*cursor_y,
    8, 12);
}
cursor_off() {
  if (!c_owrite) {
/*  if (!(save_char & 0xff00)) save_char |= 0x2000; */
    save_char = *(unsigned short *)(vidbase + 160*cursor_y + 2*cursor_x);
    put_char(cursor_y, cursor_x, save_char, 1, 1); 
  }
  c_owrite = 0;
}

refresh_cursor(count)
int count; {
  static unsigned char toggle = 0;
  int j;
  if ((count & 0x7) ||
      (cursor_x == CUR_X && cursor_y == CUR_Y)) {
    return;
  }
  cursor_off();
  cursor_on();
}

/*
 * Check for a pending X input event and if one
 * is available, process it.
 */
pcm_dispatch() {
  char keybuf[64];
  KeySym ksym;
  XComposeStatus compose;
  XEvent evt;
  if (!XCheckMaskEvent(display, event_mask, &evt)) {
    return;
  }
/*printf("rec'd. evt.\n");*/
  switch(evt.type) {
  case Expose: /* get rid of all other Expose events on queue */
    while (XCheckTypedEvent(display, Expose, &evt)) ;
    XCopyArea(display, pmap, win, norm_gc, 0, 0, 80*8, 25*16, 0, 0);
    cursor_off();
    cursor_on();
  break;
  case KeyPress:
    enterkey(pc_scan[evt.xkey.keycode]);
  break;
  case KeyRelease:
    enterkey(pc_scan[evt.xkey.keycode] | 0x80);
  break;
  }
}

/*
 * Load font, exit if failure
 */
load_font(fname, finfo)
char *fname;
XFontStruct **finfo; {
  if ((*finfo = XLoadQueryFont(display, fname)) == NULL) {
    XCloseDisplay(display);
    xwin = 0;
    pcmexit("Error: X Server cannot load font %s\n",fname);
  }
}
/*
 * Create/Set the Graphics Context
 */
get_GC(gcp, finfo, winp)
GC *gcp;
XFontStruct *finfo; 
int winp; {
  if (winp) {
    *gcp = XCreateGC(display, win, 0, NULL);
  }
  else {
    *gcp = XCreateGC(display, pmap, 0, NULL);
  }
/*
 * Set font
 */
  if (finfo) {
    XSetFont(display, *gcp, finfo->fid);
  }
/*
 * Specify black foreground
 */
  XSetBackground(display, *gcp, white);
  XSetForeground(display, *gcp, black);
/*
 * Monochrome emulation, so don't worry about color planes
 */
  XSetPlaneMask(display, *gcp,
    white ^ black);
}

/*
 * put_char is too slow to redraw the entire window when scrolling
 */
char clear[80];

scroll_window(start_line, lcount, attr)
unsigned char *start_line;
int lcount, attr; {
  unsigned char *vb, *limit;

  int i, j, reset_vid = 0;
  short *sp, val = ' ';

  val <<= 8;
  val |= attr;

  limit = pc_mem + 0xb1000;

  XCopyArea(display, win, win, norm_gc, 0, 16*lcount, 80*8, 
    (25-lcount)*16, 0, 0);

  if (lcount < 25) {
    bcopy(last_attr+(lcount*80),last_attr,lcount*160);
  }
  else {
    lcount = 25;
  }

  XClearArea(display, win, 0, 16*(25-lcount), 0, 0, 0);

  XCopyArea(display, win, pmap, norm_gc, 0, 0, 80*8, 25*16, 0, 0);

  vb = start_line;
  sp = last_attr + 80*(25-lcount);
  cursor_y -= lcount;
  if (cursor_y < 0) {
    c_owrite = 1;
  }

  for (j = lcount; j > 0; j--) {
    for (i = 0 ; i < 80; i++) {
      if (vb >= limit) vb -= 0x1000;
      *sp++ = *(short *)vb = val; 
  /*    put_char(25-j, i, val, 1, 1);*/
      vb += 2;
    }
    XDrawImageString(display, win, norm_gc, 0, 16*(25-j)+11, clear, 80);
    XDrawImageString(display, pmap, norm_gc, 0, 16*(25-j)+11, clear, 80);
  }
}

/*
 * Sometimes the GC seems to inherit bits from the previous client.
 * Maybe this is a server (Apollo) bug, but just to make sure, clear
 * the window with XDrawImageString.
 */
clear_window() {
  int i;
  for (i = 0; i < 25; i++) {
    XDrawImageString(display, win, norm_gc, 0, 16*i+11, clear, 80);
    XDrawImageString(display, pmap, norm_gc, 0, 16*i+11, clear, 80);
  }
}

window_exit() {
  XUnloadFont(display, norm_fid);
  XUnloadFont(display, bold_fid);
  XFreeGC(display, norm_gc);
  XFreeGC(display, bold_gc);
  XCloseDisplay(display);
}

#endif /* CURSES */

/*
 * check_for_notifier is called during the 
 * int_08 hardware clock interrupt
 */
check_for_notifier() {
  static int cursor_count = 0;
  pcm_dispatch();
  refresh_cursor(cursor_count++); 
}



/*
 * Events in the PCM window are processed here
 */
static unsigned int keyboard_buffer[256];
/*
 * PC scan codes are stored in the keyboard_buffer
 */
unsigned char keyhead, keytail;

enterkey(k)
int k; {
  if (keytail == keyhead) {
    key_pending();
  }
  keyboard_buffer[keytail++] = k;
  if (keytail == keyhead) {
    sound_bell();
    keytail--;
  }
}

char_in_buffer() {
  return keyboard_buffer[keyhead];
}

chars_pending() {
  if (keytail >= keyhead) return (keytail - keyhead);
  return ((int)keytail + 256 - (int)keyhead);
}

int last_char_read;

readchar() {
  int rval;
  rval = keyboard_buffer[keyhead];
  if (keytail != keyhead) {
    keyhead++;
    if (keytail != keyhead) {
      key_pending(); /* more scan codes to look at */
    }
  }
  return last_char_read = rval;
}

pmove(row, col)
int row, col; {
  CUR_Y = row;
  CUR_X = col;  
  return 1;
}

paddch(loc)
unsigned char *loc; {
/*printf("add char %c at %d %d\n",*loc,CUR_Y,CUR_X);*/
#ifdef BIGEND
  put_char(CUR_Y, CUR_X, *(unsigned short *)loc, 0, 0); 
#endif
}

sound_bell() {
  putchar('\7'); 
  fflush(stdout);
}


