 /* 
  * UAE - The Un*x Amiga Emulator
  * 
  * SVGAlib interface.
  * 
  * (c) 1995 Bernd Schmidt
  */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <vga.h>
#include <vgamouse.h>
#include <vgakeyboard.h>

#include "config.h"
#include "amiga.h"
#include "options.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"
#include "keyboard.h"
#include "xwin.h"
#include "keybuf.h"

#ifndef LINUX_SVGALIB
#error Compiling svga.c, but LINUX_SVGALIB unset. Re-edit "config.h".
#endif

#define SCODE_CURSORUP	72	/* Principal cursor keys. */
#define SCODE_CURSORLEFT	75	/* (On numeric keypad.) */
#define SCODE_CURSORRIGHT	77
#define SCODE_CURSORDOWN	80

#define SCODE_CURSORUPLEFT	71	/* Diagonal keypad keys. */
#define SCODE_CURSORUPRIGHT	73
#define SCODE_CURSORDOWNLEFT	79
#define SCODE_CURSORDOWNRIGHT 81

#define SCODE_CURSORBLOCKUP	103	/* Cursor key block. */
#define SCODE_CURSORBLOCKLEFT 105
#define SCODE_CURSORBLOCKRIGHT 106
#define SCODE_CURSORBLOCKDOWN 108

#define SCODE_KEYPAD0	82
#define SCODE_KEYPAD1	79
#define SCODE_KEYPAD2	80
#define SCODE_KEYPAD3	81
#define SCODE_KEYPAD4	75
#define SCODE_KEYPAD5	76
#define SCODE_KEYPAD6	77
#define SCODE_KEYPAD7	71
#define SCODE_KEYPAD8	72
#define SCODE_KEYPAD9	73
#define SCODE_KEYPADENTER	96
#define SCODE_KEYPADPLUS	78
#define SCODE_KEYPADMINUS	74

#define SCODE_Q		16
#define SCODE_W		17
#define SCODE_E		18
#define SCODE_R		19
#define SCODE_T		20
#define SCODE_Y		21
#define SCODE_U		22
#define SCODE_I		23
#define SCODE_O		24
#define SCODE_P		25

#define SCODE_A		30
#define SCODE_S		31
#define SCODE_D		32
#define SCODE_F		33
#define SCODE_G		34
#define SCODE_H		35
#define SCODE_J		36
#define SCODE_K		37
#define SCODE_L		38

#define SCODE_Z		44
#define SCODE_X		45
#define SCODE_C		46
#define SCODE_V		47
#define SCODE_B		48
#define SCODE_N		49
#define SCODE_M		50

#define SCODE_ESCAPE		1
#define SCODE_ENTER		28
#define SCODE_RIGHTCONTROL	97
#define SCODE_CONTROL	97
#define SCODE_RIGHTALT	100
#define SCODE_LEFTCONTROL	29
#define SCODE_LEFTALT	56
#define SCODE_SPACE		57

#define SCODE_F1		59
#define SCODE_F2		60
#define SCODE_F3		61
#define SCODE_F4		62
#define SCODE_F5		63
#define SCODE_F6		64
#define SCODE_F7		65
#define SCODE_F8		66
#define SCODE_F9		67
#define SCODE_F10		68

/* Why doesn't SVGAlib define these !??? */

#define SCODE_0 11
#define SCODE_1 2
#define SCODE_2 3
#define SCODE_3 4
#define SCODE_4 5 
#define SCODE_5 6
#define SCODE_6 7 
#define SCODE_7 8
#define SCODE_8 9
#define SCODE_9 10

#define SCODE_LEFTSHIFT 42
#define SCODE_RIGHTSHIFT 54
#define SCODE_TAB 15

#define SCODE_F11 87
#define SCODE_F12 88
#define SCODE_NEXT 81
#define SCODE_PRIOR 73
#define SCODE_BS 14
/*
#define SCODE_asciicircum 1
*/
#define SCODE_bracketleft 26
#define SCODE_bracketright 27
#define SCODE_comma 51
#define SCODE_period 52
#define SCODE_slash 53
#define SCODE_semicolon 39
#define SCODE_grave 40
#define SCODE_minus 12
#define SCODE_equal 13
#define SCODE_numbersign 41
#define SCODE_ltgt 43
#define SCODE_scrolllock 70

#define SCODE_LWIN95 125
#define SCODE_RWIN95 126
#define SCODE_MWIN95 127

static vga_modeinfo modeinfo;
static char *linear_mem = NULL;
static char pixel_buffer[3200];
char *xlinebuffer;
xcolnr xcolors[4096];

static int next_scanline;
static bool next_double;

static int scrfirstline, scrlastline;
static int prev_scrfirstline, prev_scrlastline;
static xcolnr scrbgcol;

void prepare_line(int y, bool need_double)
{
    next_scanline = y;
    next_double = need_double;
    if (linear_mem != NULL)
	xlinebuffer = linear_mem + y*modeinfo.linewidth;
    else
	xlinebuffer = pixel_buffer;
}

void flush_line(void)
{
    if (scrfirstline == -1)
	scrfirstline = next_scanline;
    scrlastline = next_scanline + 1 + (next_double != 0);
    if (linear_mem == NULL) {
	vga_drawscanline(next_scanline, pixel_buffer);
	if (next_double)
	    vga_drawscanline(next_scanline+1, pixel_buffer);
    } else {
	if (next_double)
	    memcpy (linear_mem + (next_scanline+1)*modeinfo.linewidth, 
		    linear_mem + next_scanline*modeinfo.linewidth, 
		    modeinfo.linewidth);
    }
}

void flush_screen(xcolnr bgcol)
{
    int i;
    if (scrbgcol != bgcol) {
	prev_scrfirstline = 0;
	prev_scrlastline = dont_want_aspect ? 300 : 600;
    }
    scrbgcol = bgcol;
    if (scrfirstline != -1) {
	if (prev_scrfirstline < scrfirstline || prev_scrlastline > scrlastline) {
	    char *oldxlb = xlinebuffer;
	    xlinebuffer = pixel_buffer;
	    for (i = 0; i < 796; i++)
		DrawPixel(i, bgcol);
	    xlinebuffer = oldxlb;
	}
	if (linear_mem == NULL) {
	    for (i = prev_scrfirstline; i < scrfirstline; i++)
		vga_drawscanline(i, pixel_buffer);
	    for (i = scrlastline; i < prev_scrlastline; i++)
		vga_drawscanline(i, pixel_buffer);
	} else {
	    for (i = prev_scrfirstline; i < scrfirstline; i++)
		memcpy (linear_mem + i*modeinfo.linewidth, pixel_buffer,
			modeinfo.linewidth);
	    for (i = scrlastline; i < prev_scrlastline; i++)
		memcpy (linear_mem + i*modeinfo.linewidth, pixel_buffer,
			modeinfo.linewidth);
	}
	prev_scrfirstline = scrfirstline;
	prev_scrlastline = scrlastline;
    }
    scrfirstline = scrlastline = -1;
}

static __inline__ unsigned long doMask(int p, int bits, int shift)
{
    /* p is a value from 0 to 15 (Amiga color value)
     * scale to 0..255, shift to align msb with mask, and apply mask */

    unsigned long val = p * 0x11111111UL;
    val >>= (32 - bits);
    val <<= shift;

    return val;
}

static void init_colors(void)
{
#ifdef SVGALIB_16BIT_SCREEN
    int i;
    for(i=0; i<4096; i++) {
	int r = i >> 8;
	int g = (i >> 4) & 0xF;
	int b = i & 0xF;
	xcolors[i] = (doMask(r, SVGALIB_R_WEIGHT, SVGALIB_G_WEIGHT + SVGALIB_B_WEIGHT) 
		      | doMask(g, SVGALIB_G_WEIGHT, SVGALIB_B_WEIGHT)
		      | doMask(b, SVGALIB_B_WEIGHT, 0));
    }
#endif
#ifdef SVGALIB_8BIT_SCREEN
    /* This is kind of kludgy...
     * Try to allocate as many different colors as possible. */
    int step = 5, mode = 0, count = 0, col;
    int allocated[4096];
    memset(allocated,0,sizeof allocated);
    
    do {
	int r, g, b, rs, bs, gs;
	bs = step / (mode & 4 ? 2 : 1);
	rs = step / (mode & 2 ? 2 : 1);
	gs = step / (mode & 1 ? 2 : 1);
	for(b = 0; b < 16; b += bs) {
	    for(r = 0; r < 16; r += rs) {
		for(g = 0; g < 16; g += gs) {
		    int cnr = (r << 8) + (g << 4) + b;
		    if (!allocated[cnr]) {
			if (++count == 256)
			    goto finished;
			allocated[cnr] = true;
			xcolors[cnr] = count-1;
			vga_setpalette(count-1,doMask(r, 6, 0),doMask(g, 6, 0),
				       doMask(b, 6, 0));
		    }
		}
	    }
	}
#if 0
	mode++;
	if (mode == 8) {
	    mode = 0;
	    step /= 2;
	}
#endif
	if (step > 1)
	    step++;
	step /= 2;
    } while (step);
    
    finished:
    for(col = 0; col < 4096; col++) {
	int cnr = col;
	if (!allocated[cnr]) {
	    int r = cnr >> 8;
	    int g = (cnr >> 4) & 0xF;
	    int b = cnr & 0xF;
	    int maxd = 4096,best = 0;
	    int c2;
	    for(c2 = 0; c2 < 4096; c2++)
	    	if (allocated[c2]) {
		    int r2 = c2 >> 8;
		    int g2 = (c2 >> 4) & 0xF;
		    int b2 = c2 & 0xF;
		    int rd = (r2-r)*r*2;
		    int bd = (b2-b)*b*2;
		    int gd = (g2-g)*g*2;
		    int dist = abs(rd) + abs(gd) + abs(bd) + abs(rd-gd)/3 + abs(rd-bd)/3 + abs(gd-bd)/3;
		    if (dist < maxd) {
			maxd = dist;
			best = c2;
		    }
		}
	    cnr = best;
	}
	xcolors[col] = xcolors[cnr];
    }
#endif
}

bool buttonstate[3] = { false, false, false };
int lastmx, lastmy;
bool newmousecounters = false;

static bool keystate[256];

static int scancode2amiga(int scancode)
{
    switch(scancode) {
     case SCODE_A: return AK_A;
     case SCODE_B: return AK_B;
     case SCODE_C: return AK_C;
     case SCODE_D: return AK_D;
     case SCODE_E: return AK_E;
     case SCODE_F: return AK_F;
     case SCODE_G: return AK_G;
     case SCODE_H: return AK_H;
     case SCODE_I: return AK_I;
     case SCODE_J: return AK_J;
     case SCODE_K: return AK_K;
     case SCODE_L: return AK_L;
     case SCODE_M: return AK_M;
     case SCODE_N: return AK_N;
     case SCODE_O: return AK_O;
     case SCODE_P: return AK_P;
     case SCODE_Q: return AK_Q;
     case SCODE_R: return AK_R;
     case SCODE_S: return AK_S;
     case SCODE_T: return AK_T;
     case SCODE_U: return AK_U;
     case SCODE_V: return AK_V;
     case SCODE_W: return AK_W;
     case SCODE_X: return AK_X;
     case SCODE_Y: return AK_Y;
     case SCODE_Z: return AK_Z;
	
     case SCODE_0: return AK_0;
     case SCODE_1: return AK_1;
     case SCODE_2: return AK_2;
     case SCODE_3: return AK_3;
     case SCODE_4: return AK_4;
     case SCODE_5: return AK_5;
     case SCODE_6: return AK_6;
     case SCODE_7: return AK_7;
     case SCODE_8: return AK_8;
     case SCODE_9: return AK_9;
	
     case SCODE_KEYPAD0: return AK_NP0;
     case SCODE_KEYPAD1: return AK_NP1;
     case SCODE_KEYPAD2: return AK_NP2;
     case SCODE_KEYPAD3: return AK_NP3;
     case SCODE_KEYPAD4: return AK_NP4;
     case SCODE_KEYPAD5: return AK_NP5;
     case SCODE_KEYPAD6: return AK_NP6;
     case SCODE_KEYPAD7: return AK_NP7;
     case SCODE_KEYPAD8: return AK_NP8;
     case SCODE_KEYPAD9: return AK_NP9;
	
     case SCODE_F1: return AK_F1;
     case SCODE_F2: return AK_F2;
     case SCODE_F3: return AK_F3;
     case SCODE_F4: return AK_F4;
     case SCODE_F5: return AK_F5;
     case SCODE_F6: return AK_F6;
     case SCODE_F7: return AK_F7;
     case SCODE_F8: return AK_F8;
     case SCODE_F9: return AK_F9;
     case SCODE_F10: return AK_F10;
	
     case SCODE_BS: return AK_BS;
     case SCODE_LEFTCONTROL: return AK_CTRL;
     case SCODE_RIGHTCONTROL: return AK_CTRL;
     case SCODE_TAB: return AK_TAB;
     case SCODE_LEFTALT: return AK_LALT;
     case SCODE_RIGHTALT: return AK_RALT;
     case SCODE_ENTER: return AK_RET;
     case SCODE_SPACE: return AK_SPC;
     case SCODE_LEFTSHIFT: return AK_LSH;
     case SCODE_RIGHTSHIFT: return AK_RSH;
     case SCODE_ESCAPE: return AK_ESC;
	
     case SCODE_CURSORBLOCKUP: return AK_UP;
     case SCODE_CURSORBLOCKDOWN: return AK_DN;
     case SCODE_CURSORBLOCKLEFT: return AK_LF;
     case SCODE_CURSORBLOCKRIGHT: return AK_RT;
	
     case SCODE_F11: return AK_BACKSLASH;
/*
     case SCODE_asciicircum: return AK_00;
 */
     case SCODE_bracketleft: return AK_LBRACKET;
     case SCODE_bracketright: return AK_RBRACKET;
     case SCODE_comma: return AK_COMMA;
     case SCODE_period: return AK_PERIOD;
     case SCODE_slash: return AK_SLASH;
     case SCODE_semicolon: return AK_SEMICOLON;
     case SCODE_grave: return AK_QUOTE;
     case SCODE_minus: return AK_MINUS;
     case SCODE_equal: return AK_EQUAL;
	
	/* This one turns off screen updates. */
     case SCODE_scrolllock: return AK_inhibit;

     case SCODE_RWIN95: return AK_RAMI;
     case SCODE_LWIN95: return AK_LAMI; 
	
/*#ifdef KBD_LANG_DE*/
     case SCODE_numbersign: return AK_NUMBERSIGN;
     case SCODE_ltgt: return AK_LTGT;
/*#endif*/
    }
    return -1;
}

static void my_kbd_handler(int scancode, int newstate)
{
    int akey = scancode2amiga(scancode);
    
    assert(scancode >= 0 && scancode < 0x100);
    if (scancode == SCODE_F12)
    	specialflags |= SPCFLAG_BRK;
    if (keystate[scancode] == newstate)
    	return;
    keystate[scancode] = newstate;

    if (akey == -1)
    	return;

    if (newstate == KEY_EVENTPRESS) {
	if (akey == AK_inhibit)
	    inhibit_frame ^= 1;
	else
	    record_key (akey << 1);
    } else
    	record_key ((akey << 1) | 1);
    
    /* "Affengriff" */
    if(keystate[AK_CTRL] && keystate[AK_LAMI] && keystate[AK_RAMI])
    	MC68000_reset();
}

int graphics_init(void)
{
    int i;
    int vgamode;
#ifdef SVGALIB_8BIT_SCREEN
    vgamode = SVGALIB_MODE_8;
#endif
#ifdef SVGALIB_16BIT_SCREEN
    vgamode = SVGALIB_MODE_16;
#endif
	
    vga_init();
    modeinfo = *vga_getmodeinfo (vgamode);
    if (vga_setmode(vgamode) < 0) {
	sleep(1);
	vga_setmode(TEXT);
	fprintf(stderr, "SVGAlib doesn't like my video mode. Giving up.\n");
	return 0;
    }
	
    if (modeinfo.flags & CAPABLE_LINEAR) {	
	if (vga_setlinearaddressing() != -1) {
	    linear_mem = (char *)vga_getgraphmem();
	    printf("Using linear addressing: %p.\n", linear_mem);
	}
    }
    vga_setmousesupport(1);
    mouse_init("/dev/mouse",vga_getmousetype(),10);
    if (keyboard_init() != 0)
    	abort();
    keyboard_seteventhandler(my_kbd_handler);
    keyboard_translatekeys(DONT_CATCH_CTRLC);
    init_colors();
    
    scrfirstline = scrlastline = -1;
    prev_scrfirstline = 0;
    prev_scrlastline = dont_want_aspect ? 300 : 600;
    scrbgcol = xcolors[0];
    
    buttonstate[0] = buttonstate[1] = buttonstate[2] = false;
    for(i = 0; i < 256; i++) 
	keystate[i] = false;

    lastmx = lastmy = 0; newmousecounters = false;

    mouse_setxrange(-1000,1000);
    mouse_setyrange(-1000,1000);
    mouse_setposition(0,0);
    
    return 1;
}

void graphics_leave(void)
{
    sleep(1); /* Maybe this will fix the "screen full of garbage" problem */
    vga_setmode(TEXT);
    keyboard_close();
}

void handle_events(void)
{
    int button = mouse_getbutton();
    
    keyboard_update();
    mouse_update();
    lastmx += mouse_getx();
    lastmy += mouse_gety();
    mouse_setposition(0,0);

    buttonstate[0] = button & 4;
    buttonstate[1] = button & 2;
    buttonstate[2] = button & 1;
}

bool debuggable(void)
{
    return false;
}

bool needmousehack(void)
{
    return false;
}

void LED(int on)
{
}
