#include "iface.h"
#include <assert.h>

namespace voxel
{
	void ZGame::clear()
	{
		for (int a=0; a<YSIZE; a++)
			for (int b=0; b<XSIZE; b++)
			{
				zbuf[a][b] = 10001.f;
				pixbuf[a][b][0] = 0;
				pixbuf[a][b][1] = 0;
				pixbuf[a][b][2] = 0;
			}
	}

	void ZGame::pset( int x, int y, float z, uchar r, uchar g, uchar b )
	{
		//assert( 0 <= x && x < XSIZE );
		//assert( 0 <= y && y < YSIZE );
		if (!( 0 <= x && x < XSIZE && 0 <= y && y < YSIZE )) return;
		if (z < zbuf[y][x])
		{
			zbuf[y][x] = z;
			pixbuf[y][x][0] = r;
			pixbuf[y][x][1] = g;
			pixbuf[y][x][2] = b;
		}
	}

	void ZGame::set_cam( int alpha, int beta )
	{
		static float PI = acos(-1.f);
		float sin_a = sin(PI*alpha/180);
		float cos_a = cos(PI*alpha/180);
		float sin_b = sin(PI*beta/180);
		float cos_b = cos(PI*beta/180);

		cam = T( 1.5f*FIELD_N*sin_a*cos_b, 1.5f*FIELD_N*cos_a*cos_b, 1.5f*FIELD_N*sin_b );
		dir = T( -cam.x, -cam.y, -cam.z );
		dir.normalize();

		cam = T( cam.x + FIELD_N*0.5f, cam.y + FIELD_N*0.5f, cam.z );
	}

	T ZGame::p_to_scr( T p )
	{
		// по нормальному надо умножать вектор на матрицу
		// но мы же извращенцы...
		static float xsz_inv = 1.f/XSIZE;

		T aa = T( p.x - cam.x, p.y - cam.y, p.z - cam.z );
		float xx = ( aa.y*bb.x-aa.x*bb.y )/( aa.x*cc.y-aa.y*cc.x );
		float t;
		if (fabs(aa.x) > EPS) t = (bb.x + cc.x*xx)/aa.x;
		else t = (bb.y + cc.y*xx)/aa.y;
		float yy = (t*aa.z - bb.z)/cc.z;

		return T( xx, yy, aa.z/(bb.z + cc.z*yy) );
	}

	float ZGame::fast_rsqrt( float number )
	{
		// http://en.wikipedia.org/wiki/Fast_inverse_square_root
		// ну вы знаете...
        long i;
		float x2, y;
		const float threehalfs = 1.5f;
		
		x2 = number * 0.5f;
		y  = number;
		i  = * ( long * ) &y;
		i  = 0x5f3759df - ( i >> 1 );
		y  = * ( float * ) &i;
		y  = y * ( threehalfs - ( x2 * y * y ) );
		return y;
	}

	void ZGame::draw_ball( T ce, float R, uchar r, uchar g, uchar b )
	{
		// находим границы шара
		int x1 = XSIZE-1, x2 = 0, y1 = YSIZE-1, y2 = 0;
		for (int a=0; a<2; a++)
			for (int b=0; b<2; b++)
				for (int c=0; c<2; c++)
				{
					T q = p_to_scr( T( ce.x-R+R*2*a, ce.y-R+R*2*b, ce.z-R+R*2*c ) );
					x1 = std::min( x1, (int)q.x );
					x2 = std::max( x2, (int)q.x );
					y1 = std::min( y1, (int)q.y );
					y2 = std::max( y2, (int)q.y );
				}
		x1 = std::max( x1, 0 );
		x2 = std::min( x2, XSIZE-1 );
		y1 = std::max( y1, 0 );
		y2 = std::min( y2, YSIZE-1 );

		// для каждой точки в прямоугольнике пробуем найти точку на шаре
		// если нашли - рисуем
		T aa = T( ce.x - cam.x, ce.y - cam.y, ce.z - cam.z );
		float C = aa.x*aa.x + aa.y*aa.y + aa.z*aa.z - R*R;
		for (int i=x1; i<=x2; i++)
			for (int j=y1; j<=y2; j++)
			{
				float vx = bb.x + cc.x*i, vy = bb.y + cc.y*i, vz = bb.z + cc.z*j;
				float A = vx*vx + vy*vy + vz*vz;
				float B = aa.x*vx + aa.y*vy + aa.z*vz;
				float D = B*B - A*C;
				if (D > EPS)
				{
					float T = (B - D*fast_rsqrt(D)) / A;
					pset( i, YSIZE-j-1, T, r, g, b );
				}
			}
	}

	void ZGame::draw_line( T A, T B, uchar r, uchar g, uchar b )
	{
		T aa = p_to_scr( A );
		T bb = p_to_scr( B );

		// модифицированный брезенхема
		int x1 = (int)aa.x, y1 = (int)aa.y, x2 = (int)bb.x, y2 = (int)bb.y;
		int deltaX = abs(x2 - x1);
		int deltaY = abs(y2 - y1);
		int signX = x1 < x2 ? 1 : -1;
		int signY = y1 < y2 ? 1 : -1;

		int error = deltaX - deltaY;

		//pset(x2, YSIZE-y2-1, bb.z, r, g, b);
		pset(x2, YSIZE-y2-1, 0.f, r, g, b);
		while(x1 != x2 || y1 != y2)
		{
			float tt;
			if (deltaX > 0) tt = (float)abs(x1 - x2) / deltaX;
			else tt = (float)abs(y1 - y2) / deltaY;

			//pset(x1, YSIZE-y1-1, bb.z + (aa.z-bb.z)*tt, r, g, b);
			pset(x1, YSIZE-y1-1, 0.f, r, g, b);
			int error2 = error * 2;

			if(error2 > -deltaY)
			{
				error -= deltaY;
				x1 += signX;
			}
			if(error2 < deltaX)
			{
				error += deltaX;
				y1 += signY;
			}
		}
	}

	void ZGame::render()
	{
		clear();

		static float xsz_inv = 1.f/XSIZE;
		bb = T(dir.x + dir.y, dir.y - dir.x, dir.z - YSIZE*xsz_inv);
		cc = T(-dir.y*2*xsz_inv, dir.x*2*xsz_inv, 2*xsz_inv);

		for (int i=0; i<XSIZE; i++) // идем по строчкам
		{
			float vx = bb.x + cc.x*i;
			float vy = bb.y + cc.y*i;
			float vx_inv = 1.f/vx;
			float vy_inv = 1.f/vy;

			// находим все клетки, которые находятся на луче (cam.x,cam.y) - (vx,vy)
			// рассматриваем 4 случая, которые я объединил всего в 2
			int sz = 0;
			if (fabs(vx) > fabs(vy))
			{
				// случай 1
				int be=0, en=FIELD_N, de=1;
				if (vx < 0)
				{
					be = FIELD_N;
					en = 0;
					de = -1;
				}

				for (int j=be; j!=en; j+=de)
				{
					int xx = j - (de==-1 ? 1 : 0);
					float tmp = vy*((float)j - cam.x)*vx_inv + cam.y;
					int yy = -1;
					if (tmp > 0) yy = (int)tmp;
					if (0 <= yy && yy < FIELD_N)
					{
						X[sz] = xx;
						Y[sz] = yy;
						sz++;
					}
					tmp = vy*((float)(j + de) - cam.x)*vx_inv + cam.y;
					yy = -1;
					if (tmp > 0) yy = (int)tmp;
					if (0 <= yy && yy < FIELD_N)
						if (!(sz>0 && X[sz-1]==xx && Y[sz-1]==yy))
						{
							X[sz] = xx;
							Y[sz] = yy;
							sz++;
						}
				}
			}
			else
			{
				// случай 2
				int be=0, en=FIELD_N, de=1;
				if (vy < 0)
				{
					be = FIELD_N;
					en = 0;
					de = -1;
				}

				for (int j=be; j!=en; j+=de)
				{
					int yy = j - (de==-1 ? 1 : 0);
					float tmp = vx*((float)(j) - cam.y)*vy_inv + cam.x;
					int xx = -1;
					if (tmp > 0) xx = (int)tmp;
					if (0 <= xx && xx < FIELD_N)
					{
						X[sz] = xx;
						Y[sz] = yy;
						sz++;
					}
					tmp = vx*((float)(j + de) - cam.y)*vy_inv + cam.x;
					xx = -1;
					if (tmp > 0) xx = (int)tmp;
					if (0 <= xx && xx < FIELD_N)
						if (!(sz>0 && X[sz-1]==(int)xx && Y[sz-1]==yy))
						{
							X[sz] = (int)xx;
							Y[sz] = yy;
							sz++;
						}
				}
			}

			/*int yy=0;
			for (int j = 0; j < sz; j ++)
				if (h[X[j]][Y[j]] > yy)
				{
					uchar cc = clr[X[j]][Y[j]][0];
					for (int k = yy; k < h[X[j]][Y[j]]; k++)
						pset( i, YSIZE-k-1, 1.f, pal[cc][0], pal[cc][1], pal[cc][2] );
					yy = h[X[j]][Y[j]];
				}*/

			// теперь идем по всем клеткам на луче
			// и рендерим снизу вверх
			// как только какой-то столбик закончился - переходим к следующему и тд
			int yyy = 0;
			for (int j = 0; j < sz; j ++)
			{
				float t = 1e9; // ближайшее пересечение
				float t2 = -1e9; // последнее пересечение
				if (fabs(vx) > EPS)
				{
					float tt, yy;
					tt = ((float)X[j] - cam.x) * vx_inv;
					yy = cam.y + tt*vy;
					if (Y[j]-EPS < yy && yy < Y[j]+1+EPS)
					{
						t = std::min( tt, t );
						t2 = std::max( tt, t2 );
					}
					tt = ((float)(X[j]+1) - cam.x) * vx_inv;
					yy = cam.y + tt*vy;
					if (Y[j]-EPS < yy && yy < Y[j]+1+EPS)
					{
						t = std::min( tt, t );
						t2 = std::max( tt, t2 );
					}
				}

				if (fabs(vy) > EPS)
				{
					float tt, xx;
					tt = ((float)Y[j] - cam.y) * vy_inv;
					xx = cam.x + tt*vx;
					if (X[j]-EPS < xx && xx < X[j]+1+EPS)
					{
						t = std::min( tt, t );
						t2 = std::max( tt, t2 );
					}
					tt = ((float)(Y[j]+1) - cam.y) * vy_inv;
					xx = cam.x + tt*vx;
					if (X[j]-EPS < xx && xx < X[j]+1+EPS)
					{
						t = std::min( tt, t );
						t2 = std::max( tt, t2 );
					}
				}

				// тут мы, собственно, идем снизу вверх
				while(1)
				{
					if (yyy >= YSIZE) break;

					float vz = bb.z + cc.z*yyy;
					float z = cam.z + vz*t;
					float z2 = cam.z + vz*t2;
					
					if (z < 0.f || (z2 < 0.f && h[X[j]][Y[j]]==0))
					{
						// море
						pset( i, YSIZE-yyy-1, t, 0, 0, vz>0 ? 0 : 128 );
						yyy++;
					}
					else if ((int)z < h[X[j]][Y[j]])
					{
						// ближайший край
						uchar C = clr[X[j]][Y[j]][(int)z];
						pset( i, YSIZE-yyy-1, t, pal[C][0], pal[C][1], pal[C][2] );
						yyy++;
					}
					else if ((int)z2 < h[X[j]][Y[j]])
					{
						// верхушка столбика
						// t2 немного не то... ну всем пофиг
						uchar C = top[X[j]][Y[j]];
						pset( i, YSIZE-yyy-1, t2, pal[C][0], pal[C][1], pal[C][2] );
						yyy++;
					}
					else break; // столбик кончился, идем к следующему
				}
			}
			while (yyy < YSIZE) // дозаполняем после последнего столбика - небо там или море
			{
				float vz = bb.z + cc.z*yyy;
				pset( i, YSIZE-yyy-1, 10000.f, 0, 0, vz>0 ? 0 : 128 );
				yyy++;
			}
		}

		for (int a=0; a<256; a++)
			for (int b=0; b<10; b++)
				pset( b, a, 1000.f, pal[a][0], pal[a][1], pal[a][2] );

		/*for (int a=0; a<=2; a++)
			for (int b=0; b<=2; b++)
				for (int c=0; c<=2; c++)
					draw_ball( T( a*FIELD_N*0.5f, b*FIELD_N*0.5f, c*FIELD_H*0.5f ), 10.f, 255, 255, 128 );
		*/

		/*for (int a=0; a<8; a++)
			for (int b=0; b<8; b++)
				if ( (((a>>2)^(b>>2))&1) + (((a>>1)^(b>>1))&1) + ((a^b)&1) == 1 ) // если различие ровно в 1 бите
					draw_line(
						T( (a>>2) * FIELD_N * 1.f, ((a>>1)&1) * FIELD_N * 1.f, (a&1) * FIELD_H * 1.f ),
						T( (b>>2) * FIELD_N * 1.f, ((b>>1)&1) * FIELD_N * 1.f, (b&1) * FIELD_H * 1.f ),
						255, 255, 255 );*/

		for (int a=0; a<WIND_N; a++)
		{
			T p = p_to_scr( wind[a] );
			pset( (int)p.x, YSIZE-(int)p.y-1, p.z, 255, 255, 255 );
			p = wind[a];
			p = T( p.x+wx*2.f, p.y+wy*2.f, p.z-0.1f );
			if (p.x < 0.f) p.x += FIELD_N*1.f;
			if (p.x > FIELD_N*1.f) p.x -= FIELD_N*1.f;
			if (p.y < 0.f) p.y += FIELD_N*1.f;
			if (p.y > FIELD_N*1.f) p.y -= FIELD_N*1.f;
			if (p.z < 0.f) p.z += FIELD_H*1.f;
			wind[a] = p;
		}

		static float pi = acos(-1.f);

		for (int a=0; a<2; a++)
		{
			T ce = T( pp[a].pos_x+0.5f, pp[a].pos_y+0.5f, h[pp[a].pos_x][pp[a].pos_y]*1.f + 1.f );
			draw_ball( ce, 2.f, pp[a].r, pp[a].g, pp[a].b );
			for (int b=0; b<8; b++)
				draw_line(
					T( ce.x + sin( b*pi*0.25f )*5.f, ce.y + cos( b*pi*0.25f )*5.f, ce.z ),
					T( ce.x + sin( (b+1)*pi*0.25f )*5.f, ce.y + cos( (b+1)*pi*0.25f )*5.f, ce.z ),
					pp[a].r, pp[a].g, pp[a].b );

			if (1 <= pp[a].weapon && pp[a].weapon <= 4)
			{
				float d = pp[a].power*0.25f;
				if (pp[a].weapon == 4) d = 50.f;
				draw_line( ce, T( ce.x, ce.y+10.f, ce.z ), 255, 255, 255 );
				float aa = pp[a].alpha*pi/180.f, bb = pp[a].beta*pi/180.f;
				T p2 = T( sin( aa )*cos( bb ), cos( aa )*cos( bb ), sin( bb ) );
				float tt = fast_rsqrt( p2.x*p2.x + p2.y*p2.y + p2.z*p2.z );
				T p3 = T( ce.x + p2.x*tt*d, ce.y + p2.y*tt*d, ce.z + p2.z*tt*d );
				draw_line( ce, p3, 255, 255, 255 );
				draw_line( ce, T( p3.x, p3.y, ce.z ), 255, 255, 255 );
				draw_line( p3, T( p3.x, p3.y, ce.z ), 255, 255, 255 );

				draw_line( T( p3.x-1.f, p3.y, p3.z ), T( p3.x+1.f, p3.y, p3.z ), 255, 255, 255 );
				draw_line( T( p3.x, p3.y-1.f, p3.z ), T( p3.x, p3.y+1.f, p3.z ), 255, 255, 255 );
				draw_line( T( p3.x, p3.y, p3.z-1.f ), T( p3.x, p3.y, p3.z+1.f ), 255, 255, 255 );
			}
			else if (pp[a].weapon == 5)
			{
				draw_line( T( pp[a].X-10.f, pp[a].Y*1.f, FIELD_H*1.f ),
					T( pp[a].X+10.f, pp[a].Y*1.f, FIELD_H*1.f ), 255, 255, 255 );
				draw_line( T( pp[a].X*1.f, pp[a].Y-10.f, FIELD_H*1.f ),
					T( pp[a].X*1.f, pp[a].Y+10.f, FIELD_H*1.f ), 255, 255, 255 );
			}
		}

		for (int a=0; a<(int)balls.size(); a++)
			if (balls[a].cur_time < balls[a].life_time)
			{
				uchar r, g, b;
				balls[a].get_color( r, g, b );
				draw_ball( balls[a].c, balls[a].r, r, g, b );
			}
	}

	uchar * ZGame::get_pixbuf()
	{
		return (uchar *)pixbuf;
	}
};