/*--------------------------------------------------------------------------
 * File: vga.c
 * Written by: Alexander Boczar, 1997-03-29
 * Description: A MCGA driver for 320x200 resolutions
 *
 * Updates:
 * -- Date -- | ----- Name ----- |-- Did what....
 * 1997-03-31 | Alexander Boczar | La till blit() funktion
 * 1997-04-26 | Alexander Boczar | Gjorde om driver till den nya standarden
 *
 -------------------------------------------------------------------------------*/

#include <conio.h>
#include <i86.h>
#include "system/xstdio.h"
#include "system/xstring.h"
#include "drivers/drv8.h"
#include "draw8/draw8.h"


/* ######################################################################### */

typedef enum
{
	ERR_NOERR,
	ERR_NOMEM,
	ERR_NOMOUSE
} ERRORS;

extern DRV vgaDRV;

/* ######################################################################### */

static BYTE *vgascr = (char *)0x0a0000;

static ERRORS error = ERR_NOERR;

static int mouseactive = FALSE;

/* ######################################################################### */

static int setup()
{
	return( TRUE);
}

static int init( int width, int height, int config)
{
	union REGS r;

	vgaDRV.width = 320;
	vgaDRV.height = 200;
	vgaDRV.minx = 0;
	vgaDRV.maxx = 319;
	vgaDRV.miny = 0;
	vgaDRV.maxy = 199;
	vgaDRV.config = config;

	vgaDRV.palette = (RGB *)xmalloc( 256 * sizeof( RGB));

	r.h.ah = 0x0;
	r.h.al = 0x13;
	int386( 0x10, &r, &r);

  vgaDRV.setpalette( NULL);

	return( TRUE);
}

static int reinit(int width, int height)
{
	if( (width != vgaDRV.height) || (height != vgaDRV.height))
		return( FALSE);
	return( TRUE);
}

static void exit()
{
	union REGS r;

  xfree( vgaDRV.palette);

  r.h.ah = 0x0;
	r.h.al = 0x03;
	int386( 0x10, &r, &r);
}

static void setpalette( RGB *newpal)
{
  int i;

  if( newpal != NULL)
    memcpy( vgaDRV.palette, newpal, sizeof(RGB) * 256);

	vgaDRV.palette[0].r = 0;
	vgaDRV.palette[0].g = 0;
	vgaDRV.palette[0].b = 0;
	vgaDRV.palette[255].r = 255;
	vgaDRV.palette[255].g = 255;
	vgaDRV.palette[255].b = 255;

	outp( 0x3c8, 0);
	for (i=0; i<256; i++)
	{
		outp( 0x3c9, vgaDRV.palette[i].r >> 2);
		outp( 0x3c9, vgaDRV.palette[i].g >> 2);
		outp( 0x3c9, vgaDRV.palette[i].b >> 2);
	}
}

static void vsync()
{
	while ( !(inp( 0x3da) & 0x08));
	while ( (inp( 0x3da) & 0x08));
}

static void clearscreen( BYTE col)
{
	memset4( vgascr, (col | (col << 8) | (col << 16) | (col << 24)), 320*200 >> 2);
}

/* ######################################################################### */

static BUFF *createbuff( int width, int height)
{
  BUFF *buff;
	int i;

	if( (buff = (BUFF *)xmalloc( sizeof(BUFF))) == NULL)
	{
		error = ERR_NOMEM;
		return( NULL);
	}

	buff->drv = &vgaDRV;

	if( (buff->image = (BYTE *)xmalloc( sizeof( BYTE) * width * height)) == NULL)
	{
		xfree( buff);
		error = ERR_NOMEM;
		return( NULL);
	}

	if( (buff->ytab = (int *)xmalloc( sizeof( int) * height)) == NULL)
	{
		xfree( buff->image);
		xfree( buff);
		error = ERR_NOMEM;
		return( NULL);
	}

	if( vgaDRV.config & DRVCFG_ZBUFFER)
	{
    if( (buff->zbuffer = (int *)xmalloc( sizeof( int) * width * height)) == NULL)
		{
			xfree( buff->ytab);
			xfree( buff->image);
			xfree( buff);
			error = ERR_NOMEM;
			return( NULL);
		}
	}

	for (i=0; i<height; i++)
		buff->ytab[i] = i * width;

	buff->width = width;
	buff->height = height;

	buff->xorigo = width/2;
	buff->yorigo = height/2;

	buff->minx = 0;
	buff->maxx = width - 1;
	buff->miny = 0;
	buff->maxy = height - 1;

	buff->lighttab = NULL;
	buff->transtab = NULL;

  memset( buff->image, 0, buff->width * buff->height);

	return( buff);
}

static void destroybuff( BUFF *buff)
{
	if( buff != NULL)
	{
		if( buff->zbuffer != NULL) xfree( buff->zbuffer);
		if( buff->ytab != NULL) xfree( buff->ytab);
		if( buff->image != NULL) xfree( buff->image);
		xfree( buff);
	}
}

static void setbuff( BUFF *buff)
{
	vgaDRV.activebuff = buff;
}
static void xcopybuff (int x1, int y1, int x2, int y2)
{
	int sxs,ixs,xlen;
	int sys,iys,ylen;
	register int i,j;
	BYTE *image;
	BYTE *screen;


	// Check boundaries...

	if ( x1 <= vgaDRV.minx)  x1 = vgaDRV.minx;
	if ( x2 >= vgaDRV.maxx) x2 = vgaDRV.maxx;

	sxs = x1;
	ixs = x1;
	xlen = x2 - x1;

	if ( y1 <= vgaDRV.miny)	y1 = vgaDRV.miny;
	if ( y2 >= vgaDRV.maxy)  y2 = vgaDRV.maxy;

	sys = y1;
	iys = y1;
	ylen = y2 - y1;


	for ( j=0; j<ylen; j++)
	{
		image = &vgaDRV.activebuff->image[ ixs + vgaDRV.activebuff->ytab[j + iys]];
		screen = &vgascr[ sxs + 320 * (j + sys)];
		for ( i=0; i<xlen; i++)
			*(screen++) = *(image++);
	}
}
static void xmovebuff (int x1, int y1, int x2, int y2,int col)
{
	int sxs,ixs,xlen;
	int sys,iys,ylen;
	register int i,j;
	BYTE *image;
	BYTE *screen;


	// Check boundaries...

	if ( x1 <= vgaDRV.minx)  x1 = vgaDRV.minx;
	if ( x2 >= vgaDRV.maxx) x2 = vgaDRV.maxx;

	sxs = x1;
	ixs = x1;
	xlen = x2 - x1;

	if ( y1 <= vgaDRV.miny)	y1 = vgaDRV.miny;
	if ( y2 >= vgaDRV.maxy)  y2 = vgaDRV.maxy;

	sys = y1;
	iys = y1;
	ylen = y2 - y1;


	for ( j=0; j<ylen; j++)
	{
		image = &vgaDRV.activebuff->image[ ixs + vgaDRV.activebuff->ytab[j + iys]];
		screen = &vgascr[ sxs + 320 * (j + sys)];
		for ( i=0; i<xlen; i++)
		{
			*(screen++) = *(image);
			*(image)=(BYTE)col;
			image++;
		}
	}

}

static void copybuff( int x, int y)
{
	BUFF *buff = vgaDRV.activebuff;

	if( (x == 0) && (y == 0) && (buff->width == vgaDRV.width) && (buff->height == vgaDRV.height))
    memcpy16( vgascr, buff->image, (vgaDRV.width * vgaDRV.height) >> 4);
	else
	{
		int sxs,ixs,xlen;
		int sys,iys,ylen;
		register int i,j;
		BYTE *image;
		BYTE *screen;

		// Check X

		if ( x >= vgaDRV.minx )
		{
			sxs = x;
			ixs = 0;
			if ( (x + buff->width) <= vgaDRV.maxx)
				xlen = buff->width;
			else
				xlen = vgaDRV.maxx - x;
		}
		else
		{
			sxs = 0;
			ixs = vgaDRV.minx - x;
			if ( (x + buff->width) <= vgaDRV.maxx)
				xlen = (x + buff->width) - vgaDRV.minx;
			else
				xlen = vgaDRV.maxx - vgaDRV.minx;
		}

		// Check Y

		if ( y >= vgaDRV.miny )
		{
			sys = y;
			iys = 0;
			if ( (y + buff->height) <= vgaDRV.maxy)
				ylen = buff->height;
			else
				ylen = vgaDRV.maxy - y;
		}
		else
		{
			sys = 0;
			iys = vgaDRV.miny - y;
			if ( (y + buff->height) <= vgaDRV.maxy)
				ylen = (y + buff->height) - vgaDRV.miny;
			else
				ylen = vgaDRV.maxy - vgaDRV.miny;
		}

		for ( j=0; j<ylen; j++)
		{
			image = &buff->image[ ixs + buff->ytab[j + iys]];
			screen = &vgascr[ sxs + 320 * (j + sys)];
			for ( i=0; i<xlen; i++)
				*(screen++) = *(image++);
		}
	}
}

static void clearbuff( BYTE col)
{
  memset4( vgaDRV.activebuff->image, (col | (col << 8) | (col << 16) | (col << 24)), (vgaDRV.activebuff->width * vgaDRV.activebuff->height) >> 2);
	if ( vgaDRV.config & DRVCFG_ZBUFFER)
    memset4( vgaDRV.activebuff->zbuffer, 0x7fffffff, (vgaDRV.activebuff->width * vgaDRV.activebuff->height * sizeof( int)) >> 2);
}

static void movebuff( int x, int y, BYTE col)
{
	copybuff( x, y);
	clearbuff( col);
}

/* ######################################################################### */

static void _plot( XYZ p, BYTE col)
{
  plot( vgaDRV.activebuff, p, col);
}

static void _line( XYZ p1, XYZ p2, BYTE col)
{
  line( vgaDRV.activebuff, p1, p2, col);
}

static void _poly( XYZ p1, XYZ p2, XYZ p3, char col )
{
  poly( vgaDRV.activebuff, p1, p2, p3, col);
}

static void _tpoly( XYZ p1, XYZ p2, XYZ p3, BYTE *data )
{
  tpoly( vgaDRV.activebuff, p1, p2, p3, data );
}

static void _blit( XYZ p, BYTE *image, int *zimage, int width, int height)
{
  blit( vgaDRV.activebuff, p, image, zimage, width, height);
}


/* ######################################################################### */

static int mouseinit( int minx, int miny, int maxx, int maxy)
{
	union REGS r;

	if( mouseactive)
	{
		r.w.ax = 0x0007;
		r.w.cx = minx;
		r.w.dx = maxx;
		int386( 0x33, &r, &r);

		r.w.ax = 0x0008;
		r.w.cx = miny;
		r.w.dx = maxy;
		int386( 0x33, &r, &r);
		return( TRUE);
	}

	r.w.ax = 0x0;
	int386( 0x33, &r, &r);
	if ( r.w.ax == 0x0ffff)
	{
		r.w.ax = 0x0007;
		r.w.cx = minx;
		r.w.dx = maxx;
		int386( 0x33, &r, &r);

		r.w.ax = 0x0008;
		r.w.cx = miny;
		r.w.dx = maxy;
		int386( 0x33, &r, &r);

		r.w.ax = 0x0004;
		r.w.cx = minx + ((maxx - minx) >> 1);
		r.w.dx = miny + ((maxy - miny) >> 1);
		int386( 0x33, &r, &r);

		mouseactive = TRUE;
		return( TRUE);
	}
	else
	{
		mouseactive = FALSE;
		error = ERR_NOMOUSE;
		return( FALSE);
	}
}

static int mousecallback( void ( *callback)( DRVMOUSE change))
{
	return( FALSE);
}

static void getmousepos( int *x, int *y)
{
	union REGS r;

	if( mouseactive)
	{
		r.w.ax = 0x0003;
		int386( 0x33, &r, &r);
		*x = (signed short)r.w.cx;
		*y = (signed short)r.w.dx;
	}
}

static void setmousepos( int x, int y)
{
	union REGS r;

	if( mouseactive)
	{
		r.w.ax = 0x0004;
		r.w.cx = x;
		r.w.dx = y;
		int386( 0x33, &r, &r);
	}
}

static void getmousediff( int *x, int *y)
{
	union REGS r;

	if( mouseactive)
	{
		r.w.ax = 0x000b;
		int386( 0x33, &r, &r);
		*x = (signed short)r.w.cx;
		*y = (signed short)r.w.dx;
	}
}

/* ######################################################################### */

static char getkey()
{
	return( getch());
}

/* ######################################################################### */

static char *geterror()
{
	ERRORS olderr = error;
	error = ERR_NOERR;
	switch( olderr)
	{
		case ERR_NOMEM: return( "Not enough memory");
		case ERR_NOMOUSE: return( "Mouse driver not found");
		default : return( "Unknown error");
	}
}

/* ######################################################################### */

static DRVMODE onlymode =
{
	320, 200
};

DRV vgaDRV =
{
	"MCGA (320x200) Driver",

	1,
	&onlymode,

	320,200,
	0,319,0,199,
	1,1,

	0,/*config*/

	NULL,

	NULL,

	setup,
	init,
	reinit,
	exit,

	setpalette,
	vsync,
	clearscreen,

	createbuff,
	destroybuff,
	setbuff,

	xcopybuff,
	xmovebuff,
	copybuff,
	clearbuff,
	movebuff,

  _plot,
  _line,
  _poly,
  _tpoly,
  _blit,

	mouseinit,
	mousecallback,
	getmousepos,
	setmousepos,
	getmousediff,

	kbhit,
	getkey,

	geterror,
};
