#include <stdio.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "dosrun.h"
#include "memory.h"
#include "loader.h"

#include "config.h"

int dta_seg,dta_ofs;
static int current_drive=2;
static char path[DOS_PATH_MAXSIZE];
static int dos_version=(DOS_VER_MINOR<<8)+DOS_VER_MAJOR;
extern int exitcode;


#define ERRNO_TAB_SIZE 31
static const signed char errno_tab[ERRNO_TAB_SIZE] = {
     5, /* EPERM		 1	Operation not permitted */
     2, /* ENOENT		 2	No such file or directory */
     0, /* ESRCH		 3	No such process */
     0, /* EINTR		 4	Interrupted system call */
    23, /* EIO		 	 5	I/O error */
     0, /* ENXIO		 6	No such device or address */
     0, /* E2BIG		 7	Arg list too long */
     0, /* ENOEXEC		 8	Exec format error */
     6, /* EBADF		 9	Bad file number */
     0, /* ECHILD		10	No child processes */
     0, /* EAGAIN		11	Try again */
     8, /* ENOMEM		12	Out of memory */
     5, /* EACCES		13	Permission denied */
     0, /* EFAULT		14	Bad address */
     0, /* ENOTBLK		15	Block device required */
    32, /* EBUSY		16	Device or resource busy */
    80, /* EEXIST		17	File exists */
     0, /* EXDEV		18	Cross-device link */
     0, /* ENODEV		19	No such device */
     0, /* ENOTDIR		20	Not a directory */
     0, /* EISDIR		21	Is a directory */
     0, /* EINVAL		22	Invalid argument */
     4, /* ENFILE		23	File table overflow */
     4, /* EMFILE		24	Too many open files */
     0, /* ENOTTY		25	Not a typewriter */
    32, /* ETXTBSY		26	Text file busy */
     0, /* EFBIG		27	File too large */
     8, /* ENOSPC		28	No space left on device */
     0, /* ESPIPE		29	Illegal seek */
    19, /* EROFS		30	Read-only file system */
     5  /* EMLINK		31	Too many links */
};

int errno_u2d ( int err )
{
  if (err>ERRNO_TAB_SIZE) return 31;
  if (!errno_tab[err]) return 31;
  return errno_tab[err];
}


void path_d2u ( char *s )
{
  int max=DOS_PATH_MAXSIZE;
  char *t=path;
  if (s[1]==':') s+=2,max-=2;
  while (*s&&max--)
    if (*s>='A'&&*s<='Z') *(t++)=*(s++)+32; else
      if (*s=='\\') *(t++)='/',s++; else
        *(t++)=*(s++);
  *t='\0';
}


int check_avail_char ( int fd )
{
  struct timeval tv;
  fd_set fds;
  tv.tv_sec=tv.tv_usec=0;
  FD_ZERO(&fds);
  FD_SET(fd,&fds);
  return select(fd+1,&fds,0,0,&tv)?255:0;
}


#define INTERRUPT    /* don't delete this */
#include "int21_misc.c" /* this line must be after the later line */


int int21_emu ( void )
{
  switch (AH) {
    case 0x00 : fprintf(stderr,"Exit on int 21h AH=00h AL=??\n");
                return 0;
    case 0x4C : fprintf(stderr,"Exit on int 21h AH=4Ch AL=%02X\n",AL);
                exitcode=AL;
                return 0;
    case 0x31 : fprintf(stderr,"TSR programs are not supported by dosrun.\n");
                return 0;
    case 0x01 : read(0,&vm.regs.eax,1);
		putchar(AL);
		fflush(stdout);
		return 1;
    case 0x02 : putchar(DL);
                fflush(stdout);
		return 1;
    /* character direct I/O */
    case 0x06 : if (DL==255) {
                  if (check_avail_char(0)) {
		    read(0,&vm.regs.eax,1);
		    CLC;
		  } else STC;
		  return 1;
                }
		putchar(DL);
		fflush(stdout);
		return 1;
    /* char out without in */
    case 0x07 :
    case 0x08 : read(0,&vm.regs.eax,1);
                return 1;
    /* print '$' terminated string */
    case 0x09 : {
                  byte *p=DOS_PB(DS,DX);
		  while (*p!='$') putchar(*(p++));
		  fflush(stdout);
		  return 1;
		}
    /* get string */
    case 0x0A : {
                  byte *p=DOS_PB(DS,DX);
		  fgets(p+2,*p,stdin);
		  *(p+1)=strlen(p+2);
		  return 1;
		}
    case 0x0B : AL=check_avail_char(1);
                return 1;
    case 0x0C : if (AL!=1&&AL!=6&&AL!=7&&AL!=8&&AL!=10) return 1;
                AH=AL;
                int21_emu();   /* ugly ? ;-) */
		AH=0x0C;
		return 1;
    case 0x0D : return 1;    /* reset block devices */
    case 0x0E : if (DL<3) current_drive=DL;
                AL=3;
		return 1;
    case 0x0F : /* sily FCB functions, get lost ! */
    case 0x10 :
    case 0x11 :
    case 0x12 :
    case 0x13 :
    case 0x14 :
    case 0x15 :
    case 0x16 :
    case 0x17 :
    case 0x21 :
    case 0x22 :
    case 0x23 :
    case 0x24 :
    case 0x27 :
    case 0x28 :
    case 0x29 : AL=255;
                return 1;
    case 0x19 : AL=current_drive;
                return 1;
    case 0x1A : dta_seg=DS;
		dta_ofs=DX;
		return 1;
    /* SET interrupt vector */
    case 0x25 : {
                  word *w=(word*)((unsigned int)AL<<2);
		  *w=DX;
		  *(w+1)=DS;
		  return 1;
		}
    /* get date */
    case 0x2A : {
                  time_t t=time(NULL);
		  struct tm *tim=localtime(&t);
		  AL=tim->tm_wday;
		  CX=tim->tm_year-1980;
		  DH=tim->tm_mon;
		  DL=tim->tm_mday;
		  return 1;
		}
    /* set date and time, Linux has got its own time mechanism do not allow to screw it by a DOS program */
    case 0x2D :
    case 0x2B : AL=0;
                return 1;
    /* get time */
    case 0x2C : {
                  time_t t=time(NULL);
		  struct tm *tim=localtime(&t);
		  CH=tim->tm_hour;
		  CL=tim->tm_min;
		  DH=tim->tm_sec;
		  DL=0;
		  return 1;
		}
    /* Set verify flag */
    case 0x2E : return 1; /* not output */
    /* get DTA address */
    case 0x2F : ES=dta_seg;
                BX=dta_ofs;
		return 1;
    /* Get DOS version */
    case 0x30 : AX=dos_version;
                return 1;
    /* get/set ctrl-break checking */
    case 0x33 : if (AL==0) DL=1;
                return 1;
    /* read interrupt vector */
    case 0x35 : {
                  word *w=(word*)((unsigned int)AL<<2);
		  BX=*w;
		  ES=*(w+1);
		  return 1;
		}
    /* Get free disk space */
    case 0x36 : AX=16;    /* total dummy values !! :-( */
                BX=32768;
		CX=512;
		DX=49152;
		return 1;
    /* switch char ?! */
    case 0x37 : if (AL==0) { 
                  AL='-';
                  return 1;
		}
    /* get/set country information */
    case 0x38 : if (DX==65535) { /* set */
                  CLC;
		  return 1;
		} else { /* get */
		  byte *p=DOS_PB(DS,DX);
		  bzero(p,34);
		  *(word*)p=2; /* japan style dates : year-month-day */
		  *(p+2)='F';
		  *(p+3)='t'; /* hungarian forint */
		  *(p+7)=',';
		  *(p+9)='.';
		  *(p+11)='-';
		  *(p+13)=':';
		  *(p+15)=3;  /* X Ft */
		  *(p+16)=8;
		  *(p+17)=1;
		  *(p+22)=',';
		  return 1;
		}
    /* Make directory */
    case 0x39 : path_d2u(DOS_PB(DS,DX));
                if (mkdir(path,0777)) {
		  AX=errno_u2d(errno);
		  STC;
		} else CLC;
		return 1;
    /* Delete directory */
    case 0x3A : path_d2u(DOS_PB(DS,DX));
                if (rmdir(path)) {
		  AX=errno_u2d(errno);
		  STC;
		} else CLC;
		return 1;
    /* Chdir */
    case 0x3B : path_d2u(DOS_PB(DS,DX));
                if (chdir(path)) {
		  AX=errno_u2d(errno);
		  STC;
		} else CLC;
		return 1;
    /* Create file */
    case 0x3C : {
                  int fd;
                  path_d2u(DOS_PB(DS,DX));
                  fd=open(path,O_RDWR|O_CREAT|O_TRUNC,0777);
		  if (fd==-1) {
		    AX=errno_u2d(errno);
		    STC;
		  } else {
		    AX=fd;
		    CLC;
		  }
		  return 1;
		}
    /* Open file */
    case 0x3D : {
                  int fd,mode;
		  switch (vm.regs.eax&7) {
		    case 0  : mode=O_RDONLY; break;
		    case 1  : mode=O_WRONLY; break;
		    default : mode=O_RDWR; break;
		  }
		  path_d2u(DOS_PB(DS,DX));
		  fd=open(path,mode);
		  if (fd==-1) {
		    AX=errno_u2d(errno);
		    STC;
		  } else {
		    AX=fd;
		    CLC;
		  }
		  return 1;
		}
    /* Close file */
    case 0x3E : if (close(BX)) {
                  STC;
		  AX=errno_u2d(errno);
		} else CLC;
		return 1;
    /* Read from file */
    case 0x3F : {
                  int ret;
		  ret=read(BX,DOS_PB(DS,DX),CX);
		  if (ret==-1) {
		    STC;
		    AX=errno_u2d(errno);
		  } else {
		    CLC;
		    AX=ret;
		  }
		  return 1;
		}
    /* Write into file */
    case 0x40 : {
                  int ret;
		  ret=write(BX,DOS_PB(DS,DX),CX);
		  if (ret==-1) {
		    STC;
		    AX=errno_u2d(errno);
		  } else {
		    CLC;
		    AX=ret;
		  }
		  return 1;
		}
    /* Delete file */
    case 0x41 : if (unlink(DOS_PB(DS,DX))) {
                  STC;
		  AX=errno_u2d(errno);
		} else CLC;
		return 1;
    /* Seek file */
    case 0x42 : {
                  int ret,mode;
		  switch (AL) {
		    case 0  : mode=SEEK_SET; break;
		    case 1  : mode=SEEK_CUR; break;
		    case 2  : mode=SEEK_END; break;
		    default : AX=1;
		              STC;
		              return 1;
		  }
		  ret=lseek(BX,DOS_LONG(CX,DX),mode);
		  if (ret==-1) {
		    STC;
		    AX=errno_u2d(errno);
		  } else {
		    CLC;
		    DX=ret>>16;
		    AX=ret&0xffff;
		  }
		  return 1;
		}
    /* get/set file attributes */
    case 0x43 : switch (AL) {
                  case 0x00 : { /* get */
		                struct stat st;
				if (stat(DOS_PB(DS,DX),&st)) {
				  AX=errno_u2d(errno);
				  STC;
				} else {
				  int r=0;
				  if ((S_IWUSR&st.st_mode)==0) r|=1;
				  if (S_ISDIR(st.st_mode)) r|=16;
				  CX=r;
				  CLC;
				}
				return 1;
			      }	
		  case 0x01 : { /* set */
		                struct stat st;
				byte *p=DOS_PB(DS,DX);
				if (stat(p,&st)) {
				  AX=errno_u2d(errno);
				  STC;
				  return 1;
				}
				if (S_ISDIR(st.st_mode)) {
				  AX=5;
				  STC;
				  return 1;
				}
				if (((S_IWUSR&st.st_mode)&&(CX&1))||((!S_IWUSR&st.st_mode)&&(!CX&1))) {
				  if (st.st_mode&S_IWUSR) st.st_mode&=~S_IWUSR; else st.st_mode|=S_IWUSR;
				  if (chmod(p,st.st_mode)) {
				    AX=errno_u2d(errno);
				    STC;
				    return 1;
				  }
				}
				CLC;   /* fake : other attr changing was ok */
				return 1;
		              }
                }
		STC;          /* invalid function */
		AX=1;
		return 1;
    /* IOCTL functions */
    case 0x44 : return int21_emu_ioctl();
    /* duplicate file handle ?? */
    case 0x45 : {
                  int r=dup(BX);
		  if (r==-1) {
		    STC;
		    AX=errno_u2d(errno);
		  } else {
		    CLC;
		    AX=r;
		  }
		  return 1;
		}
    /* dup2()  ?? */
    case 0x46 : if (dup2(BX,CX)) {
                  STC;
		  AX=errno_u2d(errno);
		} else CLC;
		return 1;
    /* get current directory */
    case 0x47 : {
                  char *s=getcwd(NULL,0);
		  if (!s) {
		    STC;
		    AX=errno_u2d(errno);
		  } else {
		    char *d=DOS_PB(DS,SI),*q=s;
		    CLC;
		    s++;
		    while (*s) 
		      if (*s=='/') *(d++)='\\',s++; else *(d++)=*(s++);
		    *d='\0';
		    free(q);
		  }
		  return 1;
		}
    /* allocate RAM */
    case 0x48 : {
                  int r=dosmem_alloc(BX,prg_id);
		  if (r<0) {
		    STC;
		    AX=-r;
		  } else {
		    CLC;
		    AX=r>>4;
		  }
		  BX=dosmem_largest();
		  return 1;
		}
    /* free RAM */
    case 0x49 : {
                  int r=dosmem_free(ES<<4,prg_id);
                  if (r) {
                    STC;
		    AX=r;
		  } else CLC;
		  return 1;
	        }
    /* realloc RAM */
    case 0x4A : {
                  int r=dosmem_realloc(ES<<4,BX,prg_id);
                  if (r) {
                    STC;
		    AX=r;
		  } else CLC;
		  BX=dosmem_largest();
                  return 1;
		}
    /* find first directory entry */
    case 0x4E : int21_emu_findfirst();
                return 1;
    /* find next directory entry */
    case 0x4F : int21_emu_findnext();
                return 1;
    /* set PID (PSP address) */
    case 0x50 : return 1;  /* FAKE, return ! */
    /* memory allocation strategy */
    case 0x58 : if (AL==0) {  /* read: best fit */
                  AX=1;
		  CLC;
		  return 1;
		} else {      /* write: NOT allowed now ! */
		  STC;
		  AX=1;
		  return 1;
		}
    /* get current PSP address (PID) */
    case 0x62 : BX=prg_id;
                return 1;
    /* get double byte character */
    case 0x63 : if (AL==0) AL=0xFF;
		STC;
		return 1;
    /* country information and character captailization */
    case 0x65 : if (AL<8) {  /* pass dummy country info */
                  BX=-1;
		  DX=-1;
		  STC;
		  return 1;
		}
		break;
  } /* switch */
#ifdef CONFIG_DEBUG_UNIMPLEMENTED_FUNC
  fprintf(stderr,"INT21: unimplemented function AX=%04X BX=%04X ES=%04X DX=%04X SI=%04X\n",AX,BX,ES,DX,SI);
#endif
  STC;
  return 1;
}

#undef INTERRUPT
