/* GnarlPlot sprite C code V0.38 22/2/08
   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_SPR_C
#define _GP_SPR_C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "spr.h"
#include "col.h"

/* Generic functions */

gp_spr *gp_spr_gen_fromscreen(gp_sprfmt *f,gp_screen *s,int x,int y,int w,int h,int ox,int oy)
{
	gp_spr *spr;
	int cx,cy,mt;
	spr = (f->create)(f,w,h,ox,oy); /* Create blank sprite in required format */
	if (spr == 0)
		return 0;
	/* Now copy data */
	w = (f->getw)(spr); /* Use actual size */
	h = (f->geth)(spr);
	mt = (f->getmt)(spr);
	for (cy=0;cy<h;cy++)
		for (cx=0;cx<w;cx++)
		{
			(f->setpix)(spr,cx,cy,s->ps,gp_screen_readpix(s,x+cx,y+cy,s->ps));
			if (mt != GP_MT_NONE)
				(f->setmask)(spr,cx,cy,s->ps,s->mt,gp_screen_readmask(s,x+cx,y+cy,s->mt,s->ps));
		}
	return spr;
}

gp_spr *gp_spr_gen_fromspr(gp_sprfmt *f,gp_spr *s,int x,int y,int w,int h,int ox,int oy)
{
	gp_spr *spr;
	int cx,cy,mt;
	spr = (f->create)(f,w,h,ox,oy);
	if (spr == 0)
		return 0;
	w = (f->getw)(spr);
	h = (f->geth)(spr);
	mt = (f->getmt)(spr);
	for (cy=0;cy<h;cy++)
		for (cx=0;cx<w;cx++)
		{
			(f->setpix)(spr,cx,cy,2,(s->f->getpix)(s,x+cx,y+cy,2));
			if (mt != GP_MT_NONE)
				(f->setmask)(spr,cx,cy,mt,2,(s->f->getmask)(s,x+cx,y+cy,mt,2));
		}                
	return spr;
}

void gp_spr_gen_delete(gp_spr *s)
{
	if (s == 0)
		return;
	if (s->d)
		free(s->d);
	free(s);
}

int gp_spr_gen_setw(gp_spr *s,int w)
{
	gp_spr *n;
	if (w < 1)
		return 1;
	n = (s->f->fromspr)(s->f,s,0,0,w,(s->f->geth)(s),(s->f->getox)(s),(s->f->getoy)(s));
	if (n == 0)
		return 1;
	if (s->d)
		free(s->d);
	s->d = n->d;
	n->d = 0;
	free(n);
	return 0;
}

int gp_spr_gen_seth(gp_spr *s,int h)
{
	gp_spr *n;
	if (h < 1)
		return 1;
	n = (s->f->fromspr)(s->f,s,0,0,(s->f->getw)(s),h,(s->f->getox)(s),(s->f->getoy)(s));
	if (n == 0)
		return 1;
	if (s->d)
		free(s->d);
	s->d = n->d;
	n->d = 0;
	free(n);
	return 0;
}

int gp_spr_gen_setox(gp_spr *s,int ox)
{
	gp_spr *n;
	n = (s->f->fromspr)(s->f,s,0,0,(s->f->getw)(s),(s->f->geth)(s),ox,(s->f->getoy)(s));
	if (n == 0)
		return 1;
	if (s->d)
		free(s->d);
	s->d = n->d;
	n->d = 0;
	free(n);
	return 0;
}

int gp_spr_gen_setoy(gp_spr *s,int oy)
{
	gp_spr *n;
	n = (s->f->fromspr)(s->f,s,0,0,(s->f->getw)(s),(s->f->geth)(s),(s->f->getox)(s),oy);
	if (n == 0)
		return 1;
	if (s->d)
		free(s->d);
	s->d = n->d;
	n->d = 0;
	free(n);
	return 0;
}

int gp_spr_gen_plot(gp_spr *s,int x,int y,gp_screen *scr)
{
	unsigned int spr_col,scr_col,mask,tcol;
	int ox,oy;
	x -= (s->f->getox)(s);
	y -= (s->f->getoy)(s);
	for (oy=0;oy<(s->f->geth)(s);oy++)
		for (ox=0;ox<(s->f->getw)(s);ox++)
		{
			spr_col = (s->f->getpix)(s,ox,oy,2); /* Best possible format */
			switch ((s->f->getmt)(s))
			{
			case GP_MT_NONE:
				gp_screen_writepix(scr,x+ox,y+oy,2,spr_col);
				gp_screen_writemask(scr,x+ox,y+oy,0,0,0); break;
			case GP_MT_ONOFF:
				if ((s->f->getmask)(s,ox,oy,GP_MT_ONOFF,2))
				{
					gp_screen_writepix(scr,x+ox,y+oy,2,spr_col);
					gp_screen_writemask(scr,x+ox,y+oy,0,0,0);
				}
				break;
			/* Else greyscale/RGB, so treat as RGB */
			default:
				scr_col = gp_screen_readpix0(scr,x+ox,y+oy,2); /* Best possible format */
				mask = (s->f->getmask)(s,ox,oy,GP_MT_RGB,2);
				tcol = gp_col_24_rgb(spr_col,mask,scr_col);
				gp_screen_writepix(scr,x+ox,y+oy,2,tcol);
				/* Combine mask */
				if (scr->mt)
				{
					tcol = gp_screen_readmask0(scr,x+ox,y+oy,GP_MT_RGB,2);
					/* Multiply each component together */
					tcol = gp_col_24_rgb(0,mask,tcol); /* Cheat and use this function */
					/* Now write back */
					gp_screen_writemask(scr,x+ox,y+oy,GP_MT_RGB,2,tcol);
				}
			}
		}
	return 0;
}

int gp_spr_gen_tplot(gp_spr *s,int x,int y,gp_screen *scr,int l,f1616 sx,f1616 sy,f1616 dx,f1616 dy)
{
	unsigned int spr_col,scr_col,mask,tcol;
	int sw,sh;
	sw = (s->f->getw)(s) << 16;
	sh = (s->f->geth)(s) << 16;
	while (l-- > 0)
	{
		sx = sx % sw; /* Round coords to lie within sprite */
		sy = sy % sh;
		spr_col = (s->f->getpix)(s,sx >> 16,sy >> 16,2); /* Best possible format */
		switch ((s->f->getmt)(s))
		{
		case GP_MT_NONE:
			gp_screen_writepix(scr,x,y,2,spr_col);
			gp_screen_writemask(scr,x,y,0,0,0); break;
		case GP_MT_ONOFF:
			if ((s->f->getmask)(s,sx >> 16,sy >> 16,GP_MT_ONOFF,2))
			{
				gp_screen_writepix(scr,x,y,2,spr_col);
				gp_screen_writemask(scr,x,y,0,0,0);
			}
			break;
		/* Else greyscale/RGB, so treat as RGB */
		default:
			scr_col = gp_screen_readpix0(scr,x,y,2); /* Best possible format */
			mask = (s->f->getmask)(s,sx >> 16,sy >> 16,GP_MT_RGB,2);
			tcol = gp_col_24_rgb(spr_col,mask,scr_col);
			gp_screen_writepix(scr,x,y,2,tcol);
			/* Combine mask */
			if (scr->mt)
			{
				tcol = gp_screen_readmask0(scr,x,y,GP_MT_RGB,2);
				/* Multiply each component together */
				tcol = gp_col_24_rgb(0,mask,tcol); /* Cheat and use this function */
				/* Now write back */
				gp_screen_writemask(scr,x,y,GP_MT_RGB,2,tcol);
			}
		}
		x++;
		sx+=dx;
		sy+=dy;
	}
	return 0;
}

/* Loading/saving */

static int fputw(int i,FILE *f)
{
	if (fputc(i & 255,f) == EOF) return EOF;
	if (fputc((i >> 8) & 255,f) == EOF) return EOF;
	if (fputc((i >> 16) & 255,f) == EOF) return EOF;
	if (fputc((i >> 24) & 255,f) == EOF) return EOF;
	return 0;
}

static int fgetw(FILE *f,int *i)
{
	int t;
	t = fgetc(f); if (t == EOF) return EOF; *i = t;
	t = fgetc(f); if (t == EOF) return EOF; *i += t << 8;
	t = fgetc(f); if (t == EOF) return EOF; *i += t << 16;
	t = fgetc(f); if (t == EOF) return EOF; *i += t << 24;
	return 0;
}

int gp_spr_save(gp_spr *s,FILE *f)
{
	int sz;
	if ((s == 0) || (s->f == 0) || (s->d == 0) || (f == 0))
		return 1; /* Fail */
	sz = (s->f->getsize)(s);
	if (fputw(sz,f) == EOF) return 1;
	if (fwrite(s->d,sz,1,f) != 1)
		return 1;
	return 0;
}

gp_spr *gp_spr_load(gp_sprfmt *fmt,FILE *f)
{
	int sz;
	gp_spr *s;
	if ((fmt == 0) || (f == 0))
		return 0;
	if (fgetw(f,&sz) == EOF) return 0;
	s = malloc(sizeof(gp_spr));
	if (s == 0)
		return 0;
	s->d = malloc(sz);
	if (s->d == 0)
	{
		free(s);
		return 0;
	}
	s->f = fmt;
	if (fread(s->d,sz,1,f) != 1)
	{
		free(s->d);
		free(s);
		return 0;
	}
	return s;
}

/* Array operations */

void gp_spr_ar_delete(gp_spr **s,int n)
{
	/* Free all sprites in the array, and the array itself */
	if (s == 0)
		return;
	while (n-- > 0)
		if (s[n])
			(s[n]->f->delete)(s[n]);
	free(s);
}

gp_spr **gp_spr_ar_convert(gp_spr **s,int n,gp_sprfmt *f,gp_spr **d)
{
	/* Convert the sprites in array s to format f
	   If d is null, allocate an array ourselves
	   If d is s, overwrite array s, deleting the source sprites as we go
	   Returns 0 on failure, else array pointer
	   Individual conversions which fail will not cause the entire operation to fail though */
	int i;
	gp_spr *spr;
	if ((s == 0) || (n < 1))
		return 0;
	if (d == 0)
		d = malloc(4*n);
	if (d == 0)
		return 0;
	for (i=0;i<n;i++)
	{
		if (s[i])
		{
			spr = (f->fromspr)(f,s[i],0,0,(s[i]->f->getw)(s[i]),(s[i]->f->geth)(s[i]),(s[i]->f->getox)(s[i]),(s[i]->f->getoy)(s[i]));
			if (s == d)
				(s[i]->f->delete)(s[i]);
			d[i] = spr;
		}
		else
			d[i] = 0;
	}
	return d;
}

/* Rot/scale plotter */

#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)<(b)?(b):(a))
#define ABS(x) ((x)<0?(-(x)):(x))

/* Return the integer number of screen rows/columns needed to go from sprite
   coord (x1,y1) to (x2,y2)
   dx,dy = sprite pixels per screen
   sqr = dx*dx+dy*dy
*/
#define R(x1,y1,x2,y2) ((y2-y1)*dx-(x2-x1)*dy)/sqr
#define C(x1,y1,x2,y2) ((x2-x1)*dx+(y2-y1)*dy)/sqr

#define INRANGE(SPRX,SPRY) (((SPRX)>=0)&&((SPRY)>=0)&&((SPRX)<(sprw << 16))&&((SPRY)<(sprh << 16)))

int gp_spr_plot_rotscale(gp_spr *s,int x,int y,gp_screen *scr,f1616 dx,f1616 dy)
{
	/* Rotated/scaled sprite plotter, mk III
	   Plot s to scr, with centre of the sprite at x,y, using a sprite-per-screen X delta of dx,dy and Y delta of -dy,dx
	   Strategy:
	   1. Find a screen coord inside the sprite
	   2. From that coord, plot all rows at that location and above
	   3. Plot all rows below the location
	*/
	f1616 sprx,spry; /* Sprite coords of current line start */
	int len; /* Length of current line */
	f1616 sqr;
	int sprw,sprh; /* Sprite width, height */
	int a,b,c=0,d=0;
	/* Step 1 */
	/* If sprite origin is outside sprite, calculate location of a point within sprite. Else use sprite origin */
	sqr = f1616_sqr(dx)+f1616_sqr(dy);
	if (sqr == 0)
		return 1; /* Sprite too big! */
	a = (s->f->getox)(s);
	b = (s->f->getoy)(s);
	sprw = (s->f->getw)(s);
	sprh = (s->f->geth)(s);
	sprx = a << 16;
	spry = b << 16;
	if ((a < 0) || (b < 0) || (a >= sprw) || (b >= sprh)) {
		c = C(a,b,(sprw/2),(sprh/2));
		d = R(a,b,(sprw/2),(sprh/2));
		x += c;
		y += d;
		sprx += dx*c-dy*d;
		spry += dy*c+dx*d;
	}
	/* Make a note of these coords */
	a = x;
	b = y;
	c = sprx;
	d = spry;
	/* Step 2 */
	/* Expand line */
	len=1;
	while (INRANGE(sprx-dx,spry-dy)) {
		x--;
		sprx -= dx;
		spry -= dy;
		len++;
	}
	while (INRANGE(sprx+len*dx,spry+len*dy))
		len++;
	/* Plot up */
	while (len > 0) {
		(s->f->tplot)(s,x,y,scr,len,sprx,spry,dx,dy);
		y--;
		if (y < 0)
			break;
		sprx += dy;
		spry -= dx;
		/* Move left edge out */
		while (INRANGE(sprx,spry)) {
			x--;
			sprx -= dx;
			spry -= dy;
			len++;
		}
		/* Move right edge out */
		while (INRANGE(sprx+len*dx,spry+len*dy))
			len++;
		/* Move left edge in */
		while (!INRANGE(sprx,spry) && (len > 0)) {
			x++;
			sprx += dx;
			spry += dy;
			len--;
		}
		/* Move right edge in */
		while (!INRANGE(sprx+(len-1)*dx,spry+(len-1)*dy) && (len > 0))
			len--;
	}
	/* Step 3 */
	/* Retrieve old coord */
	x = a;
	y = b;
	sprx = c;
	spry = d;
	len=1;
	/* Step 3 */
	while (len > 0) {
		/* Move down a row */
		y++;
		if (y == scr->height)
			break;
		sprx -= dy;
		spry += dx;
		/* Move left edge out */
		while (INRANGE(sprx,spry)) {
			x--;
			sprx -= dx;
			spry -= dy;
			len++;
		}
		/* Move right edge out */
		while (INRANGE(sprx+len*dx,spry+len*dy))
			len++;
		/* Move left edge in */
		while (!INRANGE(sprx,spry) && (len > 0)) {
			x++;
			sprx += dx;
			spry += dy;
			len--;
		}
		/* Move right edge in */
		while (!INRANGE(sprx+(len-1)*dx,spry+(len-1)*dy) && (len > 0))
			len--;
		/* Plot if valid */
		if (len > 0)
			(s->f->tplot)(s,x,y,scr,len,sprx,spry,dx,dy);
	}
	return 0;
}

/* Mouse pointer definition creator */

char *gp_spr_mouseptr(gp_spr *s,int cols[3])
{
	int w,h,x,y,c,i;
	char *d,*p;
	w = ((s->f->getw)(s)+3) >> 2; /* Width in bytes, 4 pixels per byte */
	h = (s->f->geth)(s);
	if ((s == 0) || (w < 1) || (w > 8) || (h < 1) || (h > 32) || ((s->f->getox)(s) < 0) || ((s->f->getox)(s) >= w*4) || ((s->f->getoy)(s) < 0) || ((s->f->getoy)(s) >= h))
		return 0; /* No sprite/bad size/origin */
	/* Allocate data */
	d = malloc(10+w*h);
	d[0] = 0;
	d[1] = 0; /* Shape number, leave for user to fill */
	d[2] = w;
	d[3] = h;
	d[4] = (s->f->getox)(s);
	d[5] = (s->f->getoy)(s);
	p = d+10;
	d[6] = ((int) p) & 0xFF;
	d[7] = (((int) p) & 0xFF00) >> 8;
	d[8] = (((int) p) & 0xFF0000) >> 16;
	d[9] = (((int) p) & 0xFF000000) >> 24;
	/* Fill in data */
	for (y=0;y<h;y++)
		for (x=0;x<w;x+=4)
		{
			c = 0;
			if ((s->f->getmask)(s,x,y,GP_MT_ONOFF,0)) /* Visible pixel? */
			{
				i = (s->f->getpix)(s,x,y,2); /* Get RGB */
				if (i == cols[0])
					c |= 1;
				else if (i == cols[1])
					c |= 2;
				else if (i == cols[2])
					c |= 3;
			}
			if ((s->f->getmask)(s,x+1,y,GP_MT_ONOFF,0))
			{
				i = (s->f->getpix)(s,x+1,y,2);
				if (i == cols[0])
					c |= 4;
				else if (i == cols[1])
					c |= 8;
				else if (i == cols[2])
					c |= 12;
			}
			if ((s->f->getmask)(s,x+2,y,GP_MT_ONOFF,0))
			{
				i = (s->f->getpix)(s,x+2,y,2);
				if (i == cols[0])
					c |= 16;
				else if (i == cols[1])
					c |= 32;
				else if (i == cols[2])
					c |= 48;
			}
			if ((s->f->getmask)(s,x+3,y,GP_MT_ONOFF,0))
			{
				i = (s->f->getpix)(s,x+3,y,2);
				if (i == cols[0])
					c |= 64;
				else if (i == cols[1])
					c |= 128;
				else if (i == cols[2])
					c |= 192;
			}
			*p++ = c;
		}
	return d;
} 

#endif
