/*
 * 6845 module
 *
 * Created 14/03/94 by James Bonfield
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <setjmp.h>

#include <X11/Xlib.h>

#include <sys/param.h>

/*
 * Use shared memory for communicating with the X server.
 * Currently only choice is to define this.
 */
/* #define USE_SHM */

#ifdef  USE_SHM
#    include <sys/ipc.h>
#    include <sys/shm.h>
#    include <X11/extensions/XShm.h>
#endif

#include "6845.h"
#include "ula.h"
#include "../defs.h"
#include "../timer.h"
#include "../memory.h"

#ifdef notdef
/*
 * The 6845 with default values for mode 7
 */

static byte r0 = 0x3f;	/*  0 horiz total */
static byte r1 = 40;	/*  1 char per line */
static byte r2 = 51 ;	/*  2 horiz sync */
static byte r3 = 0x24;	/*  3 bits 0-3 horiz sync width */
			/*    bits 4-7 vert sync time */
static byte r4 = 30;	/*  4 vert total */
static byte r5 = 2;	/*  5 vert total adjust */
static byte r6 = 25;	/*  6 vert displayed char */
static byte r7 = 27;	/*  7 very sync pos */
static byte r8 = 0x93;	/*  8 bits 0/1 interlace */
			/*        4/5 display delay */
			/*        6/7 cursor delay */
static byte r9 = 18;	/*  9 scan lines per char */
static byte r10 = 0x72;	/*  a bits 0-4 cursor start */
			/*         5   cursor type */
			/*         6   cursor blink */
static byte r11 = 19;	/*  b cursor end */
static byte r12 = 0x7c;	/*  c screen start addr hi */
static byte r13 = 0x00;	/*  d screen start addr lo */
static byte r14 = 0;	/*  e cursor pos hi */
static byte r15 = 0;	/*  f cursor pos lo */
static byte r16 = 0;	/* 10 light pen pos lo */
static byte r17 = 0;	/* 11 light pen pos hi */

#endif

/*
 * mode 0 defaults
 */

static byte r0 = 0x7f;	/* horiz total */
static byte r1 = 80;	/* char per line */
static byte r2 = 98;	/* horiz sync */
static byte r3 = 0x28;	/* bits 0-3 horiz sync width */
			/* bits 4-7 vert sync time */
static byte r4 = 38;	/* vert total */
static byte r5 = 0;	/* vert total adjust */
static byte r6 = 32;	/* vert displayed char */
static byte r7 = 34;	/* very sync pos */
static byte r8 = 0x01;	/* bits 0/1 interlace */
			/*      4/5 display delay */
			/*      6/7 cursor delay */
static byte r9 = 7;	/* scan lines per char */
static byte r10 = 67;	/* bits 0-4 cursor start */
			/*      5   cursor type */
			/*      6   cursor blink */
static byte r11 = 8;	/* cursor end */
static byte r12 = 0x30;	/* screen start addr hi */
static byte r13 = 0x00;	/* screen start addr lo */
static byte r14 = 0;	/* cursor pos hi */
static byte r15 = 0;	/* cursor pos lo */
static byte r16 = 0;	/* light pen pos lo */
static byte r17 = 0;	/* light pen pos hi */

static int ula_bit_4 = 1;
static int current_reg = 0;

static Window w;
static int scrn;
static GC gc;
static GC gc_set;
static GC gc_clear;
static int refresh_timer;
static int start_addr = 0x3000;
Display *d;

#ifdef USE_SHM
XShmSegmentInfo xshmi;
static XImage *xim;

static int screen_changed = 0;

/*
 * A vital routine - must be fast!
 */
/* ARGSUSED */
void refresh(int x) {
    /* putchar('.');fflush(stdout); */
    printf("Screen_changed=%d\n", screen_changed);
    if (screen_changed) {
	int p = ((r12-6)<<8) + r13;
	int q = ((int)(p / 80))*8;

	/* puts("putimage"); */
	if (q >= 0 && q < 256) {
	    XShmPutImage(d, w, gc, xim, 0, 0, 0, 256-q, 640, q, False);
	    XShmPutImage(d, w, gc, xim, 0, q, 0, 0, 640, 256-q, False);
	    XFlush(d);
	}

	screen_changed = 0;
    }
}
#endif

#ifdef USE_SHM
static int trans[0x5000];
#else
static int x_pos[0x5000];
static int y_pos[0x5000];
#endif

static char col[256][8];

/*
 * FIXME - use colour palette and XLookupColour() - A ULA thing...
 */
static int col_2[] = {1,2,3,4};
static int col_4[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

byte write_screen(word addr, byte val);
byte write_fe00(word addr, byte val);
byte read_fe00(word addr);
byte write_fe01(word addr, byte val);
byte read_fe01(word addr);

int GO_FOR_IT;

void read_char(){}

/*
 * Oh what a wonderful day for hacks ;-)
 */
static void init_mode() {
    static int hack = 0;
    int i, j;
    int y, c, l, addr;

    /*
     * [  fe40 = Input reg B of system via which address an 8 bit latch.
     *    bits 4-5 represent the number to be added to the start of screen
     *    address in hardware to control hardware scrolling. ]
     *
     * scrn_size[4] = {0x5000, 0x4000, 0x2800, 0x1000};
     *
     * for (i = 0; i < scrn_size[(readb(0xfe40) >> 4) & 0x3]; i++)
     */

#ifdef USE_SHM
    printf("%d x %d\n", r6, r1);
    addr = 0;
    for (y = 0; y < r6; y++) {
	for (l = 0; l < 8; l++) {
	    for (c = 0; c < r1; c++) {
		trans[l + 8*(c + r1*y)] = addr;
		addr += 8;
	    }
	}
    }
#else
    for (i = 0; i < 0x5000; i++) {
	x_pos[i] = (i % 640) & 0xfff8;
	y_pos[i] = (i % 8) + ((i / 80) & 0xfff8);
    }
#endif

    /* tmp hack - FIXME */
    switch (hack % 3) {
    case 0:
	for (i = 0; i < 255; i++) {
	    for (j = 7; j >=0; j--) {
		col[i][7-j] = colours[(i >> j) & 0x01];
	    }
	}
	break;
    case 1:
	for (i = 0; i < 255; i++) {
	    for (j = 3; j >= 0; j--) {
		col[i][6-j*2] = col[i][7-j*2] =
		    colours[(((i >> j) & 0x01) >> 0) +
			    (((i >> j) & 0x10) >> 3)];
	    }
	}
	break;
    case 2:
	for (i = 0; i < 255; i++) {
	    for (j = 1; j >= 0; j--) {
		col[i][4-j*4] = col[i][5-j*4] = col[i][6-j*4] = col[i][7-j*4] =
		    colours[(((i >> j) & 0x01) >> 0) +
			    (((i >> j) & 0x04) >> 1) + 
			    (((i >> j) & 0x10) >> 2) + 
			    (((i >> j) & 0x40) >> 3)];
	    }
	}
	break;
    }
    hack++;
	

    /*
     * ULA colour code table:
     *
     * bits               pixels and bits used to determine colour
     * ----               ----------------------------------------
     *
     * mode 0 (2 colour)
     * 7 6 5 4 3 2 1 0       7    6    5    4    3    2    1    0
     *
     * mode 1 (4 colour)
     * 7 6 5 4 3 2 1 0      73   73   62   62   51   51   40   40
     *
     * mode 2 (16 colour)
     * 7 6 5 4 3 2 1 0    7531 7531 7531 7531 6420 6420 6420 6420
     *
     * mode 3
     * Colours as mode 0, except skip 4 lines every 8 (yuk?)
     *
     * mode 4
     * mode 5
     * as modes 1 and 2 except double width pixels.
     *
     * mode 6
     * as mode 3 except double width pixels.
     *
     * mode 7
     * leave to teletext controller
     */

    GO_FOR_IT=1;
}

void init_screen() {
    int i,j;
    Window root;
    unsigned long valuemask;
    XGCValues values;
    XEvent event;

    /* add memory hooks */
    for (i = 0x3000; i < 0x8000; i++) {
	special_mem_w[i] = write_screen;
    }

    special_mem_r[0xfe00] = read_fe00;
    special_mem_w[0xfe00] = write_fe00;
    special_mem_r[0xfe01] = read_fe01;
    special_mem_w[0xfe01] = write_fe01;

    if (NULL == (d = XOpenDisplay(NULL))) {
	fprintf(stderr, "Cannot open display.\n");
	return;
    }
    
    root = DefaultRootWindow(d);
    scrn = DefaultScreen(d);

    /* create window */
    w = XCreateSimpleWindow(d, root, 0, 0, 640, 256, 0,
			    BlackPixel(d, scrn), WhitePixel(d, scrn));
    XMapRaised(d, w);

#ifdef USE_SHM
    /* create XImage - it's data is the shared memory segment */
    xim = XShmCreateImage(d, DefaultVisual(d, scrn), DefaultDepth(d, scrn),
			  ZPixmap, NULL, &xshmi, 640, 256);

    /* create shared memory */
    xshmi.shmid = shmget(IPC_PRIVATE, xim->bytes_per_line * xim->height,
			 IPC_CREAT | 0777);
    if (-1 == xshmi.shmid) {
	perror("shmget");
	exit(1);
    }

    xim->data = xshmi.shmaddr = shmat(xshmi.shmid, 0, 0);
    if ((void *)-1 == xim->data) {
	perror("shmat");
	exit(1);
    }

    xshmi.readOnly = False;
    /* See /usr/include/X11.h for "ERROR CODES".
     * Why does this return BadRequest!?
     */
    printf("XShmAttach() = %d\n", XShmAttach(d, &xshmi));

    /* remove shared memory - it'll still be available as we've got access to
     * it. But upon exit the system will tidy up for us.
     * Ooops - doesn't work on all systems, better get rid of this. (Most
     * likely some icky race condition involved).
     */
    /* shmctl(xshmi.shmid, IPC_RMID, NULL); */
#endif

    values.foreground = WhitePixel(d, scrn);
    values.background = BlackPixel(d, scrn);
    valuemask = GCForeground | GCBackground | GCFunction;

    values.function   = GXset;
    gc_set = XCreateGC(d, w, valuemask, &values);

    values.function   = GXclear;
    gc_clear = XCreateGC(d, w, valuemask, &values);

    values.function = GXcopy;
    gc = XCreateGC(d, w, valuemask, &values);

    XSelectInput(d, w, KeyPressMask | KeyReleaseMask);

    init_mode();
    init_ula(d, scrn);

#ifdef USE_SHM
    /* refresh_timer = add_timer(1000000/HZ_6845, 1000000/HZ_6845, refresh); */
#endif

    return;
}

byte write_screen(word addr, byte val) {
    register int x, y, i, j;

#ifdef USE_SHM
    addr -= 0x3000; /* start_addr; */

    memcpy(&xshmi.shmaddr[trans[addr]], col[val], 8);
    screen_changed = 1;

#else
    /*
     * XPoint set[8];
     * XPoint clr[8];
     * int nset = 0, nclr = 0;
     */

    /* mode 0 translations */
    addr -= 0x3000;
    x = x_pos[addr];
    y = y_pos[addr];
/*
    x = (addr % 640) & 0xfff8;
    y = (addr % 8) + ((addr / 80) & 0xfff8);
*/

    /* fixme - use xdrawpoints */
    for (i = 0x80; i > 0; i >>= 1) {
	if (val & i) {
	    /*
	     * clr[nclr].x = x;
	     * clr[nclr++].y = y;
	     */
	    XDrawPoint(d, w, gc_clear, x, y);
	} else {
	    /*
	     * set[nset].x = x;
	     * set[nset++].y = y;
	     */
	    XDrawPoint(d, w, gc_set, x, y);
	}

	x++;
    }

    /*
     * if (nclr)
     *     XDrawPoints(d, w, gc_clear, clr, nclr, CoordModeOrigin);
     * if (nset)
     *     XDrawPoints(d, w, gc_set,   set, nset, CoordModeOrigin);
     */

    XFlush(d);
#endif

    return 0;
}

void write_char(char c) {
    if (c == 0x7f)
	fputs("\b \b", stdout);
    else
	fputc(c, stdout);

    fflush(stdout);
}

/*
 * memory hooks
 */
byte write_fe00(word addr, byte val) {
    current_reg = val & 0x1f;

    return 0;
}

byte read_fe00(word addr) {
    return 0xff;
}

byte write_fe01(word addr, byte val) {
    switch (current_reg) {
    case 0:
	r0 = val;
	init_mode();
	break;
    case 1:
	r1 = val;
	break;
    case 2:
	r2 = val;
	break;
    case 3:
	r3 = val;
	break;
    case 4:
	r4 = val;
	break;
    case 5:
	r5 = val;
	break;
    case 6:
	r6 = val;
	break;
    case 7:
	r7 = val;
	break;
    case 8:
	r8 = val;
	break;
    case 9:
	r9 = val;
	break;
    case 10:
	r10 = val;
	break;
    case 11:
	r11 = val;
	break;
    case 12: {
	int i;

	r12 = val;
	printf("r12=%d\n", r12);

	if (0) /* Please don't optimise me out! :-) */ {
	case 13:
	    r13 = val;
	    printf("r13=%d\n", r13);
	}

	/* FIXME */
	break;

	start_addr = 8 * (r12 * 0x100 + r13);
	printf("Start address = %x\n", start_addr);
	/* FIXME - hardly the fastest method, but it's working anyhow */
	for (i = 0; i < 0x5000 /* FIXME - screen size */; i++) {
#ifdef USE_SHM
	    memcpy(&xshmi.shmaddr[trans[start_addr + i]],
		   col[readbq(start_addr + i)], 8);
#endif
	}
	break;
    }
    case 14:
	r14 = val;
	break;
    case 15:
	r15 = val;
	break;
    default:
	/* 16-17 are read only */
	break;
    }

    return 0;
}

byte read_fe01(word addr) {
    switch (current_reg) {
    case 14:
	return r14;
    case 15:
	return r15;
    case 16:
	return r16;
    case 17:
	return r17;
    default:
	/* 0-13 write only */
	return 0xff;
    }
}
