/* GnarlPlot screen code V0.29 10/11/05
   Copyright 2008 Jeffrey Lee
   This file is part of GnarlPlot.
   GnarlPlot is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
   GnarlPlot is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   You should have received a copy of the GNU General Public License
   along with GnarlPlot.  If not, see <http://www.gnu.org/licenses/>. */

#ifndef _GP_SCREEN_C
#define _GP_SCREEN_C

#include <stdlib.h>

#include "kernel.h"
#include "swis.h"

#include "screen.h"
#include "col.h"

#include "WoumInclude:lib/fixmath.h" /* For int_sqrt */

const int gp_mask_conv(int imt,int ips,int im,int omt,int ops)
{
	int r,g,b; /* Convert up to RGB, then down to output */
	if (imt == GP_MT_NONE)
		r = g = b = 0; /* Solid pixel */
	else if (imt == GP_MT_ONOFF)
	{
		if (im)
			r = g = b = 0; /* Solid */
		else
			r = g = b = 255; /* Transparent */
	}
	else if (imt == GP_MT_GREY)
		r = g = b = im & 255;
	else
	{
		im = gp_col_conv(ips,im,2); /* Convert up to RGB */
		r = im & 255;
		g = (im >> 8) & 255;
		b = (im >> 16) & 255;
	}
	/* Now convert down */
	if (omt == GP_MT_NONE)
		return 0;
	else if (omt == GP_MT_ONOFF)
	{
		if (r+g+b > 127*3)
			return 0; /* Transparent */
		else
			return 1; /* setpix must be careful to convert this to crrect value */
	}
	else if (omt == GP_MT_GREY)
		return (r+g+b)/3;
	else
		return gp_col_conv(2,r+(g << 8)+(b << 16),ops);
}

gp_screen *gp_screen_new_fromvdu()
{
	gp_screen *s;
	_kernel_swi_regs regs;
	int vars[6];
	vars[0] = 9; /* Pixel size */
	vars[1] = 11; /* Width-1 */
	vars[2] = 12; /* Height-1 */
	vars[3] = 148; /* VDU bank */
	vars[4] = 149; /* hardware bank */
	vars[5] = -1;
	regs.r[0] = (int) vars;
	regs.r[1] = (int) vars;
	_kernel_swi(OS_ReadVduVariables,&regs,&regs);
	if ((vars[0] < 3) || (vars[0] > 5))
		return 0; /* Bad pixel size */
	s = malloc(sizeof(gp_screen));
	if (s == 0)
		return 0;
	s->width = vars[1]+1;
	s->height = vars[2]+1;
	s->ps = vars[0]-3;
	s->mt = GP_MT_NONE;
	s->gap = 0;
	s->bank0 = (void *) vars[3];
	s->bank1 = (void *) vars[4];
	return s;
}

gp_screen *gp_screen_new_frompaintsprite(const int *spr)
{
	gp_screen *s;
	int ps;
	_kernel_swi_regs regs;
	if ((spr[6] & 3) || ((spr[7]+1) & 3)) /* Sprite data not byte aligned! */
		return 0;
	ps = spr[10] >> 27;
	if (ps == 0)
	{
		regs.r[0] = spr[10];
		regs.r[1] = 3;
		if (_kernel_swi(OS_ReadModeVariable,&regs,&regs))
			return 0;
		if ((regs.r[2] == 63) || (regs.r[2] == 255))
			ps = 0;
		else if (regs.r[2] == 65535)
			ps = 1;
		else if (regs.r[2] == -1)
			ps = 2;
		else
			return 0; /* Bad mode */
	}
	else
	{
		ps = ps-4;
		if ((ps < 0) || (ps > 2))
			return 0;
	}
	s = malloc(sizeof(gp_screen));
	if (s == 0)
		return 0;
	s->width = (spr[4]+1) << (2-ps);
	s->height = spr[5]+1;
	s->ps = ps;
	s->mt = GP_MT_NONE;
	s->gap = (32-((spr[7]+1)-spr[6]))/8; /* Number of bytes left in the gap areas */
	s->bank0 = s->bank1 = (void *) (spr[8]+((int) spr))+(spr[6]/8);
	return s;
}

void gp_screen_delete(gp_screen *s)
{
	if (s)
		free(s);
}

void gp_screen_crop(gp_screen *s,int x,int y,int w,int h)
{
	/* Validate values... */
	if (s == 0)
		return;
	if (x < 0)
	{
		w+=x;
		x=0;
	}
	if (y < 0)
	{
		h+=y;
		y=0;
	}
	if (x+w > s->width)
		w=s->width-x;
	if (y+h > s->height)
		h=s->height-y;
	if ((w <= 0) || (h <= 0))
		return;
	/* Crop! */
	s->bank0 += ((y*s->width + x) << s->ps) + y*s->gap;
	s->bank1 += ((y*s->width + x) << s->ps) + y*s->gap;
	s->gap += (s->width-w) << s->ps;
	s->width = w;
	s->height = h;
}

int gp_screen_readpix(const gp_screen *s,int x,int y,int ps)
{
	int c,tps;
	if ((s == 0) || (x < 0) || (x >= s->width) || (y < 0) || (y >= s->height) || (ps < 0) || (ps > 2))
		return 0;
	tps = gp_col_trueps(s->ps,s->mt);
	c = ((y*s->width + x) << tps) + y*s->gap;
	if (s->ps == 0)
		c = ((char *) s->bank1)[c];
	else if (s->ps == 1)
		c = ((short *) s->bank1)[c >> 1] & 0x7FFF;
	else if (s->ps == 2)
		c = ((int *) s->bank1)[c >> 2] & 0xFFFFFF;
	else
		c = 0; /* Dunno */
	return gp_col_conv(s->ps,c,ps); /* Convert colour */
}

int gp_screen_readpix0(const gp_screen *s,int x,int y,int ps)
{
	int c,tps;
	if ((s == 0) || (x < 0) || (x >= s->width) || (y < 0) || (y >= s->height) || (ps < 0) || (ps > 2))
		return 0;
	tps = gp_col_trueps(s->ps,s->mt);
	c = ((y*s->width + x) << tps) + y*s->gap;
	if (s->ps == 0)
		c = ((char *) s->bank0)[c];
	else if (s->ps == 1)
		c = ((short *) s->bank0)[c >> 1] & 0x7FFF;
	else if (s->ps == 2)
		c = ((int *) s->bank0)[c >> 2] & 0xFFFFFF;
	else
		c = 0; /* Dunno */
	return gp_col_conv(s->ps,c,ps); /* Convert colour */
}

int gp_screen_readmask(const gp_screen *s,int x,int y,int mt,int ps)
{
	int c,tps;
	if ((s == 0) || (x < 0) || (x >= s->width) || (y < 0) || (y >= s->height) || (ps < 0) || (ps > 2) || (mt < 0) || (mt > GP_MT_RGB) || (s->mt == GP_MT_NONE))
		return gp_mask_conv(GP_MT_ONOFF,0,0,mt,ps);
	tps = gp_col_trueps(s->ps,s->mt);
	c = ((y*s->width + x) << tps) + y*s->gap;
	if (s->ps == 0)
		c = ((char *) s->bank1)[c+1];
	else if (s->mt == GP_MT_ONOFF)
	{
		if (s->ps == 1)
			c = ((char *) s->bank1)[c+1] & 0x8000;
		else
			c = ((char *) s->bank1)[c+3];
	}
	else if (s->mt == GP_MT_GREY)
	{
		if (s->ps == 1)
			c = ((char *) s->bank1)[c+2];
		else
			c = ((char *) s->bank1)[c+3];
	}
	else
	{
		if (s->ps == 1)
			c = ((char *) s->bank1)[c+2] + (((char *) s->bank1)[c+3] << 8);
		else
			c = ((int *) s->bank1)[(c+4) >> 2];
	}
	return gp_mask_conv(s->mt,s->ps,c,mt,ps);
}

int gp_screen_readmask0(const gp_screen *s,int x,int y,int mt,int ps)
{
	int c,tps;
	if ((s == 0) || (x < 0) || (x >= s->width) || (y < 0) || (y >= s->height) || (ps < 0) || (ps > 2) || (mt < 0) || (mt > GP_MT_RGB) || (s->mt == GP_MT_NONE))
		return gp_mask_conv(GP_MT_ONOFF,0,0,mt,ps);
	tps = gp_col_trueps(s->ps,s->mt);
	c = ((y*s->width + x) << tps) + y*s->gap;
	if (s->ps == 0)
		c = ((char *) s->bank0)[c+1];
	else if (s->mt == GP_MT_ONOFF)
	{
		if (s->ps == 1)
			c = ((char *) s->bank0)[c+1] & 0x8000;
		else
			c = ((char *) s->bank0)[c+3];
	}
	else if (s->mt == GP_MT_GREY)
	{
		if (s->ps == 1)
			c = ((char *) s->bank0)[c+2];
		else
			c = ((char *) s->bank0)[c+3];
	}
	else
	{
		if (s->ps == 1)
			c = ((char *) s->bank0)[c+2] + (((char *) s->bank0)[c+3] << 8);
		else
			c = ((int *) s->bank0)[(c+4) >> 2];
	}
	return gp_mask_conv(s->mt,s->ps,c,mt,ps);
}

void gp_screen_writepix(gp_screen *s,int x,int y,int ps,int c)
{
	int tps;
	if ((s == 0) || (x < 0) || (x >= s->width) || (y < 0) || (y >= s->height) || (ps < 0) || (ps > 2))
		return;
	tps = gp_col_trueps(s->ps,s->mt);
	tps = ((y*s->width + x) << tps) + y*s->gap;
	c = gp_col_conv(ps,c,s->ps);
	if (s->ps == 0)
		((char *) s->bank0)[tps] = c;
	else if (s->ps == 1)
	{
		((char *) s->bank0)[tps] = c & 255;
		c = (c >> 8) & 0x7F;
		if (s->mt == GP_MT_ONOFF)
			c |= ((char *) s->bank0)[tps+1] & 0x80;
		((char *) s->bank0)[tps+1] = c;
	}
	else if (s->ps == 2)
	{
		c = c & 0xFFFFFF;
		c |= ((char *) s->bank0)[tps+3] << 24;
		((int *) s->bank0)[tps >> 2] = c;
	}
}

void gp_screen_writemask(gp_screen *s,int x,int y,int mt,int ps,int c)
{
	int tps;
	if ((s == 0) || (x < 0) || (x >= s->width) || (y < 0) || (y >= s->height) || (ps < 0) || (ps > 2) || (s->mt == GP_MT_NONE))
		return;
	tps = gp_col_trueps(s->ps,s->mt);
	tps = ((y*s->width + x) << tps) + y*s->gap;
	c = gp_mask_conv(mt,ps,c,s->mt,s->ps);
	if (s->mt == GP_MT_ONOFF)
	{
		if ((s->ps == 0) || (s->ps == 2))
		{
			if (c)
				c = 255;
			tps += s->ps+1;
		}
		else
		{
			if (c)
				c = 128;
			tps++;
		}
		((char *) s->bank0)[tps] = c;
	}
	else if (s->mt == GP_MT_GREY)
		((char *) s->bank0)[tps+s->ps+1] = c;
	else if (s->mt == GP_MT_RGB)
	{
		if (s->ps == 0)
			((char *) s->bank0)[tps+1] = c;
		else if (s->ps == 1)
		{
			((char *) s->bank0)[tps] = c;
			((char *) s->bank0)[tps+1] = c >> 8;
		}
		else
			((int *) s->bank0)[tps >> 2] = c;
	}
}

void gp_screen_hline(gp_screen *s,int x,int y,int l,int ps,int c)
{
	/* Draw a line using the horzline drawers */
	int tps;
	if ((s == 0) || (y < 0) || (y >= s->height))
		return;
	if (x < 0)
	{
		l -= x;
		x = 0;
	}
	else if (x+l >= s->width)
		l = s->width-x;
	if (l <= 0)
		return;
	c = gp_col_conv(ps,c,s->ps);
	tps = gp_col_trueps(s->ps,s->mt);
	x = ((int) (s->bank0)) + ((y*s->width + x) << tps) + y*s->gap;
	if (tps == 0)
		gp_screen_hline0(c + (c << 8) + (c << 16) + (c << 24),(int *) x,l);
	else if (tps == 1)
	{
		if (s->ps == 0)
		{
			c = c & 0xFF;
			if (s->mt == GP_MT_ONOFF)
				c |= c + 0xFF00; /* Turn pixel on */
		}
		else /* s->ps == 1 */
		{
			c = c & 0x7FFF;
			if (s->mt == GP_MT_ONOFF) /* must be s->ps == 1 if here */
				c |= 0x8000;
		}
		c |= c << 16;
		gp_screen_hline1(c,(int *) x,l);
	}
	else if (tps == 2)
	{
		if (s->ps == 1)
		{
			c = c & 0x7FFF;
			c |= c << 16;
		}
		else /* s->ps == 2 */
		{
			c = c & 0xFFFFFF;
			if (s->mt == GP_MT_ONOFF)
				c |= 0xFF000000;
		}
		gp_screen_hline2(c,(int *) x,l);
	}
	else /* tps == 3 */
		gp_screen_hline3(c,(int *) x,l,0);
}

/* Line drawing bits */

static inline void _line_seg(gp_screen *s,int *x,int y,int len,int dir,int c)
{
	if (len < 1)
		return;
	if (dir == 1)
	{
		gp_screen_hline(s,*x,y,len,s->ps,c);
		*x += len;
	}
	else
	{
		gp_screen_hline(s,*x-len,y,len,s->ps,c);
		*x -= len;
	}
}

static inline void _line_vseg(gp_screen *s,int x,int *y,int len,int c)
{
	while (len-- > 0)
		gp_screen_writepix(s,x,(*y)++,s->ps,c);
}

void gp_screen_line(gp_screen *s,int x1,int y1,int x2,int y2,int ps,int c)
{
	/* Draw a line, using bresenham's run-sliced line drawer */
	int xd,yd; /* Deltas */
	int xdir,einc,eover,err; /* X advance dir, error inc, error overflow, error val */
	int mlen,ilen,flen; /* Main, initial and final lengths */
	if (s == 0)
		return;
	/* Convert colour */
	c = gp_col_conv(ps,c,s->ps);
	/* Ignore clipping - let lower level code handle it */
	if (y2 < y1)
	{
		xd = x1;
		yd = y1;
		x1 = x2;
		y1 = y2;
		x2 = xd;
		y2 = yd;
	}
	xd = x2-x1;
	yd = y2-y1;
	if (xd == 0)
	{
		/* Vertical */
		_line_vseg(s,x1,&y1,yd,c);
		return;
	}
	if (xd < 0)
	{
		xdir = -1;
		xd = -xd;
	}
	else
		xdir = 1;
	if (yd == 0)
	{
		/* Horizontal */
		_line_seg(s,&x1,y1,xd,xdir,c);
		return;
	}
	/* Else some kind of diagonal */
	if (xd > yd)
	{
		/* X major */
		einc = (xd % yd)*2;
		eover = 2*yd;
		err = einc/2;
		mlen = xd/yd;
		ilen = mlen/2 + 1;
		flen = ilen;
		if (((mlen & 1) == 0) && (einc == 0))
			ilen--;
		else
			if (mlen & 1)
				err += eover/2;
		_line_seg(s,&x1,y1++,ilen,xdir,c);
		yd--;
		while (yd>0)
		{
			err += einc;
			if (err >= eover)
			{
				_line_seg(s,&x1,y1++,mlen+1,xdir,c);
				err -= eover;
			}
			else
				_line_seg(s,&x1,y1++,mlen,xdir,c);
			yd--;
		}
		_line_seg(s,&x1,y1,flen,xdir,c);
		return;
	}
	/* Else Y major */
	einc = (yd % xd)*2;
	eover = 2*xd;
	err = einc/2;
	mlen = yd/xd;
	ilen = mlen/2 + 1;
	flen = ilen;
	if (((mlen + 1) == 0) && (einc == 0))
		ilen--;
	else if (mlen & 1)
		err += eover/2;
	_line_vseg(s,x1,&y1,ilen,c);
	x1+=xdir;
	xd--;
	while (xd>0)
	{
		err += einc;
		if (err > eover)
		{
			_line_vseg(s,x1,&y1,mlen+1,c);
			err -= eover;
		}
		else
			_line_vseg(s,x1,&y1,mlen,c);
		x1+=xdir;
		xd--;
	}
	_line_vseg(s,x1,&y1,flen,c);
}

void gp_screen_rect(gp_screen *s,int lx,int rx,int ty,int by,int ps,int c)
{
	/* Draw a rectangle
	   All coordinates are inclusive */
	void *pos;
	int tps,gap;
	if (s == 0)
		return;
	/* Perform clipping */
	if (lx < 0)
		lx = 0;
	else if (lx >= s->width)
		return;
	if (rx >= s->width)
		rx = s->width-1;
	else if (rx < lx)
		return;
	if (ty < 0)
		ty = 0;
	else if (ty >= s->height)
		return;
	if (by >= s->height)
		by = s->height-1;
	else if (by < ty)
		return;
	/* Use pointer arithmetic to bypass gp_screen_hline */
	tps = gp_col_trueps(s->ps,s->mt);
	rx = (rx-lx)+1; /* Get width & height */
	by = (by-ty)+1;
	c = gp_col_conv(ps,c,s->ps);
	pos = s->bank0 + ((ty*s->width + lx) << tps) + ty*s->gap;
	gap = (s->width << tps) + s->gap;
	if (tps == 0)
		c = c + (c << 8) + (c << 16) + (c << 24);
	else if (tps == 1)
	{
		if (s->ps == 0)
		{
			c = c & 0xFF;
			if (s->mt == GP_MT_ONOFF)
				c |= c + 0xFF00; /* Turn pixel on */
		}
		else /* s->ps == 1 */
		{
			c = c & 0x7FFF;
			if (s->mt == GP_MT_ONOFF) /* must be s->ps == 1 if here */
				c |= 0x8000;
		}
		c |= c << 16;
	}
	else if (tps == 2)
	{
		if (s->ps == 1)
		{
			c = c & 0x7FFF;
			c |= c << 16;
		}
		else /* s->ps == 2 */
		{
			c = c & 0xFFFFFF;
			if (s->mt == GP_MT_ONOFF)
				c |= 0xFF000000;
		}
	}
	while (by--)
	{
		if (tps == 0)
			gp_screen_hline0(c,pos,rx);
		else if (tps == 1)
			gp_screen_hline1(c,pos,rx);
		else if (tps == 2)
			gp_screen_hline2(c,pos,rx);
		else
			gp_screen_hline3(c,pos,rx,0);
		pos += gap;
	}
}

void gp_screen_circ(gp_screen *s,int x,int y,int r,int ps,int c)
{
	/* Draw a circle */
	int yoff,xoff;
	if (s == 0)
		return;
	yoff = -r;
	if (yoff < 0) /* Clip to top of screen */
		yoff = -y;
	c = gp_col_conv(ps,c,s->ps);
	while ((yoff <= r) && (y+yoff < s->height)) /* Clip to circle & top of screen */
	{
		xoff = int_sqrt(((r*r)-(yoff*yoff))*4)/2; /* Use increased accuracy since int_sqrt sucks a bit */
		gp_screen_hline(s,x-xoff,yoff++,2*xoff+1,s->ps,c); /* Draw line */
	}
}

#endif
