
#include "common/endian.h"
#include "graphics/tinygl/zbuffer.h"

namespace TinyGL {

#define ZCMP(z, zpix) ((z) >= (zpix))

void ZB_fillTriangleFlat(ZBuffer *zb, ZBufferPoint *p0, ZBufferPoint *p1, ZBufferPoint *p2) {
	int color;

#define INTERP_Z

#define DRAW_INIT()	{							\
	color = RGB_TO_PIXEL(p2->r, p2->g, p2->b);	\
}

#define PUT_PIXEL(_a) {						\
	if (ZCMP(z, pz[_a])) {					\
	pp[_a] = color;							\
	pz[_a] = z;								\
	}										\
	z += dzdx;								\
}

#include "graphics/tinygl/ztriangle.h"
}

// Smooth filled triangle.
// The code below is very tricky :)

void ZB_fillTriangleSmooth(ZBuffer *zb, ZBufferPoint *p0, ZBufferPoint *p1, ZBufferPoint *p2) {
	int _drgbdx;

#define INTERP_Z
#define INTERP_RGB

#define SAR_RND_TO_ZERO(v,n) (v / (1 << n))

#define DRAW_INIT() {											\
	_drgbdx = (SAR_RND_TO_ZERO(drdx, 6) << 22) & 0xFFC00000;	\
	_drgbdx |= SAR_RND_TO_ZERO(dgdx, 5) & 0x000007FF;			\
	_drgbdx |= (SAR_RND_TO_ZERO(dbdx, 7) << 12) & 0x001FF000; 	\
}

#define PUT_PIXEL(_a) {						\
	if (ZCMP(z, pz[_a])) {					\
		tmp = rgb & 0xF81F07E0;				\
		pp[_a] = tmp | (tmp >> 16);			\
		pz[_a] = z;							\
	}										\
	z += dzdx;								\
	rgb = (rgb + drgbdx) & (~0x00200800);	\
}

#define DRAW_LINE()	{								\
	register unsigned int *pz;						\
	register PIXEL *pp;								\
	register unsigned int z, rgb, drgbdx;			\
	register int n;									\
	n = (x2 >> 16) - x1;							\
	pp = pp1 + x1;									\
	pz = pz1 + x1;									\
	z = z1;											\
	rgb =(r1 << 16) & 0xFFC00000;					\
	rgb |= (g1 >> 5) & 0x000007FF;					\
	rgb |= (b1 << 5) & 0x001FF000;					\
	drgbdx = _drgbdx;								\
	while (n >= 3) {								\
		PUT_PIXEL(0);								\
		PUT_PIXEL(1);								\
		PUT_PIXEL(2);								\
		PUT_PIXEL(3);								\
		pz += 4;									\
		pp += 4;									\
		n -= 4;										\
	}												\
	while (n >= 0) {								\
		PUT_PIXEL(0);								\
		pz += 1;									\
		pp += 1;									\
		n -= 1;										\
	}												\
}

#include "graphics/tinygl/ztriangle.h"
}

void ZB_setTexture(ZBuffer *zb, const Graphics::PixelBuffer &texture) {
	zb->current_texture=texture;
}

void ZB_fillTriangleMapping(ZBuffer *zb, ZBufferPoint *p0, ZBufferPoint *p1, ZBufferPoint *p2) {
	Graphics::PixelBuffer texture;

#define INTERP_Z
#define INTERP_ST

#define DRAW_INIT()	{				\
	texture = zb->current_texture;	\
}

#define PUT_PIXEL(_a) {						\
	if (ZCMP(z, pz[_a])) {					\
		pp[_a] = texture.getRawBuffer()[((t & 0x3FC00000) | s) >> 14];	\
		pz[_a] = z;							\
	}										\
	z += dzdx;								\
	s += dsdx;								\
	t += dtdx;								\
}

#include "graphics/tinygl/ztriangle.h"
}

void ZB_fillTriangleMappingPerspective(ZBuffer *zb, ZBufferPoint *p0, ZBufferPoint *p1, ZBufferPoint *p2) {
	Graphics::PixelBuffer texture;
	float fdzdx, fndzdx, ndszdx, ndtzdx;
	int _drgbdx;

#define NB_INTERP 8

	ZBufferPoint *tp, *pr1 = 0, *pr2 = 0, *l1 = 0, *l2 = 0;
	float fdx1, fdx2, fdy1, fdy2, fz0, d1, d2;
	unsigned int *pz1;
	int part, update_left, update_right;

	int nb_lines, dx1, dy1, tmp, dx2, dy2;

	int error = 0, derror = 0;
	int x1 = 0, dxdy_min = 0, dxdy_max = 0;
	// warning: x2 is multiplied by 2^16
	int x2 = 0, dx2dy2 = 0;

	int z1 = 0, dzdx, dzdy, dzdl_min = 0, dzdl_max = 0;

	int r1 = 0, drdx, drdy, drdl_min = 0, drdl_max = 0;
	int g1 = 0, dgdx, dgdy, dgdl_min = 0, dgdl_max = 0;
	int b1 = 0, dbdx, dbdy, dbdl_min = 0, dbdl_max = 0;

	float sz1 = 0.0, dszdx, dszdy, dszdl_min = 0.0, dszdl_max = 0.0;
	float tz1 = 0.0, dtzdx, dtzdy, dtzdl_min = 0.0, dtzdl_max = 0.0;

	// we sort the vertex with increasing y
	if (p1->y < p0->y) {
		tp = p0;
		p0 = p1;
		p1 = tp;
	}
	if (p2->y < p0->y) {
		tp = p2;
		p2 = p1;
		p1 = p0;
		p0 = tp;
	} else if (p2->y < p1->y) {
		tp = p1;
		p1 = p2;
		p2 = tp;
	}

	// we compute dXdx and dXdy for all interpolated values

	fdx1 = (float)(p1->x - p0->x);
	fdy1 = (float)(p1->y - p0->y);

	fdx2 = (float)(p2->x - p0->x);
	fdy2 = (float)(p2->y - p0->y);

	fz0 = fdx1 * fdy2 - fdx2 * fdy1;
	if (fz0 == 0)
		return;
	fz0 = (float)(1.0 / fz0);

	fdx1 *= fz0;
	fdy1 *= fz0;
	fdx2 *= fz0;
	fdy2 *= fz0;

	d1 = (float)(p1->z - p0->z);
	d2 = (float)(p2->z - p0->z);
	dzdx = (int)(fdy2 * d1 - fdy1 * d2);
	dzdy = (int)(fdx1 * d2 - fdx2 * d1);

	d1 = (float)(p1->r - p0->r);
	d2 = (float)(p2->r - p0->r);
	drdx = (int)(fdy2 * d1 - fdy1 * d2);
	drdy = (int)(fdx1 * d2 - fdx2 * d1);

	d1 = (float)(p1->g - p0->g);
	d2 = (float)(p2->g - p0->g);
	dgdx = (int) (fdy2 * d1 - fdy1 * d2);
	dgdy = (int) (fdx1 * d2 - fdx2 * d1);

	d1 = (float)(p1->b - p0->b);
	d2 = (float)(p2->b - p0->b);
	dbdx = (int)(fdy2 * d1 - fdy1 * d2);
	dbdy = (int)(fdx1 * d2 - fdx2 * d1);

	{
		float zz;
		zz = (float)p0->z;
		p0->sz = (float)p0->s * zz;
		p0->tz = (float)p0->t * zz;
		zz = (float)p1->z;
		p1->sz = (float)p1->s * zz;
		p1->tz = (float)p1->t * zz;
		zz = (float)p2->z;
		p2->sz = (float)p2->s * zz;
		p2->tz = (float)p2->t * zz;

		d1 = p1->sz - p0->sz;
		d2 = p2->sz - p0->sz;
		dszdx = (fdy2 * d1 - fdy1 * d2);
		dszdy = (fdx1 * d2 - fdx2 * d1);

		d1 = p1->tz - p0->tz;
		d2 = p2->tz - p0->tz;
		dtzdx = (fdy2 * d1 - fdy1 * d2);
		dtzdy = (fdx1 * d2 - fdx2 * d1);
	}

	// screen coordinates

	byte *pp1 = zb->pbuf.getRawBuffer() + zb->linesize * p0->y;
	pz1 = zb->zbuf + p0->y * zb->xsize;

	texture = zb->current_texture;
	fdzdx = (float)dzdx;
	fndzdx = NB_INTERP * fdzdx;
	ndszdx = NB_INTERP * dszdx;
	ndtzdx = NB_INTERP * dtzdx;
	_drgbdx = ((drdx / (1 << 6)) << 22) & 0xFFC00000;
	_drgbdx |= (dgdx / (1 << 5)) & 0x000007FF;
	_drgbdx |= ((dbdx / (1 << 7)) << 12) & 0x001FF000;

	for (part = 0; part < 2; part++) {
		if (part == 0) {
			if (fz0 > 0) {
				update_left = 1;
				update_right = 1;
				l1 = p0;
				l2 = p2;
				pr1 = p0;
				pr2 = p1;
			} else {
				update_left = 1;
				update_right = 1;
				l1 = p0;
				l2 = p1;
				pr1 = p0;
				pr2 = p2;
			}
			nb_lines = p1->y - p0->y;
		} else {
			// second part
			if (fz0 > 0) {
				update_left = 0;
				update_right = 1;
				pr1 = p1;
				pr2 = p2;
			} else {
				update_left = 1;
				update_right = 0;
				l1 = p1;
				l2 = p2;
			}
			nb_lines = p2->y - p1->y + 1;
		}

		// compute the values for the left edge

		if (update_left) {
			dy1 = l2->y - l1->y;
			dx1 = l2->x - l1->x;
			if (dy1 > 0)
				tmp = (dx1 << 16) / dy1;
			else
				tmp = 0;
			x1 = l1->x;
			error = 0;
			derror = tmp & 0x0000ffff;
			dxdy_min = tmp >> 16;
			dxdy_max = dxdy_min + 1;

			z1 = l1->z;
			dzdl_min = (dzdy + dzdx * dxdy_min);
			dzdl_max = dzdl_min + dzdx;

			r1 = l1->r;
			drdl_min = (drdy + drdx * dxdy_min);
			drdl_max = drdl_min + drdx;

			g1 = l1->g;
			dgdl_min = (dgdy + dgdx * dxdy_min);
			dgdl_max = dgdl_min + dgdx;

			b1 = l1->b;
			dbdl_min = (dbdy + dbdx * dxdy_min);
			dbdl_max = dbdl_min + dbdx;

			sz1 = l1->sz;
			dszdl_min = (dszdy + dszdx * dxdy_min);
			dszdl_max = dszdl_min + dszdx;

			tz1 = l1->tz;
			dtzdl_min = (dtzdy + dtzdx * dxdy_min);
			dtzdl_max = dtzdl_min + dtzdx;
		}

		// compute values for the right edge

		if (update_right) {
			dx2 = (pr2->x - pr1->x);
			dy2 = (pr2->y - pr1->y);
			if (dy2 > 0)
				dx2dy2 = (dx2 << 16) / dy2;
			else
				dx2dy2 = 0;
			x2 = pr1->x << 16;
		}

		// we draw all the scan line of the part

		while (nb_lines > 0) {
			nb_lines--;
			{
				register unsigned int *pz;
				register unsigned int s, t, z, rgb, drgbdx;
				register int n, dsdx, dtdx;
				float sz, tz, fz, zinv;
				n = (x2 >> 16) - x1;
				fz = (float)z1;
				zinv = (float)(1.0 / fz);

				Graphics::PixelBuffer buf = zb->pbuf;
				buf = pp1 + x1 * PSZB;

				pz = pz1 + x1;
				z = z1;
				sz = sz1;
				tz = tz1;
				rgb = (r1 << 16) & 0xFFC00000;
				rgb |= (g1 >> 5) & 0x000007FF;
				rgb |= (b1 << 5) & 0x001FF000;
				drgbdx = _drgbdx;
				while (n >= (NB_INTERP - 1)) {
					{
						float ss, tt;
						ss = sz * zinv;
						tt = tz * zinv;
						s = (int)ss;
						t = (int)tt;
						dsdx = (int)((dszdx - ss * fdzdx) * zinv);
						dtdx = (int)((dtzdx - tt * fdzdx) * zinv);
						fz += fndzdx;
						zinv = (float)(1.0 / fz);
					}
					for (int _a = 0; _a < 8; _a++) {
						if (ZCMP(z, pz[_a])) {
							unsigned ttt = (t & 0x003FC000) >> (9 - PSZSH);
							unsigned sss = (s & 0x003FC000) >> (17 - PSZSH);
							int pixel = ((ttt | sss) >> 1) ;

							uint8 alpha, c_r, c_g, c_b;
							texture.getARGBAt(pixel, alpha, c_r, c_g, c_b);
							if (alpha == 0xFF) {
								tmp = rgb & 0xF81F07E0;
								unsigned int light = tmp | (tmp >> 16);
								unsigned int l_r = (light & 0xF800) >> 8;
								unsigned int l_g = (light & 0x07E0) >> 3;
								unsigned int l_b = (light & 0x001F) << 3;
								c_r = (c_r * l_r) / 256;
								c_g = (c_g * l_g) / 256;
								c_b = (c_b * l_b) / 256;
								buf.setPixelAt(_a, c_r, c_g, c_b);
								pz[_a] = z;
							}
						}
						z += dzdx;
						s += dsdx;
						t += dtdx;
						rgb = (rgb + drgbdx) & (~0x00200800);
					}

					pz += NB_INTERP;
					buf.shiftBy(NB_INTERP);
					n -= NB_INTERP;
					sz += ndszdx;
					tz += ndtzdx;
				}

				{
					float ss, tt;
					ss = sz * zinv;
					tt = tz * zinv;
					s = (int)ss;
					t = (int)tt;
					dsdx = (int)((dszdx - ss * fdzdx) * zinv);
					dtdx = (int)((dtzdx - tt * fdzdx) * zinv);
				}

				while (n >= 0) {
					{
						if (ZCMP(z, pz[0])) {
							unsigned ttt = (t & 0x003FC000) >> (9 - PSZSH);
							unsigned sss = (s & 0x003FC000) >> (17 - PSZSH);
							int pixel = ((ttt | sss) >> 1) ;

							uint8 alpha, c_r, c_g, c_b;
							texture.getARGBAt(pixel, alpha, c_r, c_g, c_b);
							if (alpha == 0xFF) {
								tmp = rgb & 0xF81F07E0;
								unsigned int light = tmp | (tmp >> 16);
								unsigned int l_r = (light & 0xF800) >> 8;
								unsigned int l_g = (light & 0x07E0) >> 3;
								unsigned int l_b = (light & 0x001F) << 3;
								c_r = (c_r * l_r) / 256;
								c_g = (c_g * l_g) / 256;
								c_b = (c_b * l_b) / 256;
								buf.setPixelAt(0, c_r, c_g, c_b);
								pz[0] = z;
							}
						}
						z += dzdx;
						s += dsdx;
						t += dtdx;
						rgb = (rgb + drgbdx) & (~0x00200800);
					}
					pz += 1;
					buf.shiftBy(1);
					n -= 1;
				}
			}

			// left edge
			error += derror;
			if (error > 0) {
				error -= 0x10000;
				x1 += dxdy_max;

				z1 += dzdl_max;

				r1 += drdl_max;
				g1 += dgdl_max;
				b1 += dbdl_max;

				sz1 += dszdl_max;
				tz1 += dtzdl_max;
			} else {
				x1 += dxdy_min;

				z1 += dzdl_min;

				r1 += drdl_min;
				g1 += dgdl_min;
				b1 += dbdl_min;

				sz1 += dszdl_min;
				tz1 += dtzdl_min;
			}

			// right edge
			x2 += dx2dy2;

			// screen coordinates
			pp1 += zb->linesize;
			pz1 += zb->xsize;
		}
	}
}

} // end of namespace TinyGL
