/*
 * This file contains definitions for bios interrupt routines
 *
 * Copywrite 1993, Hamish Coleman
 * 
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <termio.h>
#include <termcap.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/times.h>
#include <sys/time.h>
#include <limits.h>
#include <linux/fd.h>
#include "global.h"
#include "bios.h"
#include "termio.h"
#include "vm.h"

unsigned char trans[] = /* LATIN CHAR SET */
{	"\0\0\0\0\0\0\0\0\00\00\00\00\00\00\00\00"
	"\0\0\0\0\266\247\0\0\0\0\0\0\0\0\0\0"
	" !\"#$%&'()*+,-./"
	"0123456789:;<=>?"
	"@ABCDEFGHIJKLMNO"
	"PQRSTUVWXYZ[\\]^_"
	"`abcdefghijklmno"
	"pqrstuvwxyz{|}~ "
	"\307\374\351\342\344\340\345\347\352\353\350\357\356\354\304\305"
	"\311\346\306\364\366\362\373\371\377\326\334\242\243\245\0\0"
	"\341\355\363\372\361\321\252\272\277\0\254\275\274\241\253\273"
	"\0\0\0|++++++|+++++"
	"++++-++++++++-++"
	"+++++++++++\0\0\0\0\0"
	"\0\337\0\0\0\0\265\0\0\0\0\0\0\0\0\0"
	"\0\261\0\0\0\0\367\0\260\267\0\0\0\262\244\0"
};

void poscur(int x, int y)
{
  if ((unsigned)x >= co || (unsigned)y >= li) return;
  tputs(tgoto(cm, x, y), 1, outc);
}

void scrollup(int x0, int y0 , int x1, int y1, int l, int att)
{
	int dx, dy, x, y, ofs;
	us *sadr, *p, blank = ' ' | (att << 8);


	sadr = SCREEN_ADR(screen);
	sadr += x0 + CO * (y0 + l);
	dx = x1 - x0 +1;
	dy = y1 - y0 - l +1;
	ofs = -CO * l;
	for (y=0; y<dy; y++) {
		p = sadr;
		if (l != 0) for (x=0; x<dx; x++, p++) p[ofs] = p[0];
		else        for (x=0; x<dx; x++, p++) p[0] = blank;
		sadr += CO;
	}
	for (y=0; y<l; y++) {
		sadr -= CO;
		p = sadr;
		for (x=0; x<dx; x++, p++) *p = blank;
	}
}

void scrolldn(int x0, int y0 , int x1, int y1, int l, int att)
{
	int dx, dy, x, y, ofs;
	us *sadr, *p, blank = ' ' | (att << 8);


	sadr = SCREEN_ADR(screen);
	sadr += x0 + CO * (y1 -l);
	dx = x1 - x0 +1;
	dy = y1 - y0 - l +1;
	ofs = CO * l;
	for (y=0; y<dy; y++) {
		p = sadr;
		if (l != 0) for (x=0; x<dx; x++, p++) p[ofs] = p[0];
		else        for (x=0; x<dx; x++, p++) p[0] = blank;
		sadr -= CO;
	}
	for (y=0; y<l; y++) {
		sadr += CO;
		p = sadr;
		for (x=0; x<dx; x++, p++) *p = blank;
	}
}

void setup_bios_ints(void)
{
	int i;
	memset( (char *)0,0,0x400);			/* clear interrupt table  */
	memset( (char *)0xff000, 0xF4, 0x1000);		/* set last page to HLT   */
	 for (i=0x10; i<0x20; i++) {
		if ((i & 0xf8) == 0x60) continue;	/* not user interrupts    */
		SETIVEC(i, 0xff01, 4*i);		/* all point to last page */
	 }
}

void setup_bios(void)
{
	*(us *)0x413 = 640;	/* size of memory */
	*(char *)0x449 = 3;	/* screen mode */
	*(us *)0x44a = CO;	/* chars per line */
	*(us *)0x44c = LI;	/* lines per page */
	*(us *)0x41a = 0x1e;	/* key buf start ofs */
	*(us *)0x41c = 0x1e;	/* key buf end ofs */
	/* key buf 0x41e .. 0x43d */
}

void boot(void)
{
	char *buffer;

	buffer = (char *)0x7c00;
	memset((char *)(1), 0, 0x7bff); /* clear the first 32 k */

	setup_bios_ints();
	setup_bios();
	
	_regs.eax = _regs.ebx = _regs.edx = 0;
	_regs.ecx = 0;
	_regs.ebp = _regs.esi = _regs.edi = _regs.esp = 0;
	_regs.cs = _regs.ss = _regs.ds = _regs.es = _regs.fs = _regs.gs = 0x7c0;
	_regs.eip = 0;
	_regs.eflags = 0;
	debmsg("Bios booted\n");
}

void do_bios_ticker(void)
{
	(*(long *)0x46c)++;
}

void do_video(void)
{
	int x, y, s, i;
	char c, m;

	switch(HI(ax)) {
		case 0x0: /* define mode */
			show_regs();
			switch(LO(ax)) {
				case 0x02:
				case 0x03:
					clear_screen(screen,7);
					break;
				default:
					fprintf(fdeb,"\tUndefined video mode %d\n", _regs.eax & 0xff);
			}
			break;
		case 0x1: /* define cursor shape */
			fprintf(fdeb,"\tDefine cursor shape not implemented\n");
			break;
		case 0x2: /* set cursor pos */
			s = (_regs.ebx >> 8) & 0xff;
			x = _regs.edx & 0xff;
			y = (_regs.edx >> 8) & 0xff;
			if (s != 0) {
				fprintf(fdeb,"\tVideo setpos- page not current page!\n");
				show_regs();
				error = 1; 
				return;
			}
			if (x >= CO || y >= LI) break;
			xpos[s] = x;
			ypos[s] = y;
			if (s == screen) poscur(x, y);
			break;
		case 0x3: /* get cursor pos */
			s = (_regs.ebx >> 8) & 0xff;
			if (s != 0) {
				fprintf(fdeb,"\tVideo getpos - page not current\n");
				show_regs();
				error = 1;
				return;
			}
			_regs.edx = (ypos[s] << 8) | xpos[s];
			break;
		case 0x5: /* change page */ 
			if ((_regs.eax & 0xff) == 0) break;
			fprintf(fdeb,"\t\tVideo - page changed!!\n");
			show_regs();
			error = 1;
			return;
		case 0x6: /* scroll up */
			/* fprintf(fdeb,"scroll up %d %d %d %d, %d\n", LO(cx), HI(cx), LO(dx), HI(dx), LO(ax)); */
			/* show_regs(); */
			scrollup(LO(cx), HI(cx), LO(dx), HI(dx), LO(ax), HI(bx));
			screen_bitfield = -1;
			break;
		case 0x7: /* scroll down */
			/* fprintf(fdeb,"scroll dn %d %d %d %d, %d\n", LO(cx), HI(cx), LO(dx), HI(dx), LO(ax)); */
			/* show_regs(); */
			scrolldn(LO(cx), HI(cx), LO(dx), HI(dx), LO(ax), HI(bx));
			screen_bitfield = -1;
			break;
		case 0x9: /* set chars at cursor pos */
			m = *(char *)&_regs.ebx;
			goto andattr;
		case 0xA: /* set chars at cursor pos */
			m = 7;
		andattr:
			c = *(char *)&_regs.eax;
			s = (_regs.ebx >> 8) & 0xff;
			if (s != 0) {
				fprintf(fdeb,"\tSet chars at curs pos - not current page error\n");
				show_regs();
				error = 1;
				return;
			}
			for (i=1; i < *(us *)&_regs.ecx; i++)
				char_out(c, m, s);
			break;
		case 0xe: /* print char */ 
			char_out(*(char *)&_regs.eax,7, screen);
			break;
		case 0x0f: /* get screen mode */
			_regs.eax = (CO << 8) | 2; /* chrs per line, mode 2 */
			_regs.ebx &= ~0xff00;
			_regs.ebx |= screen << 8;
			break;
		case 0x8: /* get char */
			fprintf(fdeb,"\tVideo- get char at cursor, note - not fully implemented\n");
			s = (_regs.ebx >> 8) & 0xff;
			if (s != 0) {
				fprintf(fdeb,"\tGet chars at curs pos - not current page error\n");
				show_regs();
				error = 1;
				return;
			}
			_regs.eax = 0x0720;
			break;
		case 0xb: /* palette */
		case 0xc: /* set dot */
		case 0xd: /* get dot */
		case 0x4: /* get light pen */
			fprintf(fdeb,"\tVideo error\n");
			show_regs();
			error = 1;
			return;
		case 0x10: /* ega palette */
			switch (LO(ax)) {
				case 0x00: /* set pel reg */
				case 0x01: /* set border color */
				case 0x02: /* set all pel */
				case 0x03: /* toggle blinking enable */
				case 0x07: /* get pel reg */
				case 0x08: /* get border color */
				case 0x09: /* get all pel */
				case 0x10: /* set DAC reg */
				case 0x12: /* set block DAC reg */
				case 0x13: /* select DAC page */
				case 0x15: /* get DAC reg */
				case 0x17: /* get block DAC reg */
				case 0x1a: /* get dac page state */
				case 0x1b: /* perform grey scale summing */
					fprintf(fdeb,"\tUnsupported Video int - Set video palette\n");
					show_regs();
					return;
				default:
					goto def_video_handling;
			}
			break;
		case 0x11: /* ega character generator */
			switch (LO(ax)) {
				case 0x00: /* load user font */
				case 0x10: /* " */
				case 0x01: /* load MONO 8x14 font */
				case 0x11: /* " */
				case 0x02: /* load 8x8 font */
				case 0x12: /* " */
				case 0x03: /* set block spec */
				case 0x13: /* " */
				case 0x04: /* load 8x16 (VGA) font */
				case 0x14: /* " */
				case 0x20: /* set 8x8 graphic font */
				case 0x21: /* set graphics chars */
				case 0x22: /* ROM 8x14 */
				case 0x23: /* ROM 8x8 */
				case 0x24: /* ROM 8x16 */
				case 0x30: /* get font information */
					fprintf(fdeb,"\tUnsupported Video int - character Generator\n");
					show_regs();
					return;
				default:
					goto def_video_handling;
			}
			break;
		case 0x12: /* get ega configuration */
			switch (LO(bx)) {
				case 0x30: /* select vert resolution */
				case 0x31: /* set pel loading */
				case 0x32: /* video addressing */
				case 0x33: /* grey scale summing */
				case 0x34: /* cursor emulation */
				case 0x35: /* alternate display */
				case 0x36: /* video refresh control */
					_regs.eax &= ~0xff; /* return function not supported */
				case 0x10: /* get EGA info */
				case 0x20: /* alternate Prtsc */
				case 0x55: /* enhanced features */
					fprintf(fdeb,"\tUnsupported Video int\n");
					show_regs();
					return;
				default:
					goto def_video_handling;
			}
			break;
		case 0x1a: /* display combination code stuff */
			/* return unsupported function */
			_regs.eax = 0;
			break;
		case 0xfe: /* (topview) get video buffer */
			fprintf(fdeb,"\tTopview/Desqview: get virtual screen buffer\n");
			break;
		case 0x4f: /* vesa interrupt */
		default:
def_video_handling:
			fprintf(fdeb,"\tUnknown video int 0x%x\n", _regs.eax);
			show_regs();
			error = 1;
			break;
	}
}

void do_disk(void)
{
	unsigned int disk;
	
	disk = LO(dx);
	switch(HI(ax)) {
		case 0: /* init */
			fprintf(fdeb,"DISK init %d\n", disk);
			break;
		case 1: /* read error code */	
			_regs.eax &= ~0xff;
			fprintf(fdeb,"DISK error code\n");
			break;
		case 2: /* read */
			fprintf(fdeb,"DISK %d read \n", disk);
			_regs.eax = 0x400; /* sector not found */
			_regs.eflags |= CF;
			break;
		case 3: /* write */
			fprintf(fdeb,"DISK write\n");
			_regs.eax = 0x400; /* sector not found */
			_regs.eflags |= CF;
			break;
		case 8: /* get disk drive parameters */
			fprintf(fdeb,"disk get parameters %d\n", disk);
			_regs.edx = 0; /* no hard disks */
			_regs.ecx = 0;
			_regs.eflags &= ~CF; /* no error */
			break;
		case 0x15: /* get disk type */
			fprintf(fdeb,"Int 15\n");
			_regs.eax = 0;
			break;
		default:
			fprintf(fdeb,"Disk IO error\n");
			show_regs();
			error = 5;
			return;
	}
}

void do_comms(void)
{
	int num;

	switch(HI(ax)) {
		case 0: /* init */
			_regs.eax = 0;
			num = _regs.edx & 0xffff;
			fprintf(fdeb,"Init serial %d\n", num);
			break;
		default:
			fprintf(fdeb,"Serial error\n");
			show_regs();
			error = 5;
			return;
	}
}

void do_keyb(void)
{
	int key;
	fd_set fds;

	switch(HI(ax)) {
		case 0: /* read key code, wait */
			/* fprintf(fdeb,"get key\n"); */
			for (;;) {
				if (ReadKeyboard(&key, WAIT)) break;
			}
			_regs.eax = key;
			break;
		case 1: /* test key code */
			/* fprintf(fdeb,"test key\n"); */
			if (ReadKeyboard(&key, TEST)) {
				_regs.eflags &= ~(ZF | CF); /* key pressed */
				_regs.eax = key;
			} else {
				_regs.eflags |= ZF | CF; /* no key */
			}
			break;
		case 2: /* read key state */
			/* fprintf(fdeb,"get key state not implemented\n"); */
			_regs.eax &= ~0xff;
			FD_ZERO(&fds);
			FD_SET(kbd_fd, &fds);
			scr_tv.tv_sec = 0;
			scr_tv.tv_usec = UPDATE;
			select(kbd_fd+1, &fds, NULL, NULL, &scr_tv);
			break;
		default:
			fprintf(fdeb,"keyboard error\n");
			show_regs();
			error = 7;
			return;
	}
}

void do_printer(void)
{
	int num;

	switch(HI(ax)) {
		case 1: /* init */
			_regs.eax &= ~0xff00;
			num = _regs.edx & 0xffff;
			fprintf(fdeb,"Init printer %d\n", num);
			break;
		default:
			fprintf(fdeb,"printer error\n");
			show_regs();
			error = 8;
			return;
	}
}

void do_clock(void)
{
	static unsigned long last_ticks;
	unsigned long ticks;
	long akt_time, elapsed;

	switch(HI(ax)) {
		case 0: /* read time counter */
			time(&akt_time);
			elapsed = akt_time - start_time;
			ticks = (elapsed *182) / 10 + last_ticks;
			_regs.eax &= ~0xff; /* 0 hours */
			_regs.ecx = ticks >> 16;
			_regs.edx = ticks & 0xffff;
			/* fprintf(fdeb,"read timer %ud t=%d\n", ticks, akt_time); */
			break;
		case 1: /* write time counter */
			last_ticks = (_regs.ecx << 16) | (_regs.edx & 0xffff);
			time(&start_time);
			fprintf(fdeb,"set timer to %ud \n", last_ticks);
			break;
		case 2:
			fprintf(fdeb,"int 1a, 2 ?????\n");
			break;
		default:
			fprintf(fdeb,"timer error\n");
			show_regs();
			error = 9;
			return;
	}
}

void do_bios_int(int i)
{
	emulate_iret();  /* since we are an interrupt vector, we need to return */
	switch (i) {
		case 0x10: /* VIDEO */
			do_video();
			break;
		case 0x11: /* CONFIGURATION */
			_regs.eax = CONFIGURATION;
			if (DISKS > 0) _regs.eax |= 1 | ((DISKS -1)<<6);
			fprintf(fdeb,"Configuration read\n");
			break;
		case 0x12: /* MEMORY */
			_regs.eax = 640;
			fprintf(fdeb,"Memory tested\n");
			break;
		case 0x13: /* DISK IO */
			do_disk();
			break;
		case 0x14: /* COM IO */
			do_comms();
			break;
		case 0x15: /* Cassette */
			_regs.eflags |= CF;
			debmsg("cassette 0x%02x???\n", (_regs.eax >>8) & 0xff);
			show_regs();
			break;
		case 0x16: /* KEYBOARD */
			do_keyb();
			break;
		case 0x17: /* PRINTER */
			do_printer();
			break;
		case 0x18: /* basic */
			break;
		case 0x19: /* Boot SYSTEM */
			fprintf(fdeb," system booted\n");
			show_regs();
			_exit(80);
		case 0x1a: /* clock */
			do_clock();
			break;
		case 0x1b: /* Break */
		case 0x1c: /* Timer */
		case 0x1d: /* Screen Init */
		case 0x1e: /* Def disk params */
		case 0x1f: /* Def graphic */
		default:
			fprintf(fdeb,"Unemulated BIOS interrupt 0x%x\n",i);
			show_regs();
			error = 2;
	}
}
