/*
 * GRX driver for VOGL c1993 by Gary Murphy (garym@virtual.rose.utoronto.ca)
 * 
 * To compile:
 * 
 * 1) add GRX to device.c and mash-up your makefiles for MsDOS 2) compile with
 * DOBJ=-DPOSTSCRIPT -DHPGL -DGRX and MFLAGS=-O2
 * 
 * To run:
 * 
 * set VDEVICE=grx
 * 
 * grateful thanks to Lance Norskog (thinman@netcom.com) and Bernie Kirby
 * (bernie@ecr.mu.oz.au) --- should either of you be in my neighbourhood, my
 * offer of an Ice Beer is still open! (Things in #ifdef BLART disabled by
 * bernie...)
 */
#undef VOGLE

#include <stdio.h>
#undef DBG
#ifdef DBG
FILE	*dfp = NULL;
#endif
#include <assert.h>

#include <stdlib.h>
#include <memory.h>
#include <grx.h>
#include <mousex.h>

#define MSG( m ) fprintf(stderr, "\n%s: %d: %s", __FILE__, __LINE__, (m))
#define ERROR1( m, p ) fprintf(stderr, "\n%s: %d: " m, __FILE__, __LINE__, (p))

#ifdef VOGLE
#include	"vogle.h"
#else
#include	"vogl.h"
#endif

#ifndef TRUE
#define TRUE	1
#endif

#ifndef FALSE
#define FALSE	0
#endif

#define MAXCOLOR 256

static struct {

	GR_graphics_modes old_mode;

	int             width, height, planes;
	unsigned        scrsize;/* size of buffer in long words */
	GrContext      *cbuf;	/* current context */
	GrContext      *fbuf;
	GrContext      *bbuf;

	int             palette[8];

	GrLineOption    lopt;	/* pen drawing options */
	int             fg;	/* foreground/background colours */
	int             bg;

	int             has_mouse;

	GrFont         *font;	/* Current font */
	GrFont         *lfont;	/* Preloaded small font */
	GrFont         *sfont;	/* Preloaded large font */
	char           *fname;	/* Fontname */
	GrTextOption   to;	/* Other text stuff */

	int             cx;
	int             cy;

} grx;

/*
 * access functions: *
 * 
/* I'm going to need this to fudge in stereo graphics ...
 */

GrContext * setBackBuffer(GrContext * newBB)
{
	GrContext      *oldBB = grx.bbuf;
	assert(newBB != NULL);

	grx.bbuf = newBB;
	return oldBB;
}

static int grx_init(void)
{
#ifdef DBG
	dfp = fopen("grx.dbg", "w");
#endif
	grx.old_mode = GrCurrentMode();
	GrSetMode(GR_default_graphics);

#ifndef VOGLE
	vdevice.devname = "Grx";
#endif

	/* set the VOGL device */
	vdevice.sizeX = GrSizeY();	/* square max, was GrScreenX(); */
	vdevice.sizeY = GrSizeY();

	grx.width = vdevice.sizeSx = GrScreenX();
	grx.height = vdevice.sizeSy = GrScreenY();
	grx.planes = vdevice.depth = GrNumPlanes();

	grx.scrsize = (GrPlaneSize(grx.width, grx.height) * grx.planes) / sizeof(long);

	/* setup default palette */
	GrSetRGBcolorMode();
	grx.lopt.lno_color = grx.fg = GrWhite();
	grx.bg = GrBlack();

	grx.palette[BLACK] = GrAllocColor(0, 0, 0);
	grx.palette[RED] = GrAllocColor(255, 0, 0);
	grx.palette[GREEN] = GrAllocColor(0, 255, 0);
	grx.palette[YELLOW] = GrAllocColor(255, 255, 0);
	grx.palette[BLUE] = GrAllocColor(0, 0, 255);
	grx.palette[MAGENTA] = GrAllocColor(255, 0, 255);
	grx.palette[CYAN] = GrAllocColor(0, 255, 255);
	grx.palette[WHITE] = GrAllocColor(255, 255, 255);

	/*
	 * setup back/front buffers: frontbuffer is the current screen, back
	 * is a ram context
	 */
	grx.cbuf = grx.fbuf = GrSaveContext(NULL);
	grx.bbuf = NULL;

	/* initialize mouse */
	if ((grx.has_mouse = MouseDetect()) == TRUE) {
		/* dare I do interrupts? ... */
		MouseEventMode(1);
		MouseInit();

		/* no keyboard (use getch) */
		MouseEventEnable(0, 1);

		/* cheezy mouse speed algorithm (blame Lance for the pun) */
		if (grx.width * grx.height < 100000)
			MouseSetSpeed(6);
		else if (grx.width * grx.height < 200000)
			MouseSetSpeed(4);
		else if (grx.width * grx.height < 500000)
			MouseSetSpeed(3);
		else
			MouseSetSpeed(2);

		MouseWarp(1, 1);
		MouseDisplayCursor();
	};

	/* initial drawing style to thin solid lines */
	grx.lopt.lno_width = 1;
	grx.lopt.lno_pattlen = 0;
	grx.lopt.lno_dashpat = NULL;
	/* load initial fonts */
	if (getenv("GRXFONT") == NULL)
		GrSetFontPath("fonts");

	grx.font = grx.sfont = GrLoadFont(vdevice.dev.small);
#ifdef DBG
	if (!grx.font) {
		fprintf(dfp, "GrLoadFont failed");
		fflush(dfp);
	}
#endif

	grx.lfont = GrLoadFont(vdevice.dev.large);
#ifdef DBG
	if (!grx.lfont) {
		fprintf(dfp, "GrLoadFont failed");
		fflush(dfp);
	}
#endif

	grx.to.txo_font = grx.font;
	grx.to.txo_xmag = grx.to.txo_ymag = 1;
	grx.to.txo_direct = GR_TEXT_RIGHT;
	grx.to.txo_xalign = grx.to.txo_xalign = GR_ALIGN_DEFAULT;
	grx.to.txo_fgcolor = 0;

	grx.fname = vdevice.dev.small;
	vdevice.hwidth = 8.0;
	vdevice.hheight = 8.0;

	return (1);
}

/*
 * grx_frontbuffer, grx_backbuffer, grx_swapbuffers
 * 
 */
static int grx_frontbuffer(void)
{
	grx.cbuf = grx.fbuf;
	GrSetContext(grx.fbuf);
	return (0);
}

static int grx_backbuffer(void)
{
	/* if they want a backbuffer, we'd better make one ... */

	if (grx.bbuf == NULL)
		grx.bbuf = GrCreateContext(GrSizeX(), GrSizeY(), NULL, NULL);

	assert(grx.bbuf != NULL);

	grx.cbuf = grx.bbuf;
	GrSetContext(grx.bbuf);
	return (0);
}

static int grx_swapbuffers(void)
{
	if (grx.cbuf == grx.fbuf)
		grx_backbuffer();
	else {
		/*
		 * there are rumours of a portable VGA backbuffer using VESA
		 * but I've yet to track it down.
		 * 
		 * the following copies by long words from back to front buffer
		 * modify this for regions by triming the first x-limit and
		 * y-limit and upping the pointers to the start of your
		 * subcontext
		 */

		MouseEraseCursor();

		/* WARNING WILL ROBINSON - WARNING WILL ROBINSON */
		/*
		 * We're using the NC version so I can copy a 2W by H/2
		 * backbuffer in my stereo graphics 'interlaced' mode
		 */

		GrBitBltNC(grx.fbuf, 0, 0,
			   grx.bbuf, 0, 0,
			   grx.bbuf->gc_xmax, grx.bbuf->gc_ymax, GrWRITE);

		MouseDisplayCursor();


	}

	return (0);
}

#ifdef VOGLE
/*
 * grx_vclear
 * 
 * Clear the screen to current colour
 */
int grx_vclear(void)
{
	grx.to.txo_bgcolor = grx.fg;
	GrClearContext(grx.fg);

}

#else

/*
 * grx_vclear
 * 
 * Clear the viewport to current colour
 */
static int grx_vclear(void)
{
	unsigned int    vw = vdevice.maxVx - vdevice.minVx;
	unsigned int    vh = vdevice.maxVy - vdevice.minVy;

	grx.to.txo_bgcolor = grx.fg;
	if ((vdevice.sizeSx == vw) && (vdevice.sizeSy == vh)) {
		GrClearContext(grx.fg);	/* full screen */
	} else
		GrFilledBox(
			    vdevice.minVx,
			    vdevice.sizeSy - vdevice.maxVy,
			    grx.width,
			    grx.height,
			    grx.fg);

	return (1);
}

#endif

/*
 * grx_exit
 * 
 * Sets the display back to text mode.
 */
static grx_exit(void)
{
	MouseUnInit();		/* disable mouse/keyboard interrupts */

	GrSetMode(grx.old_mode);
	GrDestroyContext(grx.bbuf);

	return (1);
}

static int grx_sync(void)
{
};

static int noop(void)
{
	return (-1);
}

/*
 * grx_font : load either of the fonts
 */
static int grx_font(char *name)
{
#ifdef DBG
	fprintf(dfp, "fontname=%s\n", name);
	fflush(dfp);
#endif
	/*
	 * Hacky way to quicky test for small or large font
	 * ... see of they are the same pointers.... this
	 * assumes that they have been called from the main
	 * library routine with *vdevice.Vfont(vdevice.smallfont);
	 */
	if (name == vdevice.dev.small) {
		grx.font = grx.sfont;
		grx.to.txo_font = grx.font;
		vdevice.hheight = grx.font->fnt_height;
		vdevice.hwidth = grx.font->fnt_width;
		grx.fname = name;
#ifdef DBG
		fprintf(dfp, "w, h: %f %f\n", vdevice.hheight, vdevice.hwidth);
		fflush(dfp);
#endif
		return(1);
	} else if (name == vdevice.dev.large) {
		grx.font = grx.lfont;
		grx.to.txo_font = grx.font;
		vdevice.hheight = grx.font->fnt_height;
		vdevice.hwidth = grx.font->fnt_width;
		grx.fname = name;
#ifdef DBG
		fprintf(dfp, "w, h: %f %f\n", vdevice.hheight, vdevice.hwidth);
		fflush(dfp);
#endif
		return(1);
	} else

	/* 
	 * It must be a completely different font (ala vogle possibility).
	 */
	if (strcmp(name, grx.fname)) {
		if (grx.font != grx.sfont && grx.font != grx.lfont)
			GrUnloadFont(grx.font);

		if (grx.fname) {
			free(grx.fname);
			grx.fname = (char *)malloc(strlen(name) + 1);
			strcpy(grx.fname, name);
		}

		if (!(grx.font = GrLoadFont(name))) {
			return(0);
		}

		grx.to.txo_font = grx.font;

		vdevice.hheight = grx.font->fnt_height;
		vdevice.hwidth = grx.font->fnt_width;
#ifdef DBG
		fprintf(dfp, "w, h: %f %f\n", vdevice.hheight, vdevice.hwidth);
		fflush(dfp);
#endif
	}

	return (1);
}

static int grx_char(int c)
{
	GrDrawChar(c, vdevice.cpVx, vdevice.sizeSy - vdevice.cpVy - vdevice.hheight, &grx.to);
	vdevice.cpVx += vdevice.hwidth;

	return (1);
};

static int grx_string(char *s)
{
	int	len = strlen(s);
	GrDrawString(s, len, vdevice.cpVx, vdevice.sizeSy - vdevice.cpVy - vdevice.hheight, &grx.to);
	vdevice.cpVx += vdevice.hwidth * len;
	return (1);
}


/*
 * Everything is supposed to have been through the higher up clippers in
 * vogl.. so no need to clip here..
 * 
 * Draw a solid 1 pixel wide line... libgrx checks for horizontal and vertical
 * lines for us.
 */
static int grx_solid(
  int x,
  int y)
{

	GrLineNC(x, vdevice.sizeSy - y,
		 vdevice.cpVx, vdevice.sizeSy - vdevice.cpVy,
		 grx.fg
	);

	vdevice.cpVx = x;
	vdevice.cpVy = y;

	return (0);
}

/*
 * Draw a patterned and/or > 1 pixel wide line. (Waiting for libgrx to
 * actually implement this...)
 */
static int grx_pattern(
  int x,
  int y)
{
	GrCustomLine(vdevice.cpVx, vdevice.sizeSy - vdevice.cpVy,
		     x, vdevice.sizeSy - y,
		     &grx.lopt
	);

	vdevice.cpVx = x;
	vdevice.cpVy = y;

	return (0);
};

static int grx_colour(int i)
{

	if (i < MAXCOLOR)
		grx.fg = grx.palette[i];	/* for now */
	else
		grx.fg = GrBlack();

	grx.lopt.lno_color = grx.fg;

	grx.to.txo_fgcolor = grx.fg;
	return (0);
};

/*
 * grx_mapcolor
 * 
 * change index i in the color map to the appropriate r, g, b, value.
 */
static int grx_mapcolor(
  int c,
  int r,
  int g,
  int b)
{
	int             j;

	if (c >= MAXCOLOR || vdevice.depth == 1)
		return (-1);

	grx.palette[c] = GrAllocColor(r, g, b);

}


static int grx_fill(
  int sides,
  int *x,
  int *y)
{
	int             i, j;
	int             points[sides][2];

	for (i = 0; i < sides; i++) {
		points[i][0] = x[i];
		points[i][1] = grx.height - y[i];
	}

	GrFilledPolygon(sides, points, grx.fg);

	return (0);
};

static int grx_checkkey(void)
{
	char            c;

	if (kbhit()) {
		if ((c = getkey()) == 3) {	/* control-c */
			grx_exit();
			/* don't call vexit(), avoid back-refs */
			exit(0);
		} else
			return c;
	} else
		return 0;
}

static int grx_locator(
  int *x,
  int *y)
{
	MouseEvent      mEv;
	static          ox = 0, oy = 0, obuttons = 0;

	if (!grx.has_mouse) {
		*x = *y = 0;
		return (-1);
	}
	/*
	 * if (MousePendingEvent()) {
	 */
	MouseGetEvent(M_MOTION | M_BUTTON_CHANGE | M_POLL, &mEv);

	if (mEv.flags & M_MOTION) {
		ox = mEv.x;
		oy = vdevice.sizeSy - mEv.y;
	}
	/*
	 * HACK... the RIGHT button is the second button and we want it to
	 * return 2...
	 */

	if (mEv.flags & M_BUTTON_CHANGE) {
		obuttons = ((mEv.buttons & M_LEFT) ? 1 : 0) |
			((mEv.buttons & M_MIDDLE) ? 2 : 0) |
			((mEv.buttons & M_RIGHT) ? 2 : 0);
	}
	/*
	 * }
	 */

	*x = ox;
	*y = oy;

	return (obuttons);
}

static int grx_lwidth(int w)
{

	grx.lopt.lno_width = w;
	if (w == 1 && grx.lopt.lno_pattlen == 0)
		vdevice.dev.Vdraw = grx_solid;
	else
		vdevice.dev.Vdraw = grx_pattern;
}

static int grx_lstyle(int s)
{

	static unsigned char dashes[16];
	unsigned        ls = s;
	int             i, n, a, b;

	if (grx.lopt.lno_width == 1 && (ls == 0 || ls == 0xffff)) {
		vdevice.dev.Vdraw = grx_solid;
		grx.lopt.lno_pattlen = 0;
		return;
	}
	for (i = 0; i < 16; i++)
		dashes[i] = 0;

#define	ON	1
#define	OFF	0

	a = b = OFF;
	if (ls & (1 << 0))
		a = b = ON;

	n = 0;
	for (i = 0; i < 16; i++) {	/* Over 16 bits */
		if (ls & (1 << i))
			a = ON;
		else
			a = OFF;

		if (a != b) {
			b = a;
			n++;
		}
		dashes[n]++;
	}

	grx.lopt.lno_pattlen = 16;
	grx.lopt.lno_dashpat = dashes;
	vdevice.dev.Vdraw = grx_pattern;
}

static DevEntry grxdev = {
	"grx",
	"@:pc8x16.fnt",		/* Large font */
	"@:pc8x8.fnt",		/* Small font */
	grx_backbuffer,		/* backbuffer */
	grx_char,		/* hardware char */
	grx_checkkey,		/* keyhit */
	grx_vclear,		/* clear viewport to current colour */
	grx_colour,		/* set current colour */
	grx_solid,		/* draw line */
	grx_exit,		/* close graphics & exit */
	grx_fill,		/* fill polygon */
	grx_font,		/* set hardware font */
	grx_frontbuffer,	/* front buffer */
	getkey,			/* wait for and get key */
	grx_init,		/* begin graphics */
	grx_locator,		/* get mouse position */
	grx_mapcolor,		/* map colour (set indices) */
#ifndef VOGLE
	grx_lstyle,		/* set linestyle */
#endif
	grx_lwidth,		/* set line width */
	grx_string,		/* draw string of chars */
	grx_swapbuffers,	/* swap buffers */
	grx_sync		/* sync display */
};

/*
 * _grx_devcpy
 * 
 * copy the pc device into vdevice.dev. (as listed in drivers.c)
 */
int _grx_devcpy(void)
{
	vdevice.dev = grxdev;
	return (0);
}
