#include <math.h>
#include "aadraw.h"
#include "defines.h"


// Draws antialiased circle with desired thickness and antialiasing strength.
// Parameters:
//   canvas - destination bitmap
//   x, y - centre of the circle
//   r - circle radius
//   thickness - thickness of the circle edge; may be less than 1.0
//   col - outline colour
//   aa - antialiasing strength; 0 means no antialiasing; 2.0 and above gives good results
//
// Note: optimized for 32bpp by using _getpixel32 instead of getpixel etc.
void aa_circle(BITMAP *canvas, int x, int y, int r, float thickness, int col, float aa) {
	// get the bounding box of the circle
	int y1 = (int)(y - r - thickness - aa);
	int y2 = (int)(y + r + thickness + aa);
	int x1 = (int)(x - r - thickness - aa);
	int x2 = (int)(x + r + thickness + aa);

	if (x1 < canvas->cl) x1 = canvas->cl;
	if (y1 < canvas->ct) y1 = canvas->ct;
	if (x2 > canvas->cr) x2 = canvas->cr;
	if (y2 > canvas->cb) y2 = canvas->cb;

	// for all pixels in the bounding box
	for (int yy = y1; yy < y2; yy++) {
		for (int xx = x1; xx < x2; xx++) {
			// opacity of this pixel (between 0 and 1)
			float alpha = 0.0f;

			// distance between current point and centre of the circle
			float d2 = sqrt((xx - x)*(xx - x) + (yy - y)*(yy - y));

			// See where the current pixel is:
			//   1. current pixel is less than the 'thickness/2' away from the circle
			//      edge -> draw the pixel with full opacity
			//   2. current pixel is more than 'thickness/2' away from circle edge but
			//      still inside the antialiasing zone (aa/2) -> calculate alpha
			//      opacity for this pixel
			//   3. same as 2. but for the inner edge of the circle
			//   4. current pixel is either in side or outside the circle comeletely ->
			//      do nothing
			if (d2 <= r + thickness*0.5 && d2 >= r - thickness*0.5) {
				// plot pixel
				_putpixel32(canvas, xx, yy, col);

				// we're done with this pixel
				continue;
			}
			else if (d2 > r + thickness*0.5 && d2 <= r + thickness*0.5 + aa*0.5) {
				// outside edge of the circle; alpha is proportional to the distance
				// of the current pixel from the circle edge
				alpha = fabs((r + thickness*0.5 - d2)/(aa*0.5));
			}
			else if (d2 < r - thickness*0.5 && d2 >= r - thickness*0.5 - aa*0.5) {
				// inside edge of the circle
				alpha = fabs((r - thickness*0.5 - d2)/(aa*0.5));
			}
			else {
				// there's nothing to be drawn here
				continue;
			}

			// inverse of the alpha opacity
			float alpha_minus_one = 1.0 - alpha;

			// get pixel from canvas
			int pc = _getpixel32(canvas, xx, yy);

			// break into RGB components
			int r1 = getr32(pc);
			int g1 = getg32(pc);
			int b1 = getb32(pc);

			// break drawing colour into RGB components
			int r2 = getr32(col);
			int g2 = getg32(col);
			int b2 = getb32(col);

			// blend the two pixels
			r1 = (int)(alpha * r1 + alpha_minus_one * r2);
			g1 = (int)(alpha * g1 + alpha_minus_one * g2);
			b1 = (int)(alpha * b1 + alpha_minus_one * b2);
			pc = makecol32(r1,g1,b1);

			// write new pixel to destination
			_putpixel32(canvas, xx, yy, pc);
		}
	}
}


// Draws a filled antialiased circle with antialiasing strength.
// Parameters:
//   canvas - destination bitmap
//   x, y - centre of the circle
//   r - circle radius
//   col - circle colour
//   aa - antialiasing strength; 0 means no antialiasing; 2.0 and above gives good results
//
// Note: optimized for 32bpp by using _getpixel32 instead of getpixel etc.
void aa_circlefill(BITMAP *canvas, int x, int y, float r, int col, float aa) {
	// get the bounding box of the circle
	int y1 = (int)(y - r - aa);
	int y2 = (int)(y + r + aa);
	int x1 = (int)(x - r - aa);
	int x2 = (int)(x + r + aa);

	if (x1 < canvas->cl) x1 = canvas->cl;
	if (y1 < canvas->ct) y1 = canvas->ct;
	if (x2 > canvas->cr) x2 = canvas->cr;
	if (y2 > canvas->cb) y2 = canvas->cb;

	// for all pixels in the bounding box
	for (int yy = y1; yy < y2; yy++) {
		for (int xx = x1; xx < x2; xx++) {
			// opacity of this pixel (between 0 and 1)
			float alpha = 0.0f;

			// distance between current point and centre of the circle
			float d2 = sqrt((xx - x)*(xx - x) + (yy - y)*(yy - y));

			// See where the current pixel is:
			//   1. current pixel is inside the circle -> draw the pixel with full opacity
			//   2. current pixel is on the circle edge inside the antialiasing
			//		zone (aa/2) -> calculate alpha opacity for this pixel
			//   3. current pixel is outside the circle comeletely -> do nothing
			if (d2 <= r) {
				// plot pixel
				_putpixel32(canvas, xx, yy, col);

				// we're done with this pixel
				continue;
			}
			else if (d2 > r && d2 <= r + aa*0.5) {
				// outside edge of the circle; alpha is proportional to the distance
				// of the current pixel from the circle edge
				alpha = fabs((r + - d2)/(aa*0.5));
			}
			else {
				// there's nothing to be drawn here
				continue;
			}

			// inverse of the alpha opacity
			float alpha_minus_one = 1.0 - alpha;

			// get pixel from canvas
			int pc = _getpixel32(canvas, xx, yy);

			// break into RGB components
			int r1 = getr32(pc);
			int g1 = getg32(pc);
			int b1 = getb32(pc);

			// break drawing colour into RGB components
			int r2 = getr32(col);
			int g2 = getg32(col);
			int b2 = getb32(col);

			// blend the two pixels
			r1 = (int)(alpha * r1 + alpha_minus_one * r2);
			g1 = (int)(alpha * g1 + alpha_minus_one * g2);
			b1 = (int)(alpha * b1 + alpha_minus_one * b2);
			pc = makecol32(r1,g1,b1);

			// write new pixel to destination
			_putpixel32(canvas, xx, yy, pc);
		}
	}
}


// Draws antialiased ellipse with desired thickness and antialiasing strength.
// Parameters:
//   canvas - destination bitmap
//   xa, ya, xb, yb - control points of the ellipse
//   r - ellipse radius (average distance of every point on the ellipse from the control points)
//   thickness - thickness of the ellipse edge; may be less than 1.0
//   col - outline colour
//   aa - antialiasing strength; 0 means no antialiasing; 2.0 and above gives good results
//
// Note: optimized for 32bpp by using _getpixel32 instead of getpixel etc.
void aa_ellipse(BITMAP *canvas, int xa, int ya, int xb, int yb, int r, float thickness, int col, float aa) {
	// get the bounding box of the ellipse
	int tmpx = r - abs(xb-xa)/2;
	int tmpy = r - abs(yb-ya)/2;
	int y1 = (int)(MIN(ya,yb) - tmpy - thickness - aa);
	int y2 = (int)(MAX(ya,yb) + tmpy + thickness + aa);
	int x1 = (int)(MIN(xa,xb) - tmpx - thickness - aa);
	int x2 = (int)(MAX(xa,xb) + tmpx + thickness + aa);

	if (x1 < canvas->cl) x1 = canvas->cl;
	if (y1 < canvas->ct) y1 = canvas->ct;
	if (x2 > canvas->cr) x2 = canvas->cr;
	if (y2 > canvas->cb) y2 = canvas->cb;

	// for all pixels in the bounding box
	for (int yy = y1; yy < y2; yy++) {
		for (int xx = x1; xx < x2; xx++) {
			// opacity of this pixel (between 0 and 1)
			float alpha = 0.0f;

			// distance between current point and the ellipse control points
			float d2 = sqrt((xx - xa)*(xx - xa) + (yy - ya)*(yy - ya));
			d2 += sqrt((xx - xb)*(xx - xb) + (yy - yb)*(yy - yb));
			d2 /= 2;

			// See where the current pixel is:
			//   1. current pixel is less than the 'thickness/2' away from the ellipse
			//      edge -> draw the pixel with full opacity
			//   2. current pixel is more than 'thickness/2' away from ellipse edge but
			//      still inside the antialiasing zone (aa/2) -> calculate alpha
			//      opacity for this pixel
			//   3. same as 2. but for the inner edge of the ellipse
			//   4. current pixel is either inside or outside the ellipse comeletely ->
			//      do nothing
			if (d2 <= r + thickness*0.5 && d2 >= r - thickness*0.5) {
				// plot pixel
				_putpixel32(canvas, xx, yy, col);

				// we're done with this pixel
				continue;
			}
			else if (d2 > r + thickness*0.5 && d2 <= r + thickness*0.5 + aa*0.5) {
				// outside edge of the ellipse; alpha is proportional to the distance
				// of the current pixel from the ellipse edge
				alpha = fabs((r + thickness*0.5 - d2)/(aa*0.5));
			}
			else if (d2 < r - thickness*0.5 && d2 >= r - thickness*0.5 - aa*0.5) {
				// inside edge of the ellipse
				alpha = fabs((r - thickness*0.5 - d2)/(aa*0.5));
			}
			else {
				// there's nothing to be drawn here
				continue;
			}

			// inverse of the alpha opacity
			float alpha_minus_one = 1.0 - alpha;

			// get pixel from canvas
			int pc = _getpixel32(canvas, xx, yy);

			// break into RGB components
			int r1 = getr32(pc);
			int g1 = getg32(pc);
			int b1 = getb32(pc);

			// break drawing colour into RGB components
			int r2 = getr32(col);
			int g2 = getg32(col);
			int b2 = getb32(col);

			// blend the two pixels
			r1 = (int)(alpha * r1 + alpha_minus_one * r2);
			g1 = (int)(alpha * g1 + alpha_minus_one * g2);
			b1 = (int)(alpha * b1 + alpha_minus_one * b2);
			pc = makecol32(r1,g1,b1);

			// write new pixel to destination
			_putpixel32(canvas, xx, yy, pc);
		}
	}
}


void aa_line(BITMAP *canvas, int x1, int y1, int x2, int y2, float thickness, int col, float aa, int caps) {
	static int points[8];
	float dx = (float)x2 - (float)x1;
	float dy = (float)y2 - (float)y1;
	float l2 = dx*dx + dy*dy;
	float l = sqrt(l2);
	float thickness2 = thickness*0.5f;

	if (fabs(l) <= 0.0001f) return;

	int dx3 = (int)(dy*thickness2/l);
	int dy3 = (int)(dx*thickness2/l);

	points[0] = x1 + dx3;
	points[1] = y1 - dy3;
	points[2] = x1 - dx3;
	points[3] = y1 + dy3;
	points[4] = x2 - dx3;
	points[5] = y2 + dy3;
	points[6] = x2 + dx3;
	points[7] = y2 - dy3;

	//polygon(canvas, 4, points, c);

	int minX = 10000;
	int minY = 10000;
	int maxX = 0;
	int maxY = 0;

	// find bounding box
	for (int i=0; i<8; i++) {
		if (points[i] < minX) minX = points[i];
		if (points[i] > maxX) maxX = points[i];
		++i;
		if (points[i] < minY) minY = points[i];
		if (points[i] > maxY) maxY = points[i];
	}

	minX -= (int)ceil(aa);
	minY -= (int)ceil(aa);
	maxX += (int)ceil(aa);
	maxY += (int)ceil(aa);

	if (minX < canvas->cl) minX = canvas->cl;
	if (minY < canvas->ct) minY = canvas->ct;
	if (maxX > canvas->cr) maxX = canvas->cr;
	if (maxY > canvas->cb) maxY = canvas->cb;

	// for all pixels in the bounding box
	for (int yy = minY; yy < maxY; yy++) {
		for (int xx = minX; xx < maxX; xx++) {
			// opacity of this pixel (between 0 and 1)
			float alpha = 0.0f;

			float u = ((xx - x1)*dx + (yy - y1)*dy) / l2;
			if (u < 0.0f || u > 1.0f) continue;

			float x = x1 + u*dx;
			float y = y1 + u*dy;

			// distance between current point and the line
			float d2 = sqrt((xx-x)*(xx-x) + (yy-y)*(yy-y));

			// See where the current pixel is:
			//   1. current pixel is less than the 'thickness/2' away from the line
			//      edge -> draw the pixel with full opacity
			//   2. current pixel is more than 'thickness/2' away from the line but
			//      still inside the antialiasing zone (aa/2) -> calculate alpha
			//      opacity for this pixel
			//   3. current pixel is outside the line -> do nothing
			if (d2 <= thickness2) {
				// plot pixel
				_putpixel32(canvas, xx, yy, col);

				// we're done with this pixel
				continue;
			}
			else if (d2 <= (thickness + aa)*0.5) {
				// alpha is proportional to the distance
				// of the current pixel from the line edge
				alpha = fabs((thickness2 - d2)/(aa*0.5));
			}
			else {
				// there's nothing to be drawn here
				continue;
			}

			// inverse of the alpha opacity
			float alpha_minus_one = 1.0 - alpha;

			// get pixel from canvas
			int pc = _getpixel32(canvas, xx, yy);

			// break into RGB components
			int r1 = getr32(pc);
			int g1 = getg32(pc);
			int b1 = getb32(pc);

			// break drawing colour into RGB components
			int r2 = getr32(col);
			int g2 = getg32(col);
			int b2 = getb32(col);

			// blend the two pixels
			r1 = (int)(alpha * r1 + alpha_minus_one * r2);
			g1 = (int)(alpha * g1 + alpha_minus_one * g2);
			b1 = (int)(alpha * b1 + alpha_minus_one * b2);
			pc = makecol32(r1,g1,b1);

			// write new pixel to destination
			_putpixel32(canvas, xx, yy, pc);
		}
	}

	if (caps & VGFX_CAPS_FIRST) aa_circlefill(canvas, x1, y1, thickness2, col, aa);
	if (caps & VGFX_CAPS_LAST)  aa_circlefill(canvas, x2, y2, thickness2, col, aa);
}
