#include "quakedef.h"
#ifdef SWQUAKE
#include "sw.h"




/* from http://www.delorie.com/djgpp/doc/ug/graphics/vesa.html */
typedef struct VESA_INFO
{
	unsigned char  VESASignature[4];
	unsigned short VESAVersion          __attribute__ ((packed));
	unsigned long  OEMStringPtr         __attribute__ ((packed));
	unsigned char  Capabilities[4];
	unsigned long  VideoModePtr         __attribute__ ((packed));
	unsigned short TotalMemory          __attribute__ ((packed));
	unsigned short OemSoftwareRev       __attribute__ ((packed));
	unsigned long  OemVendorNamePtr     __attribute__ ((packed));
	unsigned long  OemProductNamePtr    __attribute__ ((packed));
	unsigned long  OemProductRevPtr     __attribute__ ((packed));
	unsigned char  Reserved[222];
	unsigned char  OemData[256];
} VESA_INFO;



#include <dpmi.h>
#include <go32.h>
#include <sys/farptr.h>


static VESA_INFO vesa_info;


static int get_vesa_info()
{
	__dpmi_regs r;
	long dosbuf;
	int c;

	/* use the conventional memory transfer buffer */
	dosbuf = __tb & 0xFFFFF;

	/* initialize the buffer to zero */
	for (c=0; c<sizeof(VESA_INFO); c++)
		_farpokeb(_dos_ds, dosbuf+c, 0);

	dosmemput("VBE2", 4, dosbuf);

	/* call the VESA function */
	r.x.ax = 0x4F00;
	r.x.di = dosbuf & 0xF;
	r.x.es = (dosbuf>>4) & 0xFFFF;
	__dpmi_int(0x10, &r);

	/* quit if there was an error */
	if (r.h.ah)
		return -1;

	/* copy the resulting data into our structure */
	dosmemget(dosbuf, sizeof(VESA_INFO), &vesa_info);

	/* check that we got the right magic marker value */
	if (strncmp(vesa_info.VESASignature, "VESA", 4) != 0)
		return -1;

	/* it worked! */
	return 0;
}


typedef struct MODE_INFO
{
	unsigned short ModeAttributes       __attribute__ ((packed));
	unsigned char  WinAAttributes;
	unsigned char  WinBAttributes;
	unsigned short WinGranularity       __attribute__ ((packed));
	unsigned short WinSize              __attribute__ ((packed));
	unsigned short WinASegment          __attribute__ ((packed));
	unsigned short WinBSegment          __attribute__ ((packed));
	unsigned long  WinFuncPtr           __attribute__ ((packed));
	unsigned short BytesPerScanLine     __attribute__ ((packed));
	unsigned short XResolution          __attribute__ ((packed));
	unsigned short YResolution          __attribute__ ((packed));
	unsigned char  XCharSize;
	unsigned char  YCharSize;
	unsigned char  NumberOfPlanes;
	unsigned char  BitsPerPixel;
	unsigned char  NumberOfBanks;
	unsigned char  MemoryModel;
	unsigned char  BankSize;
	unsigned char  NumberOfImagePages;
	unsigned char  Reserved_page;
	unsigned char  RedMaskSize;
	unsigned char  RedMaskPos;
	unsigned char  GreenMaskSize;
	unsigned char  GreenMaskPos;
	unsigned char  BlueMaskSize;
	unsigned char  BlueMaskPos;
	unsigned char  ReservedMaskSize;
	unsigned char  ReservedMaskPos;
	unsigned char  DirectColorModeInfo;
	unsigned long  PhysBasePtr          __attribute__ ((packed));
	unsigned long  OffScreenMemOffset   __attribute__ ((packed));
	unsigned short OffScreenMemSize     __attribute__ ((packed));
	unsigned char  Reserved[206];
} MODE_INFO;


static MODE_INFO mode_info;


static int get_mode_info(int mode)
{
	__dpmi_regs r;
	long dosbuf;
	int c;

	/* use the conventional memory transfer buffer */
	dosbuf = __tb & 0xFFFFF;

	/* initialize the buffer to zero */
	for (c=0; c<sizeof(MODE_INFO); c++)
		_farpokeb(_dos_ds, dosbuf+c, 0);

	/* call the VESA function */
	r.x.ax = 0x4F01;
	r.x.di = dosbuf & 0xF;
	r.x.es = (dosbuf>>4) & 0xFFFF;
	r.x.cx = mode;
	__dpmi_int(0x10, &r);

	/* quit if there was an error */
	if (r.h.ah)
		return -1;

	/* copy the resulting data into our structure */
	dosmemget(dosbuf, sizeof(MODE_INFO), &mode_info);

	/* it worked! */
	return 0;
}


static int find_vesa_mode(int w, int h, int bpp)
{
	int mode_list[256];
	int number_of_modes;
	long mode_ptr;
	int c;

	/* check that the VESA driver exists, and get information about it */
	if (get_vesa_info() != 0)
		return 0;

	/* convert the mode list pointer from seg:offset to a linear address */
	mode_ptr = ((vesa_info.VideoModePtr & 0xFFFF0000) >> 12) + (vesa_info.VideoModePtr & 0xFFFF);

	number_of_modes = 0;

	/* read the list of available modes */
	while (_farpeekw(_dos_ds, mode_ptr) != 0xFFFF)
	{
		mode_list[number_of_modes] = _farpeekw(_dos_ds, mode_ptr);
		number_of_modes++;
		mode_ptr += 2;
	}

	/* scan through the list of modes looking for the one that we want */
	for (c=0; c<number_of_modes; c++)
	{
		/* get information about this mode */
		if (get_mode_info(mode_list[c]) != 0)
			continue;

		/* check the flags field to make sure this is a color graphics mode,
		* and that it is supported by the current hardware */
		if ((mode_info.ModeAttributes & 0x19) != 0x19)
			continue;

		/* check that this mode is the right size */
		if ((mode_info.XResolution != w) || (mode_info.YResolution != h))
			continue;

		/* check that there is only one color plane */
		if (mode_info.NumberOfPlanes != 1)
			continue;

		/* check that it is a packed-pixel mode (other values are used for
		* different memory layouts, eg. 6 for a truecolor resolution) */
		if (mode_info.MemoryModel != ((bpp==8)?4:6))
			continue;

		/* check that this is an 8-bit (256 color) mode */
		if (mode_info.BitsPerPixel != bpp)
			continue;

		/* if it passed all those checks, this must be the mode we want! */
		return mode_list[c];
	}

	/* oh dear, there was no mode matching the one we wanted! */
	return 0; 
}


static int set_vesa_mode(int w, int h, int bpp)
{
	__dpmi_regs r;
	int mode_number;

	/* find the number for this mode */
	mode_number = find_vesa_mode(w, h, bpp);
	if (!mode_number)
		return -1;

	/* call the VESA mode set function */
	r.x.ax = 0x4F02;
	r.x.bx = mode_number;
	__dpmi_int(0x10, &r);
	if (r.h.ah)
		return -1;

	/* it worked! */
	return 0;
}

void set_vesa_bank(int bank_number)
{
	__dpmi_regs r;

	r.x.ax = 0x4F05;
	r.x.bx = 0;
	r.x.dx = bank_number;
	__dpmi_int(0x10, &r);
}

static void copy_to_vesa_screen(char *memory_buffer, int screen_size)
{
	//FIXME: use OffScreenMemOffset if possible.

	int bank_size = mode_info.WinSize*1024;
	int bank_granularity = mode_info.WinGranularity*1024;
	int bank_number = 0;
	int todo = screen_size;
	int copy_size;

	while (todo > 0)
	{
		/* select the appropriate bank */
		set_vesa_bank(bank_number);

		/* how much can we copy in one go? */
		if (todo > bank_size)
			copy_size = bank_size;
		else
			copy_size = todo;

		/* copy a bank of data to the screen */
		dosmemput(memory_buffer, copy_size, 0xA0000);

		/* move on to the next bank of data */
		todo -= copy_size;
		memory_buffer += copy_size;
		bank_number += bank_size/bank_granularity;
	}
}


extern int nostdout;	//we flag with 0x800 to disable printfs while displaying stuff.
static qboolean videoatexitregistered;
static void videoatexit(void)
{
	if (nostdout & 0x800)
	{
		nostdout &= ~0x800;

		__dpmi_regs r;
		r.x.ax = 0x0000 | 3;
		__dpmi_int(0x10, &r);
	}
}


static unsigned int *backbuffer;
static unsigned int *depthbuffer;
static unsigned int framenumber;

//#define NORENDER

qboolean SW_VID_Init(rendererstate_t *info, unsigned char *palette)
{
	int bpp = info->bpp;
	vid.pixelwidth = info->width;
	vid.pixelheight = info->height;

	if (bpp != 32)
		bpp = 32; //sw renderer supports only this

#ifndef NORENDER
	nostdout |= 0x800;
	if (set_vesa_mode(vid.pixelwidth, vid.pixelheight, bpp) < 0)
		return false;
#endif

	if (!videoatexitregistered)
	{
		videoatexitregistered = true;
		atexit(videoatexit);
	}

	backbuffer = BZ_Malloc(vid.pixelwidth * vid.pixelheight * sizeof(*backbuffer));
	if (!backbuffer)
		return false;
	depthbuffer = BZ_Malloc(vid.pixelwidth * vid.pixelheight * sizeof(*depthbuffer));
	if (!depthbuffer)
		return false;

	return true;
}
void SW_VID_DeInit(void)
{
	BZ_Free(backbuffer);
	backbuffer = NULL;
	BZ_Free(depthbuffer);
	depthbuffer = NULL;
}
qboolean SW_VID_ApplyGammaRamps		(unsigned int rampcount, unsigned short *ramps)
{	//no gamma ramps with VESA.
	return false;
}
char *SW_VID_GetRGBInfo(int *bytestride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt)
{
	void *ret = BZ_Malloc(vid.pixelwidth*vid.pixelheight*4);
	if (!ret)
		return NULL;

	memcpy(ret, backbuffer, vid.pixelwidth*vid.pixelheight*4);
	*bytestride = vid.pixelwidth*4;
	*truevidwidth = vid.pixelwidth;
	*truevidheight = vid.pixelheight;
	*fmt = TF_BGRX32;
	return ret;
}
void SW_VID_SetWindowCaption(const char *msg)
{
}
void SW_VID_SwapBuffers(void)
{
#ifndef NORENDER
	copy_to_vesa_screen((char*)backbuffer, vid.pixelwidth*vid.pixelheight*4);
#endif
	framenumber++;
}
void SW_VID_UpdateViewport(wqcom_t *com)
{
	com->viewport.cbuf = backbuffer + vid.pixelwidth*(vid.pixelheight-1);
	com->viewport.dbuf = depthbuffer;
	com->viewport.width = vid.pixelwidth;
	com->viewport.height = vid.pixelheight;
	com->viewport.stride = -vid.pixelwidth;	//this is in pixels. which is stupid.
	com->viewport.framenum = framenumber;
}

#endif