#include <stdio.h>
#include <sys/time.h>	 
#include <unistd.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include "memtype.h"
#include "atari.h"
#include "6502.h"
#include "rgb.h"


/* Atari Interface for XWindows
 *  by Frank Barrus
 */


static struct xatari {
	Display	*display;
	Window	window;
	XImage	*image;
	GC	gc[256];
} xatari;


static int framecount = 1;

char *interface_name = "X-Windows interface v0.01 by Frank Barrus";

typedef struct timeval timedelta;


static int time_delta(timedelta *tlast)
{
struct timeval tv;
int tdif;
	gettimeofday(&tv,NULL);
	tdif = (tv.tv_sec-tlast->tv_sec)*1000000
		+(tv.tv_usec-tlast->tv_usec);
	*tlast = tv;
	return(tdif);
}



void interface_screen_update(void)
{
XTextItem t;
char buf[80];
static timedelta td;
static int fsec;
int fcount;
static int oldfcount;
int start;
int n,skip;
int skipmax;
int x,y;
int scpos;
static int frame = 0;
	if(++frame < framecount)
		return;
	frame = 0;
	if(sys.scale)
		skipmax = 0;
	else
		skipmax = 16;
	start = 0;
	while(start < SCHGT && !sys.scan[start].redraw)
		start++;
	n = start;
	while(1) {
		if(n >= SCHGT)
			break;
		while(n < SCHGT && sys.scan[n].redraw) {
			sys.scan[n].redraw = False;
			n++;
		}
		skip = n;
		while(skip < SCHGT && !sys.scan[skip].redraw)
			skip++;
		if(skip-n >= skipmax || skip == SCHGT)  {
			if(sys.scale) {
			    scpos = start*SCWID;
			    for(y=start; y<=n; y++)
			        for(x=0; x<SCWID; x++)
				    XFillRectangle(xatari.display,
				     xatari.window,
				     xatari.gc[sys.screen[scpos++]],
				     x*2,y*2,2,2);
			} else {
				XPutImage(xatari.display,xatari.window,
					xatari.gc[0], xatari.image,
					0,start,0,start,
					SCWID,n-start+1);
			}
			start = skip;
			n = skip;
		} else
			n = skip;
	}
	XFillRectangle(xatari.display,xatari.window,xatari.gc[0],
		0,SCHGT-11,SCWID,11);
	fcount = sys.fcount%NFRAMES;
	sprintf(buf, "frame: %5d.%2d  f/sec=%3d  eff=%3d%%",
		sys.fcount/NFRAMES, fcount, fsec, 100*fsec/NFRAMES);
	t.chars = buf;
	t.nchars = strlen(t.chars);
	t.delta = 1;
	t.font = None;
	XDrawText(xatari.display, xatari.window, xatari.gc[15],
		  0, SCHGT-1, &t, 1);
	if(fcount < oldfcount) 
		fsec = NFRAMES*1000000/time_delta(&td);
	oldfcount = fcount;
}


void interface_click(int n)
{
	if(n)
		XFillRectangle(xatari.display, xatari.window, xatari.gc[15],
			0, SCHGT-8, 8,8);
}

void interface_sound_update(void)
{
int i;
int x,v;
	for(i=0; i<4; i++) {
		x = SCWID-sys.audf[i]-1;
		v = sys.audc[i]&15;
		XFillRectangle(xatari.display, xatari.window,
			xatari.gc[i*64+16+(sys.audc[i]>>4)],x,SCHGT-v,1,v);
	}
}



static void hit_trigger(int n)
{
int xp,yp;
	xp = 64*n+32;
	yp = SCHGT+32;
	XFillArc(xatari.display,xatari.window,xatari.gc[64],
		xp-27,yp-27, 14,14,0,36000);
	XFillArc(xatari.display,xatari.window,xatari.gc[69],
		xp-25,yp-25, 10,10,0,36000);
	XFillArc(xatari.display,xatari.window,xatari.gc[67],
		xp-24,yp-24, 9,9,0,36000);
	sys.strig[n] = 1;
}

static void release_trigger(int n)
{
int xp,yp;
	xp = 64*n+32;
	yp = SCHGT+32;
	XFillArc(xatari.display,xatari.window,xatari.gc[70],
		xp-27,yp-27, 14,14,0,36000);
	XFillArc(xatari.display,xatari.window,xatari.gc[68],
		xp-25,yp-25, 10,10,0,36000);
	sys.strig[n] = 0;
}


static void move_joystick(int n, int x, int y)
{
int xp,yp;
	xp = 64*n+32;
	yp = SCHGT+32;
	XFillRectangle(xatari.display,xatari.window,xatari.gc[0],xp-28,yp-28,
			56, 56);
	if(sys.strig[n])
		hit_trigger(n);
	else
		release_trigger(n);
	if(x < -10)
		x = -16;
	else if(x > 10)
		x = 16;
	else
		x = 0;
	if(y < -10)
		y = -16;
	else if(y > 10)
		y = 16;
	else
		y = 0;
	XFillArc(xatari.display,xatari.window,xatari.gc[5],xp-10,yp-10,
		13,13,0,36000);
	XFillArc(xatari.display,xatari.window,xatari.gc[5],xp+x/2-10,yp+y/2-10,
		15,15,0,36000);
	XFillArc(xatari.display,xatari.window,xatari.gc[8],xp+x-10,yp+y-10,
		18,18,0,36000);
	sys.stick[n] = ((x<0)<<2)|((x>0)<<3)|((y>0)<<1)|(y<0);
}

static void draw_console(int n)
{
int xp;
int yp;
char *name[4] = {"START","SELECT","OPTION","RESET"};
XTextItem t;
	xp = 280;
	yp = SCHGT+50-n*16;
	if(sys.console & (1<<n)) 
		XFillRectangle(xatari.display, xatari.window, xatari.gc[31-n],
			xp, yp, 64, 12);
	else
		XFillRectangle(xatari.display, xatari.window, xatari.gc[25-n],
			xp, yp, 64, 12);
	t.chars = name[n];
	t.nchars = strlen(t.chars);
	t.delta = 1;
	t.font = None;
	XDrawText(xatari.display, xatari.window, xatari.gc[0],
		xp+32-7*t.nchars/2, yp+11, &t, 1);
}


static void press_reset(void)
{
	sys.console &= ~8;
	draw_console(3);
	XFlush(xatari.display);
	sleep(1);
	sys.console |= 8;
	draw_console(3);
	if((sys.console&7) == 0) {
		sys.console = 0;
		draw_console(2);
		draw_console(1);
		draw_console(0);
		atari_coldstart();
	} else
		atari_warmstart();
	XFlush(xatari.display);
}


static void init_controls(void)
{
int i;
	XFillRectangle(xatari.display,xatari.window,xatari.gc[44],
		0,SCHGT,SCWID,64);
	for(i=0; i<4; i++) {
		move_joystick(i,0,0);	
		draw_console(i);
	}
	XSelectInput(xatari.display, xatari.window, ButtonMotionMask|
			ButtonPressMask|ButtonReleaseMask|ExposureMask|
			KeyPressMask|KeyReleaseMask);
	XFlush(xatari.display);
}



void interface_control_update(void)
{
XEvent event;
int x,y,i;
static int stick = -1;
static int strig = -1;
int sx,sy;
static int shiftmode = 0;
int shift;
	sx = sy = 0;
	while(XCheckWindowEvent(xatari.display,xatari.window,
			ButtonPressMask|ButtonMotionMask|ExposureMask|
			ButtonReleaseMask|KeyPressMask|KeyReleaseMask,
			&event))	{
	    switch(event.type) {
	    case Expose:
		init_controls();
		break;
	    case ButtonPress:
		x = event.xbutton.x;
		y = event.xbutton.y-SCHGT;
		switch(event.xbutton.button) {
		case 1:
			if(stick >= 0)
				strig = stick;
			else {
				if(y>64) 
					break;
				if(y<0) {
					sys.penh = x/2;
					sys.penv = (y+SCHGT)/2;	
					strig = 0;
				} else {
					strig = -1;
					for(i=0; i<4; i++)
						if(x>i*64 && x<i*64+64)
							strig = i;
				}
			}
			if(strig >= 0) 
				hit_trigger(strig);
			if(x > 280 && x < 280+64) {
				if(y < 0)
					break;
				i = 3-y/16;
				if(i==3)
					press_reset();
				else {
					sys.console &= ~(1<<i);
					draw_console(i); 
				}
			}
			break;
		case 2:
			for(i=0; i<4; i++) {
				if(x>i*64 && x<i*64+64)
					stick = i;
			}
			sx = x-32-stick*64;
			sy = y-32;
			break;
		}
		break;
	    case ButtonRelease:
		x = event.xbutton.x;
		y = event.xbutton.y-SCHGT;
		switch(event.xbutton.button) {
		case 1:
			if(strig >= 0) {
				release_trigger(strig); 
				strig = -1;
			}
			if(y<0 || y>64)
				break;
			if(x > 280 && x < 280+64) {
				i = 3-y/16;
				if(i<3) {
					sys.console |= (1<<i);
					draw_console(i);
				}
			}
			break;
		case 2:
			stick = -1;
			break;
		}
		break;
	    case MotionNotify:
		x = event.xmotion.x;
		y = event.xmotion.y-SCHGT;
		if(strig >= 0) {
			sys.penh = x/2;
			sys.penv = (y+SCHGT)/2;
		}
		if(stick >= 0) {
			sx = x-32-stick*64;
			sy = y-32;
		}
		if(event.xmotion.state&Button3Mask) {
			sys.paddle[0] = x;
			sys.paddle[1] = y+SCHGT;
		}
		break;
	    case KeyPress: {
		KeySym 	key;
		char	ch;

		XLookupString(&event.xkey,&ch,1,&key,NULL);
		printf("key=%x\n", key);
		switch(key) {
		case XK_KP_1:	stick = 0; sx = -20; sy = 20; break;
		case XK_KP_2:	stick = 0; sx = 0; sy = 20; break;
		case XK_KP_3:	stick = 0; sx = 20; sy = 20; break;
		case XK_KP_4:	stick = 0; sx = -20; sy = 0; break;
		case XK_KP_6:	stick = 0; sx = 20; sy = 0; break;
		case XK_KP_7:	stick = 0; sx = -20; sy = -20; break;
		case XK_KP_8:	stick = 0; sx = 0; sy = -20; break;
		case XK_KP_9:	stick = 0; sx = 20; sy = -20; break;
		case XK_KP_0:	hit_trigger(0); break;
		case XK_F7:	sys.update_mask = 0; break;		
		case XK_F8:	sys.update_mask = 1; break;		
		case XK_F9:	sys.update_mask = 7; break;		
		case XK_F10:	sys.scale ^= 1; break;
		case XK_KP_F1:	press_reset(); break;
		case XK_KP_F2:	sys.console &= ~4; draw_console(2); break;
		case XK_KP_F3:	sys.console &= ~2; draw_console(1); break;
		case XK_KP_F4:	sys.console &= ~1; draw_console(0); break;
		case XK_F5:     SET_IRQ(0x80); break;
		case XK_F6: cpu.halt++; break;
		case XK_Shift_L: 
				sys.sk_status |= 8; 
				shiftmode |= 0x40; break;
		case XK_Control_L: case XK_Control_R:
				shiftmode |= 0x80; break;
		default:
			shift = shiftmode;
			switch(key) {
			case XK_L: case XK_l:	ch = 0; break;
			case XK_J: case XK_j:	ch = 1; break;
			case XK_semicolon: ch = 2; break;
			case XK_F1:	ch = 3; break;
			case XK_F2:	ch = 4; break;
			case XK_K: case XK_k:	ch = 5; break;
			case XK_plus:	ch = 6; break;
			case XK_asterisk: ch = 7; break;
			case XK_O: case XK_o:	ch = 8; break;
			case XK_P: case XK_p:	ch = 10; break;
			case XK_U: case XK_u:	ch = 11; break;
			case XK_Return:	ch = 12; break;
			case XK_I: case XK_i:	ch = 13; break;
			case XK_minus:	ch = 14; break;
			case XK_equal:	ch = 15; break;
			case XK_V: case XK_v:	ch = 16; break;
			case XK_Help:	ch = 17; break;
			case XK_C: case XK_c:	ch = 18; break;
			case XK_F3:	ch = 19; break;
			case XK_F4:	ch = 20; break;
			case XK_B: case XK_b:	ch = 21; break;
			case XK_X: case XK_x:	ch = 22; break;
			case XK_Z: case XK_z:	ch = 23; break;
			case XK_4:	ch = 24; break;
			case XK_3:	ch = 26; break;
			case XK_6:	ch = 27; break;
			case XK_Escape:	case XK_quoteleft:
			case XK_asciitilde:
					ch = 28; break;
			case XK_5:	ch = 29; break;
			case XK_2:	ch = 30; break;
			case XK_1:	ch = 31; break;
			case XK_comma:	ch = 32; break;
			case XK_space:	ch = 33; break;
			case XK_period:	ch = 34; break;
			case XK_N: case XK_n:	ch = 35; break;
			case XK_M: case XK_m:	ch = 37; break;
			case XK_slash:	ch = 38; break;
			case XK_Multi_key: ch = 39; break;
			case XK_R: case XK_r:	ch = 40; break;
			case XK_E: case XK_e:	ch = 42; break;
			case XK_Y: case XK_y:	ch = 43; break;
			case XK_Tab:	ch = 44; break;
			case XK_T: case XK_t:	ch = 45; break;
			case XK_W: case XK_w:	ch = 46; break;
			case XK_Q: case XK_q:	ch = 47; break;
			case XK_9:	ch = 48; break;
			case XK_0:	ch = 50; break;
			case XK_7:	ch = 51; break;
			case XK_BackSpace: ch = 52; break;
			case XK_8:	ch = 53; break;
			case XK_less:	ch = 54; break;
			case XK_greater: ch = 55; break;
			case XK_F: case XK_f:	ch = 56; break;
			case XK_H: case XK_h:	ch = 57; break;
			case XK_D: case XK_d:	ch = 58; break;
			case XK_Caps_Lock: ch = 60; break;
			case XK_G: case XK_g:	ch = 61; break;
			case XK_S: case XK_s:	ch = 62; break;
			case XK_A: case XK_a:	ch = 63; break;
			case XK_Up:  ch = 14; shift = 0x80; break;
			case XK_Down:  ch = 15; shift = 0x80; break;
			case XK_Left:  ch = 06; shift = 0x80; break;
			case XK_Right:  ch = 07; shift = 0x80; break;
			default:
				continue;
			}
			sys.keycode = ch|shift;
			SET_IRQ(0x40);
			sys.sk_status |= 0x04;
		}
		break;
	     }
	    case KeyRelease: {
		KeySym 	       key;
		char	ch;

		XLookupString(&event.xkey,&ch,1,&key,NULL);
		switch(key) {
		case XK_KP_7: case XK_KP_8: case XK_KP_9:
		case XK_KP_4:		    case XK_KP_6:
		case XK_KP_1: case XK_KP_2: case XK_KP_3:
				stick = 0; sx = 0; sy = 1; break;
		case XK_KP_0:	release_trigger(0); break;
		case XK_KP_F2:	sys.console |= 4; draw_console(2); break;
		case XK_KP_F3:	sys.console |= 2; draw_console(1); break;
		case XK_KP_F4:	sys.console |= 1; draw_console(0); break;
		case XK_Shift_L: 
				sys.sk_status &= ~8; 
				shiftmode &= ~0x40; break;
		case XK_Control_L: case XK_Control_R:
				shiftmode &= ~0x80; break;
		default:
			sys.keylast = sys.keycode;
			sys.keycode = 0xff;
			sys.sk_status &= ~0x04;
		}
	     }
	    }
	}
	if(stick >= 0 && ((sx != 0) || (sy != 0)))
		move_joystick(stick,sx,sy);
}



void interface_open(void)
{
}

void interface_close(void)
{
}


int interface_init(int argc, char **argv)
{
static int init = True;
int narg = 0;
char **argp = argv;
int i;
int screen;
int root;
int depth;
XColor xc;
XSetWindowAttributes WindowAttributes;
XGCValues GCValues;
Colormap cmap,dcmap;
char *display = NULL;
int wid = SCWID,
    hgt = SCHGT+64,
    x = 0, y = 0;

	while(argc--) {
		if(init && !strcmp(*argv,"-display")) 
			display = *(++argv);
		else if(init && !strcmp(*argv,"-geometry")) {
			XParseGeometry(*(++argv),&x,&y,&wid,&hgt);
		} else if(!strcmp(*argv,"-frame")) 
			framecount = atoi(*(++argv));
		else {
			*argp++ = *argv; narg++;
		}
		if(!strcmp(*argv++,"-h")) {
			printf("X-Windows interface options:\n");
			if(init) {
			  printf("\t-display <displayname>\tset X server\n");
			  printf("\t-geometry <geom>\tset window geometry\n");
			}
			printf("\t-frame <n>\t\trefresh every <n>th frame\n");
			return narg;
		}
	}
	if(!init)
		return narg;
	init = False;
	if(!(xatari.display = XOpenDisplay(display))) {
		fprintf(stderr, "Error: could not open display %s\n",
			XDisplayName(display));
		exit(1);
	}
	screen = DefaultScreen(xatari.display);
	depth = DefaultDepth(xatari.display,screen);
	root = RootWindow(xatari.display,screen);
	WindowAttributes.border_pixel = BlackPixel(xatari.display,screen);
	WindowAttributes.background_pixel = BlackPixel(xatari.display,screen);
	WindowAttributes.override_redirect = False;
	xatari.window = XCreateWindow(xatari.display,root,
		x,y,SCWID,SCHGT+64,2, depth, InputOutput, CopyFromParent,
		CWBackPixel|CWBorderPixel|CWOverrideRedirect,
		&WindowAttributes);
	XMapWindow(xatari.display, xatari.window);
	XFlush(xatari.display);
	dcmap = DefaultColormap(xatari.display, screen);
	cmap = XCreateColormap(xatari.display, xatari.window, 
			DefaultVisual(xatari.display, screen), AllocNone);
	xc.flags = DoRed | DoGreen | DoBlue;
	for(i=0; i<256; i++) {
		int col,lum,n;
		col = ((i+8)/16)&15;
		lum = (i+8)&15;
		if(col==0) 
			n = 3800;
		else
			n = 2125;
		xc.red = (color[col].red*(lum+7)*6)+n*lum;
		xc.green = (color[col].green*(lum+7)*6)+n*lum;
		xc.blue = (color[col].blue*(lum+7)*6)+n*lum;
		XAllocColor(xatari.display,dcmap,&xc);
		col = i/16;
		lum = i&15;
		if(col==0) 
			n = 3800;
		else
			n = 2125;
		xc.red = (color[col].red*(lum+7)*6)+n*lum;
		xc.green = (color[col].green*(lum+7)*6)+n*lum;
		xc.blue = (color[col].blue*(lum+7)*6)+n*lum;
		XAllocColor(xatari.display,cmap,&xc);
		sys.pixel[i] = xc.pixel;
		GCValues.foreground = xc.pixel;
		xatari.gc[i] = XCreateGC(xatari.display,xatari.window,
				GCForeground,&GCValues);
	}
	XSetWindowColormap(xatari.display, xatari.window, cmap);
	xatari.image = XCreateImage(xatari.display,
		DefaultVisual(xatari.display, screen),
		depth, ZPixmap, 0, (char*)sys.screen, SCWID, SCHGT, 8, 0);

	init_controls();
	return narg;
}


void interface_end(void)
{
	XDestroyWindow(xatari.display, xatari.window);
}



void	interface_map_update(uint base, int len, uint adr, int type)
{
}

void	interface_device_cmd(char type, int uint, char *cmd)
{
}

void	interface_device_param(char type, int uint, char *cmd, char *param)
{
}

