#include "../common/vgadrvr.h"
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <io.h>
#include <string.h>
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include "os.h"
#include "ega.h"
#include "misc.h"
#include "egafuncs.h"
#include "funcs.h"
#include "mxfuncs.h"

static u_short ega_oldmode;
static u_short vga_mode;

int scr_width, scr_height;

extern void kbd_init(void), kbd_exit(void);
extern void mouse_save(void), mouse_reset(void);
extern void tcp_int(void), tcp_noint(void);

extern int SetMode(unsigned short);
extern void VGAEnd(void);

int
VGAOpen(mode)
int mode;
{
	int fd, i;
	unsigned long len;
	char namebuf[256];
	char *vgadrvr, *fname = namebuf;
	u_char buf[128];
	struct vga_header vgahead;
	union REGS regs;
	u_char _far *cp;
	unsigned short code_seg;

	if (mode <= 0 || mode > 4) {
		fprintf(stderr, "%d: invalid mode\n", mode);
		return 1;
	}
	fname[0] = 0;
	if (vgadrvr = getenv("X11"))
		strcpy(fname, vgadrvr);
	vgadrvr = getenv("VGADRVR");
	if (vgadrvr) {
		if (vgadrvr[0] == '\\' || vgadrvr[1] == ':')
			fname = vgadrvr;
		else {
			strcat(fname, "\\");
			strcat(fname, vgadrvr);
		}
	} else
		strcat(fname, "\\vga.drv");
	fd = open(fname, O_RDONLY|O_BINARY);
	if (fd < 0) {
	    vgahead.mono_modes[0] = 0x11;
	    vgahead.mono_modes[1] = 0x6a;
	    vgahead.mono_modes[2] = 0;
	    vgahead.mono_modes[3] = 0;
	} else {
	    i = read(fd, (char *)&vgahead, sizeof vgahead);
	    if (i < sizeof vgahead) {
		fprintf(stderr, "Read %d bytes from vga\n", i);
		goto err;
	    }
	    if (vgahead.version != VGA_VERSION) {
		fprintf(stderr, "%s: wrong version %d\n",
		    fname, vgahead.version);
		goto err;
	    }
	    cp = (unsigned char _far *)SetMode;
	    code_seg = code2data(cp);
	    if (code_seg <= 0) {
		fprintf(stderr, "code2data: error %d\n", -code_seg);
		goto err;
	    }

	    ((struct overlay *)&cp)->seg = code_seg;

	    /* read the file into text space here. */
	    if ((len = vgahead.set_mode_len) != 0) {
		if (lseek(fd, vgahead.set_mode_off, SEEK_SET) == -1) {
		    perror("lseek");
		    goto err;
		}
		i = (char *)VGASetPage - (char *)SetMode;
		if (i < len) {
		    fprintf(stderr, "VGA Error: set_mode_len = %#x, space = %#x\n",
			len, i);
		    goto err;
		}
		while (len) {
		    i = min(len, sizeof buf);
		    i = read(fd, (char *)buf, i);
    readerr:
		    if (i == 0) {
			fprintf(stderr, "%s: early EOF\n", fname);
			goto err;
		    }
		    if (i < 0) {
			    perror("read");
			    goto err;
		    }
		    _fmemcpy(cp, buf, i);
		    cp += i;
		    len -= i;
		}
	    }
	    if ((len = vgahead.mono_set_page_len) != 0) {
		if (lseek(fd, vgahead.mono_set_page_off, SEEK_SET) == -1) {
		    perror("lseek");
		    goto err;
		}
#ifdef ERGO
		((struct overlay *)&cp)->off = (char *)VGASetPage - (char *)SetMode;
#else
		((struct overlay *)&cp)->off = (u_long)(char *)VGASetPage;
#endif
		i = (char *)VGAEnd - (char *)VGASetPage;
		if (i < len) {
		    fprintf(stderr, "VGA Error: set_page_len = %#x, space = %#x\n",
			len, i);
		    goto err;
		}
		while (len) {
		    i = min(len, sizeof buf);
		    i = read(fd, (char *)buf, i);
		    if (i <= 0)
			goto readerr;
		    _fmemcpy(cp, buf, i);
		    cp += i;
		    len -= i;
		}
	    }
	    close(fd);
	    delete_selector(code_seg);
	}

	vga_mode = vgahead.mono_modes[mode - 1];
	if (!vga_mode) {
	    fprintf(stderr,
		"Mono mode %d is not supported by your VGA driver.\n", mode);
	    goto err;
	}

	regs.h.ah = 0xf;
	int386(0x10, &regs, &regs);
	ega_oldmode = regs.h.al;

	i = SetMode(vga_mode);
	if (i) {
	    fprintf(stderr,
		"Mono mode %d is not supported by your VGA card.\n", mode);
	    goto err;
	}
	/* check VESA parameters */
	i = vesa_init(vga_mode);
	if (i == 0) {
	    vga_page_shift = 0;
	} else {
	    vga_page_shift = 7 - ffs(i);
	}
	return 0;
err:
	if (fd >= 0)
	    close(fd);
	return 1;
}

void
VGAClose()
{
	union REGS regs;

	regs.h.ah = 0;		/* function 0 */
	regs.h.al = ega_oldmode;
	int386(0x10, &regs, &regs);
}

#include <process.h>

int in_shell = 0;

void
fork_shell()
{
	char *comspec = getenv("COMSPEC");

	VGAClose();
	kbd_exit();
	mouse_save();

	in_shell++;

	tcp_int();
	fputs("\nType EXIT to return to the X server.\n", stdout);
	if (comspec)
		(void)system(comspec);
	else
		(void)spawnlp(P_WAIT, "COMMAND.COM", "COMMAND", 0);
	tcp_noint();

	in_shell = 0;

	/*  It worked before, so it should work now - ignore status */
	(void)SetMode(vga_mode);
	kbd_init();
	mouse_reset();
	if (vga_hires)
		SetPage();
	RedrawScreen();
}

void
mxFixPage()
{
	if (!vga_hires)
		return;
	SetPage();
}

static int
vesa_init(short mode)
{
    union REGS regs;
    struct SREGS sregs;
    short _far *vesa_buf;
    /* real-mode interrupt */
    struct rminfo {
	long	edi, esi, ebp, res;
	long	ebx, edx, ecx, eax;
	short	flags;
	short	es, ds, fs, gs, ip, cs, sp, ss;
    } rmi;
    short selector;

    segread(&sregs);

    /* DPMI call 100h allocates DOS memory */
    regs.w.ax = 0x100;
    regs.w.bx = 16;	/* VESA buf is 256 bytes (16 paragraphs). */
    int386(0x31, &regs, &regs);
    if (regs.x.cflag) {
	printf("vesa_init: allocate memory: error %d\n", regs.w.ax);
	return 0;
    }
    selector = regs.w.dx;
    vesa_buf = MK_FP(selector, 0);

    /* Now call the VESA bios interrupt with this memory. */
    memset(&rmi, 0, sizeof rmi);
    rmi.eax = 0x4f01;
    rmi.ecx = mode;
    rmi.edi = 0;
    rmi.es = regs.w.ax;
    regs.w.ax = 0x300;
    regs.h.bl = 0x10;
    regs.h.bh = 0;
    regs.w.cx = 0;
    sregs.es = FP_SEG(&rmi);
    regs.x.edi = FP_OFF(&rmi);
    int386x(0x31, &regs, &regs, &sregs);
    if ((short)rmi.eax == 0x004f) {
	mode = vesa_buf[2];
	vga_width = vesa_buf[8];
	if (vesa_buf[0] & 2) {
	    scr_width = vesa_buf[9];
	    scr_height = vesa_buf[10];
	}
    } else
	mode = 0;

    regs.w.ax = 0x101;	/* free DOS memory */
    regs.w.dx = selector;
    int386(0x31, &regs, &regs);
    if ((short)rmi.eax != 0x004f) {
	return 0;
    }
    return mode;
}
