/*
 * SDL_prim.c
 *
 * Simple SDL Primitive C functions.  My goal while writing this
 * was to keep everything as simple as possible, both in the design
 * of these functions and in the usage of these functions.
 *
 * I hope someone finds this useful.
 *
 */


#include "SDL_prim.h"


/*
 * simple pixel drawing function.
 *
 * tests to be sure pixel is within bounds of the surface, so you
 * can draw off the surface to your hearts content
 *
 * it's probably not good practice to be drawing off the surface though...
 *
 */
inline
void SDL_putPixel(SDL_Surface *surf, int x, int y, Uint32 clr)
{
	int Bpp = surf->format->BytesPerPixel;
	Uint8 *p;

	if (! (x < 0 || x >= surf->w || y < 0 || y >= surf->h) )
	{
		p = (Uint8*)surf->pixels + y * surf->pitch + x * Bpp;

		switch(Bpp) {
		case 1:
		  	*p = clr;
			break;

		case 2:
		  	*(Uint16*)p = clr;
			break;

		case 3:
			if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
				p[0] = (clr >> 16) & 0xff;
				p[1] = (clr >> 8) & 0xff;
				p[2] = clr & 0xff;
			} else {
				p[0] = clr & 0xff;
				p[1] = (clr >> 8) & 0xff;
				p[2] = (clr >> 16) & 0xff;
			}
			break;

		case 4:
			*(Uint32*)p = clr;
			break;
		}
	}
}


/*
 * returns pointer to pixel at x, y
 */
inline
Uint8* SDL_getPixel(SDL_Surface *surf, int x, int y)
{
	if (! (x < 0 || x >= surf->w || y < 0 || y >= surf->h) )
		return (Uint8*)surf->pixels + y * surf->pitch + x * surf->format->BytesPerPixel;
	else
		return 0;
}


/*
 * slow pixel blending function
 *
 * called from SDL_blendPixel for 8 bit surfaces.
 * may be useful for something else, I dunno.
 *
 */
inline
void __slow_SDL_blendPixel( SDL_Surface *surf, int x, int y, Uint32 clr,
                            Uint8 alpha)
{
	Uint8 *p;
	Uint8 r, g, b, r2, g2, b2;

	if ( (p = SDL_getPixel(surf, x, y)) ) {
		SDL_GetRGB(clr, surf->format, &r, &g, &b);
		SDL_GetRGB(*p, surf->format, &r2, &g2, &b2);

		r = r2 + ( r - r2 ) * ( alpha / 255.0 );
		g = g2 + ( g - g2 ) * ( alpha / 255.0 );
		b = b2 + ( b - b2 ) * ( alpha / 255.0 );

		clr = SDL_MapRGB(surf->format, r, g, b);

		SDL_putPixel(surf, x, y, clr);
	}
}


/*
 * blends pixel onto surface according to alpha
 */
inline
void SDL_blendPixel( SDL_Surface *surf, int x, int y, Uint32 clr,
                     Uint8 alpha)
{
	Uint8 *p;
	Uint32 R, G, B;

	if ( (p = SDL_getPixel(surf, x, y)) ) {
		switch(surf->format->BytesPerPixel) {
		case 1:  /* 8 bpp */
			__slow_SDL_blendPixel(surf, x, y, clr, alpha);
			break;

		case 2:  /* 16 bpp */
			R = ( (*(Uint16*)p & surf->format->Rmask) + ( ((clr & surf->format->Rmask) - ((*(Uint16*)p) & surf->format->Rmask)) * alpha >> 8) ) & surf->format->Rmask;
			G = ( (*(Uint16*)p & surf->format->Gmask) + ( ((clr & surf->format->Gmask) - ((*(Uint16*)p) & surf->format->Gmask)) * alpha >> 8) ) & surf->format->Gmask;
			B = ( (*(Uint16*)p & surf->format->Bmask) + ( ((clr & surf->format->Bmask) - ((*(Uint16*)p) & surf->format->Bmask)) * alpha >> 8) ) & surf->format->Bmask;
			*(Uint16*)p = R | G | B;
			break;

		case 3:  /* 24 bpp */
			if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
				p[0] = ( p[0] + ((((clr >> 16) & 0xff) - p[0]) * alpha >> 8) ) & 0xff;
				p[1] = ( p[1] + ((((clr >> 8) & 0xff) - p[1]) * alpha >> 8) ) & 0xff;
				p[2] = ( p[2] + (((clr & 0xff) - p[2]) * alpha >> 8) ) & 0xff;
			} else {
				p[0] = ( p[0] + (((clr & 0xff) - p[0]) * alpha >> 8) ) & 0xff;
				p[1] = ( p[1] + ((((clr >> 8) & 0xff) - p[1]) * alpha >> 8) ) & 0xff;
				p[2] = ( p[2] + ((((clr >> 16) & 0xff) - p[2]) * alpha >> 8) ) & 0xff;
			}
			break;

		case 4:  /* 32 bpp */
			R = ( (*(Uint32*)p & surf->format->Rmask) + ( ((clr & surf->format->Rmask) - (*(Uint32*)p & surf->format->Rmask)) * alpha >> 8) ) & surf->format->Rmask;
			G = ( (*(Uint32*)p & surf->format->Gmask) + ( ((clr & surf->format->Gmask) - (*(Uint32*)p & surf->format->Gmask)) * alpha >> 8) ) & surf->format->Gmask;
			B = ( (*(Uint32*)p & surf->format->Bmask) + ( ((clr & surf->format->Bmask) - (*(Uint32*)p & surf->format->Bmask)) * alpha >> 8) ) & surf->format->Bmask;
			*(Uint32*)p = R | G | B;
			break;
		}
	}
}


/*
 * Self explanatory.  draws lines, supports anti-aliasing and
 * alpha blending
 *
 */
void SDL_drawLine_TG( SDL_Surface *surf, int x, int y, int x2, int y2,
                      Uint32 clr, Uint8 alpha, Uint8 flags )
{
	int xaa, yaa, *a, *b, *a2, *b2, da, xd, yd;
	float aa, db;
	float realb;

	/*
	 * for the sake of my sanity when i look at this again...
	 *
	 * xaa:   the x aa offset value, either 1 or 0
	 * yaa:   the y aa offset value, either 1 or 0
	 * *a:    reference to either x or y, depending on which way we are
	 *        drawing.
	 * *b:    reference to either x or y, depending on which way we are
	 *        drawing.
	 * *a2:   reference to either x2 or y2, depending on which way we are
	 *        drawing.
	 * *b2:   reference to either x2 or y2, depending on which way we are
	 *        drawing.
	 * x:     starting x value, current x value
	 * y:     starting y value, current y value
	 * x2:    ending x value
	 * y2:    ending y value
	 * xd:    distance from x to x2
	 * yd:    distance from y to y2
	 * da:    change in a for each iteration, either 1 or -1
	 * db:    change in b with respect to a
	 * realb: real b value, as b is an int, and db is going to be a
	 *        fraction
	 * aa:    anti-aliasing value, fraction part of realb
	 *
	 */

	/* find x and y distances */
	xd = x2 - x;
	yd = y2 - y;

	if (abs(xd) >= abs(yd)) {
	/* draw left/right to left/right */
		xaa = 0;
		yaa = 1;
		a = &x;
		b = &y;
		a2 = &x2;
		b2 = &y2;
		db = (float)yd / xd;
	} else {
	/* draw top/bottom to top/bottom */
		xaa = 1;
		yaa = 0;
		a = &y;
		b = &x;
		a2 = &y2;
		b2 = &x2;
		db = (float)xd / yd;
	}

	if (!(alpha < 255 && (flags & SDL_TG_ALPHA)))
		alpha = 255;

	/* left or right? */
	da = (*a <= *a2) ? 1 : -1;
	/* up or down? */
	db *= da;
	/* init realb */
	realb = (float)*b;
	/* we're stopping when we hit *a2, so we have to add +/-1 to it */
	*a2 += da;

	if (flags & SDL_TG_LOCK)
		__SDL_PRIM_LOCKSURFACE(surf);

	for (; *a != *a2; *a += da) {
		*b = floor(realb + 0.5);

		if (flags & SDL_TG_ANTIALIAS) {
			aa = (realb + 0.5 - *b) * alpha;
			SDL_blendPixel(surf, x + xaa, y + yaa, clr, (int)aa);
			SDL_blendPixel(surf, x - xaa, y - yaa, clr, alpha - (int)aa);
		}

		if (alpha < 255)
			SDL_blendPixel(surf, x, y, clr, alpha);
		else
			SDL_putPixel(surf, x, y, clr);

		realb += db;
	}

	if (flags & SDL_TG_LOCK)
		__SDL_PRIM_UNLOCKSURFACE(surf);
}



/*
 * Circle drawing/filling function.  Uses a more optimized algorithm
 * when drawing non-anti-aliased circles, uses a mathematically correct algo
 * when drawing anti-aliased circles.  After much trial and error, I have
 * removed 100% of pixel overlapping, no more spots in anti-aliased circles.
 *
 */
void SDL_drawCircle_TG( SDL_Surface *surf, int x1, int y1, int r, Uint32 clr,
                        Uint8 alpha, Uint8 flags )
{
	int x = 0, tmp, dy = 0, y = r, r2, aa = 0, fy = 0;
	int r2p = r * r;
	float aatmp;

	#define __CIRCLE_OUTAA \
	{ \
		if (x) { \
			SDL_blendPixel(surf, x1 + x, y1 + y + 1, clr, aa); \
			SDL_blendPixel(surf, x1 + x, y1 - y - 1, clr, aa); \
		} \
		SDL_blendPixel(surf, x1 - x, y1 + y + 1, clr, aa); \
		SDL_blendPixel(surf, x1 - x, y1 - y - 1, clr, aa); \
	}

	#define __CIRCLE_INAA \
	{ \
		if (x) { \
			SDL_blendPixel(surf, x1 + x, y1 + y - 1, clr, alpha - aa); \
			SDL_blendPixel(surf, x1 + x, y1 - y + 1, clr, alpha - aa); \
		} \
		SDL_blendPixel(surf, x1 - x, y1 + y - 1, clr, alpha - aa); \
		SDL_blendPixel(surf, x1 - x, y1 - y + 1, clr, alpha - aa); \
	}

	#define __CIRCLE_OUTAA_ROT90 \
	{ \
		if (x) { \
			SDL_blendPixel(surf, x1 + y + 1, y1 + x, clr, aa); \
			SDL_blendPixel(surf, x1 - y - 1, y1 - x, clr, aa); \
		} \
		SDL_blendPixel(surf, x1 - y - 1, y1 + x, clr, aa); \
		SDL_blendPixel(surf, x1 + y + 1, y1 - x, clr, aa); \
	}

	#define __CIRCLE_INAA_ROT90 \
	{ \
		if (x) { \
			SDL_blendPixel(surf, x1 + y - 1, y1 + x, clr, alpha - aa); \
			SDL_blendPixel(surf, x1 - y + 1, y1 - x, clr, alpha - aa); \
		} \
		SDL_blendPixel(surf, x1 - y + 1, y1 + x, clr, alpha - aa); \
		SDL_blendPixel(surf, x1 + y - 1, y1 - x, clr, alpha - aa); \
	}

	#define __CIRCLE_BLEND \
	{ \
		if (x) { \
			SDL_blendPixel(surf, x1 + x, y1 + y, clr, alpha); \
			SDL_blendPixel(surf, x1 + x, y1 - y, clr, alpha); \
		} \
		SDL_blendPixel(surf, x1 - x, y1 + y, clr, alpha); \
		SDL_blendPixel(surf, x1 - x, y1 - y, clr, alpha); \
	}

	#define __CIRCLE_BLEND_ROT90 \
	{ \
		if (x) { \
			SDL_blendPixel(surf, x1 - y, y1 + x, clr, alpha); \
			SDL_blendPixel(surf, x1 + y, y1 + x, clr, alpha); \
		} \
		SDL_blendPixel(surf, x1 + y, y1 - x, clr, alpha); \
		SDL_blendPixel(surf, x1 - y, y1 - x, clr, alpha); \
	}

	#define __CIRCLE_DRAW \
	{ \
		if (x) { \
			SDL_putPixel(surf, x1 + x, y1 + y, clr); \
			SDL_putPixel(surf, x1 + x, y1 - y, clr); \
		} \
		SDL_putPixel(surf, x1 - x, y1 + y, clr); \
		SDL_putPixel(surf, x1 - x, y1 - y, clr); \
	}

	#define __CIRCLE_DRAW_ROT90 \
	{ \
		if (x) { \
			SDL_putPixel(surf, x1 - y, y1 + x, clr); \
			SDL_putPixel(surf, x1 + y, y1 + x, clr); \
		} \
		SDL_putPixel(surf, x1 + y, y1 - x, clr); \
		SDL_putPixel(surf, x1 - y, y1 - x, clr); \
	}

	if (!(alpha < 255 && (flags & SDL_TG_ALPHA)))
		alpha = 255;

	if (r < 6)
		r2 = 0;
	else
		r2 = r / 2;

	if (flags & SDL_TG_LOCK)
		__SDL_PRIM_LOCKSURFACE(surf);

	if (flags & SDL_TG_FILL) {
		if (alpha < 255)
			SDL_blendPixel(surf, x1, y1, clr, alpha);
		else
			SDL_putPixel(surf, x1, y1, clr);
	}

	do {
		if (alpha < 255) {
			__CIRCLE_BLEND
			__CIRCLE_BLEND_ROT90
		} else {
			__CIRCLE_DRAW
			__CIRCLE_DRAW_ROT90
		}

		if (flags & SDL_TG_FILL) {
			/* Fill the circle... */
			if (alpha < 255) {
				for (fy = y - 1; fy >= (x ? x : 1); fy--) {
					if (x) {
						SDL_blendPixel(surf, x1 - x, y1 + fy, clr, alpha);
						SDL_blendPixel(surf, x1 - x, y1 - fy, clr, alpha);
					}
					SDL_blendPixel(surf, x1 + x, y1 + fy, clr, alpha);
					SDL_blendPixel(surf, x1 + x, y1 - fy, clr, alpha);

					if (fy > x) {
						if (x) {
							SDL_blendPixel(surf, x1 - fy, y1 + x, clr, alpha);
							SDL_blendPixel(surf, x1 + fy, y1 + x, clr, alpha);
						}
						SDL_blendPixel(surf, x1 - fy, y1 - x, clr, alpha);
						SDL_blendPixel(surf, x1 + fy, y1 - x, clr, alpha);
					}
				}
			} else {
				for (fy = y - 1; fy >= (x ? x : 1); fy--) {
					if (x) {
						SDL_putPixel(surf, x1 - x, y1 + fy, clr);
						SDL_putPixel(surf, x1 - x, y1 - fy, clr);
					}
					SDL_putPixel(surf, x1 + x, y1 + fy, clr);
					SDL_putPixel(surf, x1 + x, y1 - fy, clr);

					if (fy > x) {
						if (x) {
							SDL_putPixel(surf, x1 - fy, y1 + x, clr);
							SDL_putPixel(surf, x1 + fy, y1 + x, clr);
						}
						SDL_putPixel(surf, x1 - fy, y1 - x, clr);
						SDL_putPixel(surf, x1 + fy, y1 - x, clr);
					}
				}
			}
		}

		if (flags & SDL_TG_ANTIALIAS) {
			/*
			 * anti-aliased circle algorithm.  this is
			 * mathematically correct, but fairly expensive.
			 * looks extremely good though.
			 *
			 * this is also very simple, we simply find the y
			 * value with respect to each x value, using a circle
			 * equation.  the anti-alias values are based on the
			 * real y coordinates.
			 */
			__CIRCLE_OUTAA
			__CIRCLE_OUTAA_ROT90
			if (!(flags & SDL_TG_FILL)) {
				__CIRCLE_INAA
				if (x < y-1)
					__CIRCLE_INAA_ROT90
			}

			aatmp = sqrt(r2p - x*x);
			y = aatmp;
			aatmp = aatmp - (int)aatmp;
			aa = aatmp * alpha;
			x++;
		} else {
			/*
			 * i came up with this circle drawing algorithm on my
			 * own.  i don't know how it compares to other circle
			 * drawing algos as far as optimization, but this seems
			 * to work pretty well.  it's also pretty damn
			 * simple, which is what i'm all about.
			 */

			/* move along x axis */
			x++;

			/*
			 * adjust y if we're moving too far out of the circle.
			 * the r2 is simply the radius over 2, unless the
			 * radius is less than a particular value(above).
			 * this simply makes the circle look more accurate,
			 * as we're working with integers so it's never exact,
			 * and we don't want to change the y value unless we're
			 * going off by a particular amount.  this also more
			 * closely approximates the above, mathematically
			 * correct, algo.
			 */
			if (dy)
				y += dy;
			tmp = (r2p - x*x - y*y + r2);
			if (tmp < 0)
				dy = -1;
			else if (dy)
				dy = 0;
		}
	} while (x < y);

	if (x == y) {
		if (alpha < 255)
			__CIRCLE_BLEND
		else
			__CIRCLE_DRAW

		if (flags & SDL_TG_ANTIALIAS) {
			__CIRCLE_OUTAA
			__CIRCLE_OUTAA_ROT90
		}
	}
	else if (flags & SDL_TG_ANTIALIAS)
		__CIRCLE_OUTAA

	if (flags & SDL_TG_LOCK)
		__SDL_PRIM_UNLOCKSURFACE(surf);
}


/*
 * draws triangles
 *
 * this isn't very good, there is a good amount of pixel overlapping, 
 * and there are sometimes pixels dangling off the corners...
 *
 * bleh, fuck it, i'll fix it later
 *
 */
void SDL_drawTriangle_TG(SDL_Surface *surf, int x1, int y1, int x2, int y2,
                         int x3, int y3, Uint32 clr, Uint8 alpha, Uint8 flags)
{
	int x, y, c, xlen, ylen, i, aaoffset;
	float a1, b1, a2, b2, slopea, slopeb, aa, aastep, pb1, pb2;
	float slope1, slope2, slope3;

	/* avoid compiler warnings... */
	aa = 0.0;
	aastep = 0.0;
	aaoffset = 0;

	#define __TRI_DRAW(PB, B, SLOPE) \
	{ \
		if (fabs(SLOPE) > 1.0) { \
			c = (PB) > (B) ? -1 : 1; \
			if (flags & SDL_TG_ANTIALIAS) { \
				aastep = alpha / fabs(SLOPE); \
				aa = alpha; \
			} \
			for (y = (PB); ( c > 0 ? y < floor(B) : y > (B) ); y += c) { \
				if (alpha < 255) \
					SDL_blendPixel(surf, x, y, clr, alpha); \
				else \
					SDL_putPixel(surf, x, y, clr); \
				if (flags & SDL_TG_ANTIALIAS) { \
					SDL_blendPixel(surf, x - 1, y, clr, aa); \
					SDL_blendPixel(surf, x + 1, y, clr, alpha - aa); \
					aa -= aastep; \
				} \
			} \
		} else { \
			if (alpha < 255) \
				SDL_blendPixel(surf, x, (B), clr, alpha); \
			else \
				SDL_putPixel(surf, x, (B), clr); \
			if (flags & SDL_TG_ANTIALIAS) { \
				aa = (B) - (int)(B); \
				aa *= alpha; \
				SDL_blendPixel(surf, x, (B) + 1, clr, aa); \
				SDL_blendPixel(surf, x, (B) - 1, clr, alpha - aa); \
			} \
		} \
	}

	#define __TRI_FILL_AA(PB, B, B2, SLOPE) \
	{ \
		if (fabs(SLOPE) > 2.0) { \
			c = (SLOPE > 0) ? 1 : -1; \
			if (SLOPE > 0) { \
				if (SLOPE == slope3) { \
					aaoffset = 0; \
					aa = 0; \
					aastep = alpha / SLOPE; \
				} \
				else { \
					aaoffset = -1; \
					aa = alpha; \
					aastep = alpha / SLOPE * -1; \
				} \
			} \
			else { \
				if (SLOPE == slope3) { \
					aaoffset = 0; \
					aa = 0; \
					aastep = alpha / SLOPE * -1; \
				} \
				else { \
					aaoffset = -1; \
					aa = alpha; \
					aastep = alpha / SLOPE; \
				} \
			} \
			for (y = (PB) + c; ( c > 0 ? y < floor(B) : y > (B) ); y += c) { \
				SDL_blendPixel(surf, x + aaoffset, y, clr, aa); \
				aa += aastep; \
			} \
		} else { \
			if (B > B2) \
				c = 1; \
			else \
				c = -1; \
			aa = fabs( (B) - (int)(B) ); \
			if (c < 0) \
				aa = alpha - aa * alpha; \
			else \
				aa *= alpha; \
			SDL_blendPixel(surf, x, (B) + c, clr, aa); \
		} \
	}

	if ( !(flags & SDL_TG_ALPHA && alpha < 255) )
		alpha = 255;

	/*
	 * sort points by X coordinate so we can split the drawing
	 * at a slope change
	 */
	while ( !(x1 <= x2 && x1 <= x3) ) {
		x = x1;
		y = y1;
		x1 = x2;
		y1 = y2;
		x2 = x3;
		y2 = y3;
		x3 = x;
		y3 = y;
	}
	while ( !(x2 <= x3) ) {
		x = x2;
		y = y2;
		x2 = x3;
		y2 = y3;
		x3 = x;
		y3 = y;
	}

	xlen = x3 - x1;
	ylen = y3 - y1;
	slope1 = (float)ylen / xlen;

	xlen = x1 - x2;
	ylen = y1 - y2;
	slope2 = (float)ylen / xlen;

	xlen = x2 - x3;
	ylen = y2 - y3;
	slope3 = (float)ylen / xlen;

	a1 = x1;
	a2 = x2;
	b1 = y1;
	b2 = y1;
	slopea = slope1;
	slopeb = slope2;

	pb1 = b1;
	pb2 = b2;

	if (flags & SDL_TG_LOCK)
		__SDL_PRIM_LOCKSURFACE(surf);

	for (i = 0; i <= 1; i++) {
		if ((a1 == x1 && a1 != a2) || (a1 != x1 && a1 - 1 != a2)) {
			for (x = a1; x <= a2; x++) {
				if (flags & SDL_TG_FILL) {
					c = ((int)b1 < (int)b2) ? 1 : -1;
					for (y = (int)b1; (c > 0 ? y <= (int)b2 : y >= (int)b2); y += c) {
						if (alpha < 255)
							SDL_blendPixel(surf, x, y, clr, alpha);
						else
							SDL_putPixel(surf, x, y, clr);
					}
					if (flags & SDL_TG_ANTIALIAS) {
						__TRI_FILL_AA(pb1, b1, b2, slopea)
						__TRI_FILL_AA(pb2, b2, b1, slopeb)
					}
				}
				else {
					__TRI_DRAW(pb1, b1, slopea)
					__TRI_DRAW(pb2, b2, slopeb)
				}
				pb1 = b1;
				pb2 = b2;

				b1 += slopea;
				b2 += slopeb;
			}
		} else if (!(flags & SDL_TG_FILL)) {
			if (a1 == x1)
				SDL_drawLine_TG(surf, x1,y1, x2,y2, clr, alpha, flags);
			else
				SDL_drawLine_TG(surf, x2,y2, x3,y3, clr, alpha, flags);
		}

		a1 = x2 + 1;
		a2 = x3;
		b2 = y2;
		slopeb = slope3;
		b2 += slopeb;
	}

	if (flags & SDL_TG_LOCK)
		__SDL_PRIM_LOCKSURFACE(surf);
}


