#ifndef _TPFDRAW_C
#define _TPFDRAW_C

#include <stdio.h>

#include <stdlib.h>
#include "draw.h"
#include "screen.h"
#include "f1616.h"
#include "vec.h"
#include "map.h"
#include "ang.h"
#include "tex.h"
#include "obj.h"

void TpfDraw_ClearDrawList(tpfScreen *s)
{
	int x;
	tpfDrawObj *m;
	for (x=0;x<s->x;x++)
		while (s->drawlist[x] != 0)
		{
			m = s->drawlist[x];
			s->drawlist[x] = m->next;
			free(m);
		}
}

void TpfDraw_AddToDrawList(tpfScreen *s,tpfTex *t,int x,f1616 y,f1616 yscale,f1616 z,int texx)
{
	/* x=screen x (pixels)
	   y=world location of top row of screen
	   yscale=block change per screen row
	   z=distance (blocks)
	   texx = texture x position (pixels) */
	tpfDrawObj *next,*prev,*no;
	prev = 0;
	next = s->drawlist[x];
	for (;;)
	{
		if (next == 0)
			break;
		if (next->z < z)
			break;
		prev = next;
		next = prev->next;
	}
	no = (tpfDrawObj *) malloc(sizeof(tpfDrawObj));
	no->next = next;
	no->prev = prev;
	if (next)
		next->prev = no;
	if (prev)
		prev->next = no;
	else
		s->drawlist[x] = no;
	no->texline = t->s[texx];
	no->y = 0x400000-(y << 6); /* Need it in pixels, not blocks */
	no->yscale = yscale << 6;
	no->z = z;
}

void TpfDraw_DoDrawList(tpfScreen *s)
{
	TpfDraw_ARM_DoDrawList(s->x,s->y,s->stride,s->buffer,s->drawlist);
	TpfDraw_ClearDrawList(s);
}

void TpfDraw_Draw(tpfScreen *s,tpfMap *m)
{
	int x; /* Current screen x */
	f1616 z; /* Distance moved so far */
	tpfVec pos,dir; /* Current pos & ray dir */
	tpfVec recdir; /* Reciprocal dir */
	tpfVec dirperpix; /* Ray dir change per x */
	int f; /* Stop flag */
	f1616 tx,ty; /* Current Z left until walls */
	int texx;
	int side; /* Side we have hit */
	f1616 z_x,z_y; /* Z for each x/y step */
	tpfVec tempdir;
	int bx,by; /* Current block */
	int flags;

	tpfAng degpery; /* Degrees per screen line */
	f1616 yscale; /* Y scale for textures */

	/* Need to work out relative position & rotation
	   Subtract m->org from s->pos
	   Then rotate s->pos about the origin by -m->ang
	   Then subtract m->ang from all angles in s */

	tpfVec rel_org;
	tpfAng rel_fovleft,rel_fovright;
	rel_org = s->pos;
	TpfVec_Sub(&rel_org,TpfMap_GetPos(m));
	/* Rotate... */
	TpfVec_Rotate(&rel_org,TpfAng_Neg(TpfMap_GetRot(m)));
	rel_fovleft = TpfAng_Sub(s->fovleft,TpfMap_GetRot(m));
	rel_fovright = TpfAng_Sub(s->fovright,TpfMap_GetRot(m));

	degpery = TpfAng_Sub(s->fovtop,s->fovbottom);
	degpery = TpfAng_Div(degpery,s->y);
	yscale = TpfAng_sin(degpery);
	dir.x = TpfAng_cos(rel_fovleft);
	dir.y = TpfAng_sin(rel_fovleft);
	dir.z = TpfAng_sin(s->fovtop);
	dirperpix.x = TpfAng_cos(rel_fovright);
	dirperpix.y = TpfAng_sin(rel_fovright);
	dirperpix.x -= dir.x;
	dirperpix.y -= dir.y;
	dirperpix.z = 0;
	TpfVec_Div2(&dirperpix,f1616_FromInt(s->x));
	for (x=0;x<s->x;x++)
	{
		pos = rel_org;
		recdir.x = f1616_div(65536,dir.x);
		recdir.y = f1616_div(65536,dir.y);
		z = 0;
		z_x = recdir.x;
		z_y = recdir.y;
		if (z_x < 0)
			z_x = -z_x;
		if (z_y < 0)
			z_y = -z_y;
		bx = f1616_ToInt(pos.x);
		by = f1616_ToInt(pos.y);
		/* Now work out current distances to next wall... */
		if (dir.x > 0)
			tx = f1616_mul(65536 - (pos.x & 0xFFFF),recdir.x);
		else if (dir.x < 0)
			tx = f1616_mul(-(pos.x & 0xFFFF),recdir.x);
		else
			tx = 0x7FFFFFFF;
		if (dir.y > 0)
			ty = f1616_mul(65536 - (pos.y & 0xFFFF),recdir.y);
		else if (dir.y < 0)
			ty = f1616_mul(-(pos.y & 0xFFFF),recdir.y);
		else
			ty = 0x7FFFFFFF;
		do
		{
			f = 0;
			/* Work out which wall to go to */
			if (tx < ty)
			{
				if (dir.x > 0)
				{
					side = TPFMAP_WALL_W;
					bx++;
				}
				else
				{
					side = TPFMAP_WALL_E;
					bx--;
				}
				z += tx;
				ty -= tx; /* Moved this much closer to wall in that dir */
				tx = z_x; /* Reset dist to next wall */
			}
			else
			{
				if (dir.y > 0)
				{
					side = TPFMAP_WALL_S;
					by++;
				}
				else
				{
					side = TPFMAP_WALL_N;
					by--;
				}
				z += ty;
				tx -= ty;
				ty = z_y;
			}
			/* Now draw whatever we've hit */
			if ((s->zbuffer[x] > z) && (bx >= 0) && (bx < m->x) && (by >= 0) && (by < m->y))
			{
				f = TpfMap_GetSide(m,bx,by,side);
				flags = TpfMap_GetWallFlags(m,f);
				if ((flags & TPFWALL_TEXTURE) && (TpfMap_GetWallTex(m,f) != 0))
				{
					/* Work out pos */
					tempdir = dir;
					TpfVec_Mul2(&tempdir,z);
					TpfVec_Add(&tempdir,&pos);
					if (side == TPFMAP_WALL_N)
						texx = 63 - ((tempdir.x>>10) & 63);
					else if (side == TPFMAP_WALL_E)
						texx = (tempdir.y>>10) & 63;
					else if (side == TPFMAP_WALL_S)
						texx = (tempdir.x>>10) & 63;
					else
						texx = 63 - ((tempdir.y>>10) & 63);
					TpfDraw_AddToDrawList(s,TpfMap_GetWallTex(m,f),x,tempdir.z,f1616_mul(yscale,z),z,texx);
				}
				if (flags & TPFWALL_STOPRAY)
				{
					f = 1;
					s->zbuffer[x] = z;
				}
				else
					f = 0;
			}
			/* Check for edge of level... */
			if ((dir.x >= 0) && (bx > m->x+1))
				f = 1;
			else if ((dir.x <= 0) && (bx < 0))
				f = 1;
			else if ((dir.y >= 0) && (by > m->y+1))
				f = 1;
			else if ((dir.y <= 0) && (by < 0))
				f = 1;
			else if (s->zbuffer[x] < z)
				f = 1;
		} while (f == 0);
		TpfVec_Add(&dir,&dirperpix);
	}
}

void TpfDraw_DrawObj(tpfScreen *s,tpfObj *o)
{
	/* Draw an object */

	/* Subtract s->pos from o->pos
	   Rotate o->pos by -s->ang (where s->ang is average of fovleft & right)
	   Subtract s->ang from o->rot
	   x offset will be distance to object
	   y offset will be h-pos on screen */
	tpfVec pos,posa;
	tpfAng half_screen,screen_mid,rel_rot;
	f1616 scr_lx,scr_rx,tmp,scr_y,yscale;
	f1616 texx;
	int sidenum;
	float ftmp;
	if (o->numsides == 0)
		return;
	pos = *TpfObj_GetPos(o);
	TpfVec_Sub(&pos,&(s->pos));
	half_screen = TpfAng_Sub(s->fovleft,s->fovright);
	half_screen = TpfAng_Div(half_screen,2);
	screen_mid = TpfAng_Add(half_screen,s->fovright);
	TpfVec_Rotate(&pos,TpfAng_Neg(screen_mid));
	/* Account for the quick distance measurements used in the map drawing code
	   Map drawer uses vector interpolation to measure distance (resulting in larger measurements) while this code uses the 'true' distance */
	posa.x = (TpfAng_cos(s->fovleft)+TpfAng_cos(s->fovright))/2;
	posa.y = (TpfAng_sin(s->fovleft)+TpfAng_sin(s->fovright))/2;
	posa.z = 0; /* posa's length should now be the length of the vector used in the map drawer */
	pos.x = f1616_div(pos.x,TpfVec_Len(&posa));

	rel_rot = TpfAng_Sub(TpfObj_GetAng(o),screen_mid);
	/* Now work out exactly where it appears on screen */
	if (pos.x <= 0)
		return; /* Behind player */
	scr_lx = pos.y+32768;
	scr_rx = pos.y-32768;
	/* Work out where the edges of the view area will lie at distance pos.x */
	tmp = f1616_mul(TpfAng_sin(half_screen),pos.x*2);
	scr_lx = 32768-f1616_div(scr_lx,tmp);
	scr_rx = 32768-f1616_div(scr_rx,tmp);

	if ((scr_rx < 0) || (scr_lx >= 65536))
		return; /* Off screen */
	scr_lx = scr_lx*s->x;
	scr_rx = scr_rx*s->x;
	/* Do similar for y location & scale
	   We want top & bottom view area locations */
	yscale = f1616_mul(TpfAng_sin(TpfAng_Div(TpfAng_Sub(s->fovtop,s->fovbottom),s->y)),pos.x); /* Same calculation as for level draw code */

	scr_y = f1616_mul(TpfAng_sin(s->fovtop),pos.x) - pos.z;

	/* Work out which texture to use
	   diff ang
	   0    180
	   90   90
	   180  0
	   270  270 (-90)
	   180-diff, rounded to 0-360
	   add (360/sides)/2 (i.e. 180/sides)
	   then divide by 360/sides */
	rel_rot = TpfAng_Sub(TpfAng_FromDeg(180),rel_rot);
	ftmp = 180/o->numsides;
	rel_rot = TpfAng_Add(rel_rot,TpfAng_FromDeg(ftmp));
	ftmp = (float) TpfAng_ToDeg(rel_rot);
	if (ftmp < 0)
		ftmp = ftmp+360;
	ftmp = ftmp*o->numsides;
	sidenum = ftmp/360;
	if (sidenum >= o->numsides)
		sidenum = sidenum % o->numsides;
	if (o->sides[sidenum] == 0)
		return;

	/* Draw! */
	tmp = f1616_div(0x400000,scr_rx-scr_lx); /* Changes in object x per screen pos */
	texx = 0;
	if (scr_rx >= s->x << 16)
		scr_rx = s->x << 16;
	while (scr_lx<scr_rx)
	{
		if (scr_lx >= 0)
			TpfDraw_AddToDrawList(s,o->sides[sidenum],scr_lx >> 16,scr_y,yscale,pos.x,texx >> 16);
		texx += tmp;
		scr_lx += 65536;
	}
}

#endif
