/*
 * Main MSDOS emulation code - I can see this file getting too big quite
 * fast, and will probably split it up.
 *
 * 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 <sys/vfs.h>
#include "global.h"
#include "msdos.h"
#include "vm.h"
#include "termio.h"
#include "linuxfs.h"
#include "dostype.h"
 
/* some globals */

us low_mem = 0x1000;	/* start at 4K, perhaps we should use 64K for
			   some apps, like loadfix as a switch */
us current_psp;
us dta_seg;
us dta_ofs;
char cwd[160];
	
void boot_dos(int argc, char **argv)
{
	int i;
	char s[127] = " ";

	SETIVEC(0x20,0xff01,0x20*4);
	SETIVEC(0x21,0xff01,0x21*4);

	mcb_init(low_mem);

	if (argc<2) {
		debmsg("Stupid people:- no command to run\n");
		error=80;
		return;
	}
	
	for(i = 2; i < argc; i++) {
		strcat(s, argv[i]);
		strcat(s, " ");
	}
	s[strlen(s)-1] = '\0';

	if (dos_exec(argv[1], s) < 0) {
		printf("Bad command or filename\n");
		error = 80;
		return;
	}
	
	getcwd(cwd,160);
	debmsg("DOS booted.\n\n");
}

int dos2unix(char *uni, char *dos)
{
	char drive;

	if (dos[1] == ':') {
		drive = *dos++;
		if (drive != 'C') 
			return -1;
		dos++;
	}

	for (;;) {
		switch (*dos) {
			case '\\':
				*uni++ = '/';
				break;
			case '\0':
				*uni = '\0';
				if (*--uni == '.' && *--uni != '.') *++uni = '\0';
				return 0;
			default :
				*uni++ = tolower(*dos);
		}
		dos++;
	}
}

char *wildcard(char *exp, char *dos)
{
	char name[20];
	char ext[20];
	char * cp;
	
	name[0]='\0';
	ext[0]='\0';
	cp = name;
	while (*dos) {
		switch (*dos) {
			case '*':
				strcat(cp,"????????");
				cp += 8;
				break;
			case '.':
				*cp = '\0';
				cp = ext;
				break;
			default:
				*cp++ = tolower(*dos);
		}
		dos++;
	}
	*cp = '\0';
	if (! ext[0])
		strcpy(ext,"???");
	strncpy(exp,name,8);
	strcat(exp,".");
	strncat(exp,ext,3);
	return exp;
}

int unix2dos(char *dos, char *uni)
{
	for (;;) {
		switch (*uni) {
			case '/':
				*dos++ = '\\';
				break;
			case '\0':
				*dos = '\0';
				return 0;
			default :
				*dos++ = toupper(*uni);
		}
		uni++;
	}
}

void msdos(void)
{
	char *csp, *p; 
	int c;
	int nr = HI(ax);

	emulate_iret();
Restart:
	switch(nr) {
		case 0: /* terminate */
			debmsg("Terminated.\n");
			show_regs();
			error = 10;
			break;
		case 2: /* char out */
			char_out(LO(dx),7, screen);
			/* debmsg("\t == 0x%02x\n",LO(dx)); */
			break;
		case 1: /* read and echo char */
		case 8: /* read char */
		case 7: /* read char, do not check <ctrl> C */
			debmsg("HIHI\n");
			while (ReadKeyboard(&c, WAIT) != 1);
			*(char *)&_regs.eax = c;
			if (nr == 1) char_out(c,7, screen);
			break;
		case 6: /* Direct console in/out */ /**** Needs ^C checking */
			if (LO(dx) != 0xff) {
				write(1,&_regs.edx,1);
				LO(ax) = LO(dx);
				_regs.eflags |= ZF;
				break;
			}
			if (ReadKeyboard(&c,NOWAIT) != 1) {
				_regs.eflags |= ZF;
				break;
			}
			*(char *)&_regs.eax = c;
			_regs.eflags &= ~ZF;
			break;
		case 9: /* str out */
			csp = SEG_ADR((char *), ds, dx);
			for (p = csp; *p != '$';) char_out(*p++,7, screen);
			break;
		case 0x0a: /* read string */
			csp = SEG_ADR((char *), ds, dx);
			restore_screen(screen_bitfield);
			screen_bitfield = 0;
			ReadString(((unsigned char *)csp)[0], csp +1);
			break;
		case 0x0b: /* Get stdin Status */ /**** Needs ^C checking */
			if (ReadKeyboard(&c,TEST) != 1) {
				LO(ax) = 0;
			} else {
				LO(ax) = 0xff;
			}
			break;
		case 0x0c: /* clear key buffer, do int AL */
			while (ReadKeyboard(&c, NOWAIT) == 1);
			nr = _regs.eax & 0xff;
			_regs.eax = (nr << 8) | nr;
			goto Restart;
		case 0x0d: /* Disk Reset */
			break;
		case 0x0e: /* Change Drive */
			if (LO(dx) != 2) {
				debmsg("\tDrive Changed to drive %i!!\n",LO(dx));
			}
			LO(ax) = 3;
			break;
		case 0x0f: /* FCB open */
		case 0x10: /* FCB close */
		case 0x11: /* FCB find-first */
		case 0x12: /* FCB find-next */
		case 0x13: /* FCB delete */
		case 0x14: /* FCB read */
		case 0x15: /* FCB write */
		case 0x16: /* FCB creat */
		case 0x17: /* FCB rename */
		case 0x21: /* FCB read random */
		case 0x22: /* FCB write random */
		case 0x23: /* FCB get-size */
		case 0x24: /* FCB set random number */
		case 0x27: /* FCB random block read */
		case 0x28: /* FCB random block write */
		case 0x29: /* FCB parse filename */
			debmsg("\tFCB functions not currently implemented\n");
			show_regs();
			error=1;
			break;
		case 0x18: /* "Null function for CPM compatibility" !! */
		case 0x1d: /* "Null function for CPM compatibility" !! */
		case 0x1e: /* "Null function for CPM compatibility" !! */
		case 0x20: /* "Null function for CPM compatibility" !! */
			debmsg("CPM Null function called!!!\n");
			show_regs();
			break;
		case 0x19: /* get current drive */
			/* only ONE drive - the Unix file system, so - pretend to have a HDD */
			LO(ax) = 0x2;
			break;
		case 0x1a: /* set DTA address */
			SET_DTA(_regs.ds &0xffff, _regs.edx & 0xffff);
			debmsg("\tDta = %04x:%04x\n",_regs.ds,_regs.edx);
			break;
		case 0x1b: /* statfs for current drive */
		case 0x1c: /* statfs for specific drive */
			{
			struct statfs buf;
			statfs(cwd,&buf);
			_regs.ecx = 512;
			LO(ax) = buf.f_bsize % 512;
			_regs.edx = buf.f_bavail;
			_regs.ds = _regs.ebx = 0;
			break;
			}
		case 0x1f: /* get DPB for default drive */
			LO(dx) = 2;
		case 0x32: /* get DPB for specific drive */
			debmsg("\tNot implemented\n");
			_regs.eax = 0xff;  /* invalid drive error */
			break;
		case 0x25: /* set int Vector */
			debmsg("\tWarning: int %02x, set - most int's wont work\n",LO(ax));
			SETIVEC(LO(ax),_regs.ds,_regs.edx & 0xffff);
			break;
		case 0x26: /* copy psp */
			dos_copy_psp(_regs.edx & 0xffff);
			break;
		case 0x2a: /* get DOS date */
			{
			struct tm *tp;
			long *ctime;
			
			time(ctime);
			tp = localtime(ctime);
			
			_regs.ecx = tp->tm_year;
			HI(dx) = tp->tm_mon;
			LO(dx) = tp->tm_mday;
			LO(ax) = tp->tm_wday;
			}
			break;
		case 0x2b: /* set DOS date */
			/* ignore  - WE know that the time IS right ;-) */
			if ( (_regs.ecx & 0xffff)==0x4445 ) {
				debmsg("\tDetect Desqview ..\n");
				_regs.eax = 0xff;   /* trying to detect desqview - return not present */
			} else
				_regs.eax = 0x00;
			break;
		case 0x2c: /* get DOS time */
			{
			struct tm *tp;
			long *ctime;
			
			time(ctime);
			tp = localtime(ctime);
			
			HI(cx) = tp->tm_hour;
			LO(cx) = tp->tm_min;
			HI(dx) = tp->tm_sec;
			LO(dx) = 0;
			show_regs();
			}
			break;
		case 0x2d: /* set DOS time */
			/* ignore - WE know that the time IS right */
			_regs.eax = 0x00;
			break;
		case 0x2e: /* set verify flag */
			/* do nothing */
			break;
		case 0x2f: /* get DTA address */
			GET_DTA(_regs.es, _regs.ebx);
			break;
		case 0x30: /* get DOS ver */
#if 0
			_regs.eax = 0x010b; /* version 0b.01 */
			_regs.ebx = 0xf011; /* oem = f0 , .. user = */
			_regs.ecx = 0x1111; /* 11:1111 */
#else
			_regs.eax = 0x0005;
			_regs.ebx = 0x0000;
			_regs.ecx = 0x0000;
			show_regs();
#endif
			break;
		case 0x33: /* get/set Break Flag */
			debmsg("\t\tnot implemented\n");
			break;
		case 0x35: /* get int vector */
			debmsg("\tWarning: int %02x, get - most int's wont work\n",LO(ax));
			GETIVEC(LO(ax),_regs.es,_regs.ebx);
			break;
		case 0x36: /* Get free Disk Space */
			{
			/* this will give wierd info and doesnt check for overflows */
			struct statfs buf;
			statfs(cwd,&buf);
			_regs.ecx = 512;
			_regs.eax = buf.f_bsize % 512;
			_regs.ebx = buf.f_bavail;
			_regs.edx = buf.f_blocks;
			break;
			}
		case 0x37: /* "availdev" /dev/ prefix */
			if (LO(ax)==0) {
				LO(ax) = 0;
				LO(dx) = 0x2f;
				break;
			}
			LO(ax) = 0xff;
			LO(dx) = 0;
			break;
		case 0x39: /* MKDIR */
			{
			char unixname[100];
			
			_regs.eflags &= ~CF;
			if ( dos2unix(unixname,SEG_ADR((char *),ds,dx))) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tInvalid d-name\n");
				break;
			}
			debmsg("\tMKDIR on %s\n",unixname);
			_regs.eax = mkdir(unixname,077);
			if (_regs.eax < 0) {
				_regs.eflags |= CF;
				debmsg("\tError %i\n",_regs.eax);
			}
			break;
			}
		case 0x3a: /* RMDIR */
			{
			char unixname[100];
			
			_regs.eflags &= ~CF;
			if ( dos2unix(unixname,SEG_ADR((char *),ds,dx))) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tInvalid d-name\n");
				break;
			}
			debmsg("\tRMDIR on %s\n",unixname);
			_regs.eax = rmdir(unixname);
			if (_regs.eax < 0) {
				_regs.eflags |= CF;
				debmsg("\tError %i\n",_regs.eax);
			}
			break;
			}
		case 0x3b: /* CHDIR */
			{
			char unixname[160];
			_regs.eflags &= ~CF;
			if ( dos2unix(unixname,SEG_ADR((char *),ds,dx))) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tInvalid d-name\n");
				break;
			}
			debmsg("\tchdir on %04x:%04x\n",_regs.ds,_regs.edx);
			debmsg("\tchdir to %s\n",SEG_ADR((char *),ds,dx));
			debmsg("\tCHDIR to %s\n",unixname);
			_regs.eax = chdir(unixname);
			if (_regs.eax < 0) {
				_regs.eflags |= CF;
				debmsg("\tError %i\n",_regs.eax);
				_regs.eax = 3;
			} else 
				strcpy(cwd,unixname);
			break;
			}
			
			/* Note: many of the file handling semantics for the
			 * following file functions are plain kludges that are
			 * more convinient for checking if things work, rather
			 * than emulating DOS
			 */
		case 0x3c: /* create file H */
			{
			char unixname[100];
			
			_regs.eflags &= ~CF;
			if (dos2unix(unixname,SEG_ADR((char *),ds,dx))) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tInvalid f-name!\n");
				break;
			}
			debmsg("\tCreat on %s\n",unixname);
			_regs.eax = creat(unixname,S_IREAD | S_IWRITE);
			if (_regs.eax > 0xfff0) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tFile table overflow!\n");
			}
			if (_regs.eax == -1) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tHmm: error?\n");
				show_regs();
			}
			}
			break;
		case 0x3d: /* open file H */ /**** Needs file-locking ?? */
			{
			char unixname[100];
			int flags;
			
			_regs.eflags &= ~CF;
			if (dos2unix(unixname,SEG_ADR((char *),ds,dx))) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tInvalid f-name!\n");
				break;
			}
			debmsg("\tOpen on %s\n",unixname);
			
			switch (LO(ax)&0x07) {
				case 0:
					flags = O_RDONLY;
					break;
				case 1:
					flags = O_WRONLY;
					break;
				case 2:
					flags = O_RDWR;
					break;
				default:
					debmsg("\tUnknown open mode 0x%02x\n",LO(ax)&0x07);
					flags = O_RDWR;
			}
			_regs.eax = open(unixname,flags);
			if (_regs.eax > 0xfff0) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tFile table overflow!\n");
			}
			if (_regs.eax == -1) {
				_regs.eflags |= CF;
				switch (errno) {
					case EISDIR:
					case ETXTBSY:
					case EACCES:
					case EROFS:
						_regs.eax = 5; /* Access denied */
						break;
					case ENOENT:
					case ENOTDIR:
						_regs.eax = 2; /* File not found */
						break;
					case EMFILE:
					case ENFILE:
						_regs.eax = 4; /* no handles avail */
						break;
					default:
						_regs.eax = 5;
				}
				debmsg("\tHmm: error?\n");
			}
			break;
			}
		case 0x3e: /* close file H */
			_regs.eflags &= ~CF;
			_regs.eax = close(_regs.ebx);
			if (_regs.eax) {
				debmsg("\tError closing file!\n");
				_regs.eflags |= CF;
			}
			break;
		case 0x3f: /* read from handle */
			/*debmsg("\tWarning: this may fail\n");*/
			_regs.eflags &= ~CF;
			_regs.eax = read(_regs.ebx,SEG_ADR((char *),ds,dx),_regs.ecx & 0xffff);
			if (_regs.eax == -1) {
				_regs.eflags |= CF;
				_regs.eax = 5; /* access denied */
			}
			break;
		case 0x40: /* write to handle */
			/*debmsg("\tWarning: this may fail\n");*/
			_regs.eax = write(_regs.ebx,SEG_ADR((char *),ds,dx),_regs.ecx & 0xffff);
			if (_regs.eax == -1) {
				_regs.eflags |= CF;
				_regs.eax = 5; /* access denied */
			}
			break;
		case 0x41: /* Unlink */
			{
			char unixname[100];
			
			_regs.eflags &= ~CF;
			if ( dos2unix(unixname,SEG_ADR((char *),ds,dx))) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tInvalid f-name\n");
				break;
			}
			debmsg("\tUnlink on %s\n",unixname);
			/*
			_regs.eax = unlink(unixname);
			*/
			if (_regs.eax < 0) {
				_regs.eflags |= CF;
				debmsg("\tError %i\n",_regs.eax);
			}
			}
			break;
		case 0x42: /* lseek */
			_regs.eax = lseek(_regs.ebx,(_regs.ecx << 16)+(_regs.edx & 0xffff),LO(ax));
			if (_regs.eax == -1) {
				_regs.eflags |= CF;
				_regs.eax = 1;
				break;
			}
			else
				_regs.eflags &= ~CF;
			_regs.edx = _regs.eax >> 16;
			_regs.eax &= 0xffff;
			break;
		case 0x43: /* get/set attribs */ /**** FIXME! */
			if (LO(ax) == 0) {
				debmsg("\tNot implemented\n");
				_regs.eflags &= ~CF;
				_regs.ecx = 0;
				break;
			} else {
				debmsg("\tNot implemented\n");
				_regs.eflags &= ~CF;
				_regs.eax = 2;
				break;
			}
		case 0x44: /* IOCTL */
			_regs.eax = dos_ioctl(_regs.eax, _regs.ebx, _regs.edx);
			if (_regs.eax < 0) {
				_regs.eax = -_regs.eax;
				_regs.eflags |= CF;
			}
			else {
				_regs.edx = _regs.eax;
				_regs.eax = 0;
				_regs.eflags &= ~CF;
			}
			break;
		case 0x45: /* dup */
			_regs.eflags &= ~CF;
			_regs.eax = dup(_regs.ebx & 0xffff);
			if (_regs.eax <0) {
				_regs.eflags |= CF;
				_regs.eax = 2;
			}
			break;
		case 0x46: /* dup2 */
			_regs.eflags &= ~CF;
			_regs.eax = dup2(_regs.ebx & 0xffff,_regs.ecx & 0xffff);
			if (_regs.eax <0) {
				_regs.eflags |= CF;
				_regs.eax = 2;
			}
			break;
		case 0x47: /* CWD */
			getcwd(cwd,160);
			unix2dos(SEG_ADR((char *),ds,si),cwd);
			strcpy(SEG_ADR((char *),ds,si),SEG_ADR((char*),ds,si+1));
			debmsg("\tcwd = %s\n",SEG_ADR((char *),ds,si));
			break;
		case 0x48: /* alloc mem */
			_regs.eax = dos_alloc_mem(_regs.ebx & 0xffff,
						  &_regs.ebx, TRUE);
			if (_regs.eax < 0) {
				_regs.eax = -_regs.eax;
				_regs.eflags |= CF;
			} else 
				_regs.eflags &= ~CF;
			break;
		case 0x49: /* dealloc mem */
			_regs.eax = dos_dealloc_mem(_regs.es);
			if (_regs.eax < 0) {
				_regs.eax = -_regs.eax;
				_regs.eflags |= CF;
			}
			else
				_regs.eflags &= ~CF;
			break;
		case 0x4a: /* SETBLOCK */
			_regs.eax = dos_resize_block(_regs.es,_regs.ebx & 0xffff);
			if (_regs.eax < 0) {
				_regs.eax = -_regs.eax;
				_regs.eflags |= CF;
			}
			else
				_regs.eflags &= ~CF;
			break;
		case 0x4c: /* terminate with error code */
			debmsg("Terminated.\n");
			show_regs();
			printf("\nExit code = %i\n",LO(ax));
			error=10;
			break;
		case 0x4e: /* find first */  /**** this needs fixing */
			{
			char *cp;
			char buf[80]= "./";

			_regs.eflags &= ~CF;
			if (dos2unix(findpath,SEG_ADR((char *),ds,dx))) {
				_regs.eax = 0x0f;
				_regs.eflags |= CF;
				debmsg("not a path?\n");
				break;
			}
			if (strrchr(findpath,'/')==NULL) {
				strcat(buf,findpath);
				strcpy(findpath,buf);
			}
			cp = findpath + strlen(findpath);
			while (*--cp != '/');
			cp++;
			memset(findname,'\0',sizeof(findname));
			wildcard(findname,cp);
			*cp++ = '\0';
			strncpy(DTA, "This is just some junk to fill things!",21);
			debmsg("\tFind_first %s  %x --- %s, %s\n", SEG_ADR((char *),ds,dx), _regs.ecx, findpath, findname);
			_regs.eax = dir_entry(1, _regs.ecx, (struct fentry *)DTA);
			search_mode = _regs.ecx;
			if (_regs.eax) _regs.eflags |= CF;
			}
			break;
		case 0x4f: /* find next */
			_regs.eflags &= ~CF;
			_regs.eax = dir_entry(0, search_mode, (struct fentry *)DTA);
			if (_regs.eax) _regs.eflags |= CF;
			break;
		case 0x50: /* set PSP address */
			dos_set_active_psp(_regs.ebx & 0xffff);
			break;
		case 0x51: /* get PSP address */
		case 0x62:
			_regs.ebx = dos_get_active_psp();
			break;
		case 0x54: /* get verify flag */
			/* assume that Unix has better file-writes than DOS ;-) */
			_regs.eax = 0x01;
			break;
		case 0x55: /* create new psp */
			dos_create_new_psp(_regs.edx & 0xffff, _regs.esi & 0xffff);
			_regs.eflags &= ~CF;
			break;
		case 0x56: /* Rename */
			{
			char unixname1[100];
			char unixname2[100];
			
			_regs.eflags &= ~CF;
			if ( dos2unix(unixname1,SEG_ADR((char *),ds,dx))) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tInvalid f-name\n");
				break;
			}
			if ( dos2unix(unixname2,SEG_ADR((char *),es,di))) {
				_regs.eflags |= CF;
				_regs.eax = 2;
				debmsg("\tInvalid f-name\n");
				break;
			}
			debmsg("\trename on %s to %s\n",unixname1,unixname2);
			_regs.eax = rename(unixname1,unixname2);
			if (_regs.eax < 0) {
				_regs.eflags |= CF;
				debmsg("\tError %i\n",_regs.eax);
			}
			}
			break;
		case 0x57: /* get/set file date and time */
			debmsg("\tNot implemented\n");
			_regs.eflags &= ~CF;
			_regs.ecx = _regs.ebx = 0;
			break;
		case 0x58: /* get/set memory allocation strategy */
			_regs.eax = dos_allocation_strat(_regs.eax, _regs.ebx);
			if (_regs.eax < 0) {
				_regs.eax = -_regs.eax;
				_regs.eflags |= CF;
			}
			else
				_regs.eflags &= ~CF;
			break;
		default:
			show_regs();
			error = 1;
			break;
	}
}
