//Copyright Paul Reiche, Fred Ford. 1992-2002

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "process.h"

#include "races.h"
#include "collide.h"
#include "options.h"
#include "settings.h"
#include "setup.h"
#include "sounds.h"
#include "hyper.h"
#include "element.h"
#include "battle.h"
#include "weapon.h"
#include "libs/graphics/drawable.h"
#include "libs/graphics/drawcmd.h"
#include "libs/graphics/gfx_common.h"
#include "libs/log.h"
#include "libs/misc.h"


//#define DEBUG_PROCESS

COUNT DisplayFreeList;
PRIMITIVE DisplayArray[MAX_DISPLAY_PRIMS];
extern POINT SpaceOrg;

SIZE zoom_out = 1 << ZOOM_SHIFT;
static SIZE opt_max_zoom_out;

#if 0
static inline void
CALC_ZOOM_STUFF (COUNT* idx, COUNT* sc)
{
	int i, z;

	z = 1 << ZOOM_SHIFT;
	for (i = 0; (z <<= 1) <= zoom_out; i++)
		;
	*idx = i;
	*sc = ((1 << i) << (ZOOM_SHIFT + 8)) / zoom_out;
}
#else
static inline void
CALC_ZOOM_STUFF (COUNT* idx, COUNT* sc)
{
	int i;

	if (zoom_out < 2 << ZOOM_SHIFT)
		i = 0;
	else if (zoom_out < 4 << ZOOM_SHIFT)
		i = 1;
	else
		i = 2;
	*idx = i;
	*sc = (1 << (i + ZOOM_SHIFT + 8)) / zoom_out;
}
#endif

HELEMENT
AllocElement (void)
{
	HELEMENT hElement;

	hElement = AllocLink (&disp_q);
	if (hElement)
	{
		ELEMENT *ElementPtr;

		LockElement (hElement, &ElementPtr);
		memset (ElementPtr, 0, sizeof (*ElementPtr));
		ElementPtr->PrimIndex = AllocDisplayPrim ();
		if (ElementPtr->PrimIndex == END_OF_LIST)
		{
			log_add (log_Error, "AllocElement: Out of display prims!");
			explode ();
		}
		SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
		UnlockElement (hElement);
	}

	return (hElement);
}

void
FreeElement (HELEMENT hElement)
{
	if (hElement)
	{
		ELEMENT *ElementPtr;

		LockElement (hElement, &ElementPtr);
		FreeDisplayPrim (ElementPtr->PrimIndex);
		UnlockElement (hElement);

		FreeLink (&disp_q, hElement);
	}
}

void
SetUpElement (ELEMENT *ElementPtr)
{
	ElementPtr->next = ElementPtr->current;
	if (CollidingElement (ElementPtr))
	{
		InitIntersectStartPoint (ElementPtr);
		InitIntersectEndPoint (ElementPtr);
		InitIntersectFrame (ElementPtr);
	}
}

static void
PreProcess (ELEMENT *ElementPtr)
{
	ELEMENT_FLAGS state_flags;

	if (ElementPtr->life_span == 0)
	{
		if (ElementPtr->pParent) /* untarget this dead element */
			Untarget (ElementPtr);

		ElementPtr->state_flags |= DISAPPEARING;
		if (ElementPtr->death_func)
			(*ElementPtr->death_func) (ElementPtr);
	}

	state_flags = ElementPtr->state_flags;
	if (!(state_flags & DISAPPEARING))
	{
		if (state_flags & APPEARING)
		{
			SetUpElement (ElementPtr);

			if (state_flags & PLAYER_SHIP)
				state_flags &= ~APPEARING; /* want to preprocess ship */
		}

		if (ElementPtr->preprocess_func && !(state_flags & APPEARING))
		{
			(*ElementPtr->preprocess_func) (ElementPtr);

			state_flags = ElementPtr->state_flags;
			if ((state_flags & CHANGING) && CollidingElement (ElementPtr))
				InitIntersectFrame (ElementPtr);
		}

		if (!(state_flags & IGNORE_VELOCITY))
		{
			SIZE delta_x, delta_y;

			GetNextVelocityComponents (&ElementPtr->velocity,
					&delta_x, &delta_y, 1);
			if (delta_x != 0 || delta_y != 0)
			{
				state_flags |= CHANGING;
				ElementPtr->next.location.x += delta_x;
				ElementPtr->next.location.y += delta_y;
			}
		}

		if (CollidingElement (ElementPtr))
			InitIntersectEndPoint (ElementPtr);

		if (state_flags & FINITE_LIFE)
			--ElementPtr->life_span;
	}

	ElementPtr->state_flags = (state_flags & ~(POST_PROCESS | COLLISION))
			| PRE_PROCESS;
}

static void
PostProcess (ELEMENT *ElementPtr)
{
	if (ElementPtr->postprocess_func)
		(*ElementPtr->postprocess_func) (ElementPtr);
	ElementPtr->current = ElementPtr->next;

	if (CollidingElement (ElementPtr))
	{
		InitIntersectStartPoint (ElementPtr);
		InitIntersectEndPoint (ElementPtr);
	}

	ElementPtr->state_flags = (ElementPtr->state_flags
			& ~(PRE_PROCESS | CHANGING | APPEARING))
			| POST_PROCESS;
}

static COUNT
CalcReduction (SIZE dx, SIZE dy)
{
	COUNT next_reduction;

#ifdef KDEBUG
	log_add (log_Debug, "CalcReduction:");
#endif

	if (optMeleeScale == TFB_SCALE_STEP)
	{
		SIZE sdx, sdy;

		if (LOBYTE (GLOBAL (CurrentActivity)) > IN_ENCOUNTER)
			return (0);

		sdx = dx;
		sdy = dy;
		for (next_reduction = MAX_VIS_REDUCTION;
				(dx <<= REDUCTION_SHIFT) <= TRANSITION_WIDTH
				&& (dy <<= REDUCTION_SHIFT) <= TRANSITION_HEIGHT
				&& next_reduction > 0;
				next_reduction -= REDUCTION_SHIFT)
			;

				/* check for "real" zoom in */
		if (next_reduction < zoom_out
				&& zoom_out <= MAX_VIS_REDUCTION)
		{
#define HYSTERESIS_X DISPLAY_TO_WORLD(24)
#define HYSTERESIS_Y DISPLAY_TO_WORLD(20)
		if (((sdx + HYSTERESIS_X)
				<< (MAX_VIS_REDUCTION - next_reduction)) > TRANSITION_WIDTH
				|| ((sdy + HYSTERESIS_Y)
				<< (MAX_VIS_REDUCTION - next_reduction)) > TRANSITION_HEIGHT)
		   /* if we don't zoom in, we want to stay at next+1 */
		   next_reduction += REDUCTION_SHIFT;
		}

		if (next_reduction == 0
				&& LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
			next_reduction += REDUCTION_SHIFT;
	}
	else
	{
		if (LOBYTE (GLOBAL (CurrentActivity)) > IN_ENCOUNTER)
			return (1 << ZOOM_SHIFT);
			
		dx = (dx * MAX_ZOOM_OUT) / (LOG_SPACE_WIDTH >> 2);
		if (dx < (1 << ZOOM_SHIFT))
			dx = 1 << ZOOM_SHIFT;
		else if (dx > MAX_ZOOM_OUT)
			dx = MAX_ZOOM_OUT;
			
		dy = (dy * MAX_ZOOM_OUT) / (LOG_SPACE_HEIGHT >> 2);
		if (dy < (1 << ZOOM_SHIFT))
			dy = 1 << ZOOM_SHIFT;
		else if (dy > MAX_ZOOM_OUT)
			dy = MAX_ZOOM_OUT;
			
		if (dy > dx)
			next_reduction = dy;
		else
			next_reduction = dx;

		if (next_reduction < (2 << ZOOM_SHIFT)
				&& LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
			next_reduction = (2 << ZOOM_SHIFT);
	}

#ifdef KDEBUG
	log_add (log_Debug, "CalcReduction: exit");
#endif

	return (next_reduction);
}

static VIEW_STATE
CalcView (POINT *pNewScrollPt, SIZE next_reduction,
		SIZE *pdx, SIZE *pdy, COUNT ships_alive)
{
	SIZE dx, dy;
	VIEW_STATE view_state;

#ifdef KDEBUG
	log_add (log_Debug, "CalcView:");
#endif
	dx = ((COORD)(LOG_SPACE_WIDTH >> 1) - pNewScrollPt->x);
	dy = ((COORD)(LOG_SPACE_HEIGHT >> 1) - pNewScrollPt->y);
	dx = WRAP_DELTA_X (dx);
	dy = WRAP_DELTA_Y (dy);
	if (ships_alive == 1)
	{
#define ORG_JUMP_X ((SIZE)DISPLAY_ALIGN(LOG_SPACE_WIDTH / 75))
#define ORG_JUMP_Y ((SIZE)DISPLAY_ALIGN(LOG_SPACE_HEIGHT / 75))
		if (dx > ORG_JUMP_X)
			dx = ORG_JUMP_X;
		else if (dx < -ORG_JUMP_X)
			dx = -ORG_JUMP_X;
		if (dy > ORG_JUMP_Y)
			dy = ORG_JUMP_Y;
		else if (dy < -ORG_JUMP_Y)
			dy = -ORG_JUMP_Y;
	}

	if ((dx || dy) && LOBYTE (GLOBAL (CurrentActivity)) == IN_HYPERSPACE)
		MoveSIS (&dx, &dy);

	if (zoom_out == next_reduction)
		view_state = dx == 0 && dy == 0
				&& LOBYTE (GLOBAL (CurrentActivity)) != IN_HYPERSPACE
				? VIEW_STABLE : VIEW_SCROLL;
	else
	{
		if (optMeleeScale == TFB_SCALE_STEP)
		{
			SpaceOrg.x = (COORD)(LOG_SPACE_WIDTH >> 1)
					- (LOG_SPACE_WIDTH >> ((MAX_REDUCTION + 1)
					- next_reduction));
			SpaceOrg.y = (COORD)(LOG_SPACE_HEIGHT >> 1)
					- (LOG_SPACE_HEIGHT >> ((MAX_REDUCTION + 1)
					- next_reduction));
		}
		else
		{
#define ZOOM_JUMP ((1 << ZOOM_SHIFT) >> 3)
			if (ships_alive == 1
					&& zoom_out > next_reduction
					&& zoom_out <= MAX_ZOOM_OUT
					&& zoom_out - next_reduction > ZOOM_JUMP)
				next_reduction = zoom_out - ZOOM_JUMP;
				
			// Always align the origin on a whole pixel to reduce the
			// amount of object positioning jitter
			SpaceOrg.x = DISPLAY_ALIGN((int)(LOG_SPACE_WIDTH >> 1) -
					(LOG_SPACE_WIDTH * next_reduction / (MAX_ZOOM_OUT << 2)));
			SpaceOrg.y = DISPLAY_ALIGN((int)(LOG_SPACE_HEIGHT >> 1) -
					(LOG_SPACE_HEIGHT * next_reduction / (MAX_ZOOM_OUT << 2)));
		}
		zoom_out = next_reduction;
		view_state = VIEW_CHANGE;
	}

	if (LOBYTE (GLOBAL (CurrentActivity)) <= IN_HYPERSPACE)
		MoveGalaxy (view_state, dx, dy);

	*pdx = dx;
	*pdy = dy;

#ifdef KDEBUG
	log_add (log_Debug, "CalcView: exit");
#endif
	return (view_state);
}


static ELEMENT_FLAGS
ProcessCollisions (HELEMENT hSuccElement, ELEMENT *ElementPtr,
		TIME_VALUE min_time, ELEMENT_FLAGS process_flags)
{
	HELEMENT hTestElement;

	while ((hTestElement = hSuccElement) != 0)
	{
		ELEMENT *TestElementPtr;

		LockElement (hTestElement, &TestElementPtr);
		if (!(TestElementPtr->state_flags & process_flags))
			PreProcess (TestElementPtr);
		hSuccElement = GetSuccElement (TestElementPtr);

		if (TestElementPtr == ElementPtr)
		{
			UnlockElement (hTestElement);
			continue;
		}

		if (CollisionPossible (TestElementPtr, ElementPtr))
		{
			ELEMENT_FLAGS state_flags, test_state_flags;
			TIME_VALUE time_val;

			state_flags = ElementPtr->state_flags;
			test_state_flags = TestElementPtr->state_flags;
			if (((state_flags | test_state_flags) & FINITE_LIFE)
					&& (((state_flags & APPEARING)
					&& ElementPtr->life_span > 1)
					|| ((test_state_flags & APPEARING)
					&& TestElementPtr->life_span > 1)))
				time_val = 0;
			else
			{
				while ((time_val = DrawablesIntersect (&ElementPtr->IntersectControl,
						&TestElementPtr->IntersectControl, min_time)) == 1
						&& !((state_flags | test_state_flags) & FINITE_LIFE))
				{
#ifdef DEBUG_PROCESS
					log_add (log_Debug, "BAD NEWS 0x%x <--> 0x%x", ElementPtr,
							TestElementPtr);
#endif /* DEBUG_PROCESS */
					if (state_flags & COLLISION)
					{
						InitIntersectEndPoint (TestElementPtr);
						TestElementPtr->IntersectControl.IntersectStamp.origin =
								TestElementPtr->IntersectControl.EndPoint;
						time_val = DrawablesIntersect (&ElementPtr->IntersectControl,
								&TestElementPtr->IntersectControl, 1);
						InitIntersectStartPoint (TestElementPtr);
					}

					if (time_val == 1)
					{
						FRAME CurFrame, NextFrame,
								TestCurFrame, TestNextFrame;

						CurFrame = ElementPtr->current.image.frame;
						NextFrame = ElementPtr->next.image.frame;
						TestCurFrame = TestElementPtr->current.image.frame;
						TestNextFrame = TestElementPtr->next.image.frame;
						if (NextFrame == CurFrame
								&& TestNextFrame == TestCurFrame)
						{
							if (test_state_flags & APPEARING)
							{
								do_damage (TestElementPtr, TestElementPtr->hit_points);
								if (TestElementPtr->pParent) /* untarget this dead element */
									Untarget (TestElementPtr);

								TestElementPtr->state_flags |= (COLLISION | DISAPPEARING);
								if (TestElementPtr->death_func)
									(*TestElementPtr->death_func) (TestElementPtr);
							}
							if (state_flags & APPEARING)
							{
								do_damage (ElementPtr, ElementPtr->hit_points);
								if (ElementPtr->pParent) /* untarget this dead element */
									Untarget (ElementPtr);

								ElementPtr->state_flags |= (COLLISION | DISAPPEARING);
								if (ElementPtr->death_func)
									(*ElementPtr->death_func) (ElementPtr);

								UnlockElement (hTestElement);
								return (COLLISION);
							}

							time_val = 0;
						}
						else
						{
							if (GetFrameIndex (CurFrame) !=
									GetFrameIndex (NextFrame))
								ElementPtr->next.image.frame =
										SetEquFrameIndex (NextFrame,
										CurFrame);
							else if (NextFrame != CurFrame)
							{
								ElementPtr->next.image =
										ElementPtr->current.image;
								if (ElementPtr->life_span > NORMAL_LIFE)
									ElementPtr->life_span = NORMAL_LIFE;
							}

							if (GetFrameIndex (TestCurFrame) !=
									GetFrameIndex (TestNextFrame))
								TestElementPtr->next.image.frame =
										SetEquFrameIndex (TestNextFrame,
										TestCurFrame);
							else if (TestNextFrame != TestCurFrame)
							{
								TestElementPtr->next.image =
										TestElementPtr->current.image;
								if (TestElementPtr->life_span > NORMAL_LIFE)
									TestElementPtr->life_span = NORMAL_LIFE;
							}

							InitIntersectStartPoint (ElementPtr);
							InitIntersectEndPoint (ElementPtr);
							InitIntersectFrame (ElementPtr);
							if (state_flags & PLAYER_SHIP)
							{
								STARSHIP *StarShipPtr;

								GetElementStarShip (ElementPtr, &StarShipPtr);
								StarShipPtr->ShipFacing =
										GetFrameIndex (
										ElementPtr->next.image.frame);
							}

							InitIntersectStartPoint (TestElementPtr);
							InitIntersectEndPoint (TestElementPtr);
							InitIntersectFrame (TestElementPtr);
							if (test_state_flags & PLAYER_SHIP)
							{
								STARSHIP *StarShipPtr;

								GetElementStarShip (TestElementPtr, &StarShipPtr);
								StarShipPtr->ShipFacing =
										GetFrameIndex (
										TestElementPtr->next.image.frame);
							}
						}
					}

					if (time_val == 0)
					{
						InitIntersectEndPoint (ElementPtr);
						InitIntersectEndPoint (TestElementPtr);

						break;
					}
				}
			}

			if (time_val > 0)
			{
				POINT SavePt, TestSavePt;

#ifdef DEBUG_PROCESS
				log_add (log_Debug, "0x%x <--> 0x%x at %u", ElementPtr,
						TestElementPtr, time_val);
#endif /* DEBUG_PROCESS */
				SavePt = ElementPtr->IntersectControl.EndPoint;
				TestSavePt = TestElementPtr->IntersectControl.EndPoint;
				InitIntersectEndPoint (ElementPtr);
				InitIntersectEndPoint (TestElementPtr);
				if (time_val == 1
						|| (((state_flags & COLLISION)
						|| !ProcessCollisions (hSuccElement, ElementPtr,
						time_val - 1, process_flags))
						&& ((test_state_flags & COLLISION)
						|| !ProcessCollisions (
						!(TestElementPtr->state_flags & APPEARING) ?
						GetSuccElement (ElementPtr) :
						GetHeadElement (), TestElementPtr,
						time_val - 1, process_flags))))
				{
					state_flags = ElementPtr->state_flags;
					test_state_flags = TestElementPtr->state_flags;

#ifdef DEBUG_PROCESS
					log_add (log_Debug, "PROCESSING 0x%x <--> 0x%x at %u",
							ElementPtr, TestElementPtr, time_val);
#endif /* DEBUG_PROCESS */
					if (test_state_flags & PLAYER_SHIP)
					{
						(*TestElementPtr->collision_func) (
								TestElementPtr, &TestSavePt,
								ElementPtr, &SavePt
								);
						(*ElementPtr->collision_func) (
								ElementPtr, &SavePt,
								TestElementPtr, &TestSavePt
								);
					}
					else
					{
						(*ElementPtr->collision_func) (
								ElementPtr, &SavePt,
								TestElementPtr, &TestSavePt
								);
						(*TestElementPtr->collision_func) (
								TestElementPtr, &TestSavePt,
								ElementPtr, &SavePt
								);
					}

					if (TestElementPtr->state_flags & COLLISION)
					{
						if (!(test_state_flags & COLLISION))
						{
							TestElementPtr->IntersectControl.IntersectStamp.origin =
									TestSavePt;
							TestElementPtr->next.location.x =
									DISPLAY_TO_WORLD (TestSavePt.x);
							TestElementPtr->next.location.y =
									DISPLAY_TO_WORLD (TestSavePt.y);
							InitIntersectEndPoint (TestElementPtr);
						}
					}

					if (ElementPtr->state_flags & COLLISION)
					{
						if (!(state_flags & COLLISION))
						{
							ElementPtr->IntersectControl.IntersectStamp.origin =
									SavePt;
							ElementPtr->next.location.x =
									DISPLAY_TO_WORLD (SavePt.x);
							ElementPtr->next.location.y =
									DISPLAY_TO_WORLD (SavePt.y);
							InitIntersectEndPoint (ElementPtr);

							if (!(state_flags & FINITE_LIFE) &&
									!(test_state_flags & FINITE_LIFE))
							{
								collide (ElementPtr, TestElementPtr);

								ProcessCollisions (GetHeadElement (), ElementPtr,
										MAX_TIME_VALUE, process_flags);
								ProcessCollisions (GetHeadElement (), TestElementPtr,
										MAX_TIME_VALUE, process_flags);
							}
						}
						UnlockElement (hTestElement);
						return (COLLISION);
					}

					if (!CollidingElement (ElementPtr))
					{
						ElementPtr->state_flags |= COLLISION;
						UnlockElement (hTestElement);
						return (COLLISION);
					}
				}
			}
		}

		UnlockElement (hTestElement);
	}

	return (ElementPtr->state_flags & COLLISION);
}

static VIEW_STATE
PreProcessQueue (SIZE *pscroll_x, SIZE *pscroll_y)
{
	SIZE min_reduction, max_reduction;
	COUNT sides_active;
	POINT Origin;
	HELEMENT hElement;
	COUNT ships_alive;

#ifdef KDEBUG
	log_add (log_Debug, "PreProcess:");
#endif
	sides_active = (battle_counter[0] ? 1 : 0)
			+ (battle_counter[1] ? 1 : 0);

	if (optMeleeScale == TFB_SCALE_STEP)
		min_reduction = max_reduction = MAX_VIS_REDUCTION + 1;
	else
		min_reduction = max_reduction = MAX_ZOOM_OUT + (1 << ZOOM_SHIFT);

	Origin.x = (COORD)(LOG_SPACE_WIDTH >> 1);
	Origin.y = (COORD)(LOG_SPACE_HEIGHT >> 1);

	hElement = GetHeadElement ();
	ships_alive = 0;
	while (hElement != 0)
	{
		ELEMENT *ElementPtr;
		HELEMENT hNextElement;

		LockElement (hElement, &ElementPtr);

		if (!(ElementPtr->state_flags & PRE_PROCESS))
			PreProcess (ElementPtr);
		hNextElement = GetSuccElement (ElementPtr);

		if (CollidingElement (ElementPtr)
				&& !(ElementPtr->state_flags & COLLISION))
			ProcessCollisions (hNextElement, ElementPtr,
					MAX_TIME_VALUE, PRE_PROCESS);

		if (ElementPtr->state_flags & PLAYER_SHIP)
		{
			SIZE dx, dy;

			ships_alive++;
			if (max_reduction > opt_max_zoom_out
					&& min_reduction > opt_max_zoom_out)
			{
				Origin.x = DISPLAY_ALIGN (ElementPtr->next.location.x);
				Origin.y = DISPLAY_ALIGN (ElementPtr->next.location.y);
			}

			dx = DISPLAY_ALIGN (ElementPtr->next.location.x) - Origin.x;
			dx = WRAP_DELTA_X (dx);
			dy = DISPLAY_ALIGN (ElementPtr->next.location.y) - Origin.y;
			dy = WRAP_DELTA_Y (dy);

			if (sides_active <= 2 || ElementPtr->playerNr == 0)
			{
				Origin.x = DISPLAY_ALIGN (Origin.x + (dx >> 1));
				Origin.y = DISPLAY_ALIGN (Origin.y + (dy >> 1));

				if (dx < 0)
					dx = -dx;
				if (dy < 0)
					dy = -dy;
				max_reduction = CalcReduction (dx, dy);
			}
			else if (max_reduction > opt_max_zoom_out
					&& min_reduction <= opt_max_zoom_out)
			{
				Origin.x = DISPLAY_ALIGN (Origin.x + (dx >> 1));
				Origin.y = DISPLAY_ALIGN (Origin.y + (dy >> 1));

				if (dx < 0)
					dx = -dx;
				if (dy < 0)
					dy = -dy;
				min_reduction = CalcReduction (dx, dy);
			}
			else
			{
				SIZE reduction;

				if (dx < 0)
					dx = -dx;
				if (dy < 0)
					dy = -dy;
				reduction = CalcReduction (dx << 1, dy << 1);

				if (min_reduction > opt_max_zoom_out
						|| reduction < min_reduction)
					min_reduction = reduction;
			}
//			log_add (log_Debug, "dx = %d dy = %d min_red = %d max_red = %d",
//					dx, dy, min_reduction, max_reduction);
		}

		UnlockElement (hElement);
		hElement = hNextElement;
	}

	if ((min_reduction > opt_max_zoom_out || min_reduction <= max_reduction)
			&& (min_reduction = max_reduction) > opt_max_zoom_out
			&& (min_reduction = zoom_out) > opt_max_zoom_out)
	{
		if (optMeleeScale == TFB_SCALE_STEP)
			min_reduction = 0;
		else
			min_reduction = 1 << ZOOM_SHIFT;
	}

#ifdef KDEBUG
	log_add (log_Debug, "PreProcess: exit");
#endif
	return (CalcView (&Origin, min_reduction, pscroll_x, pscroll_y, ships_alive));
}

void
InsertPrim (PRIM_LINKS *pLinks, COUNT primIndex, COUNT iPI)
{
	COUNT Link;
	PRIM_LINKS PL;

	if (iPI == END_OF_LIST)
	{
		Link = GetSuccLink (*pLinks); /* get tail */
		if (Link == END_OF_LIST)
			*pLinks = MakeLinks (primIndex, primIndex);
		else
			*pLinks = MakeLinks (GetPredLink (*pLinks), primIndex);
	}
	else
	{
		PL = GetPrimLinks (&DisplayArray[iPI]);
		if (iPI != GetPredLink (*pLinks)) /* if not the head */
			Link = GetPredLink (PL);
		else
		{
			Link = END_OF_LIST;
			*pLinks = MakeLinks (primIndex, GetSuccLink (*pLinks));
		}
		SetPrimLinks (&DisplayArray[iPI], primIndex, GetSuccLink (PL));
	}

	if (Link != END_OF_LIST)
	{
		PL = GetPrimLinks (&DisplayArray[Link]);
		SetPrimLinks (&DisplayArray[Link], GetPredLink (PL), primIndex);
	}
	SetPrimLinks (&DisplayArray[primIndex], Link, iPI);
}

PRIM_LINKS DisplayLinks;

static inline COORD
CalcDisplayCoord (COORD c, COORD orgc, SIZE reduction)
{
	if (optMeleeScale == TFB_SCALE_STEP)
	{	/* old fixed-step zoom style */
		return (c - orgc) >> reduction;
	}
	else
	{	/* new continuous zoom style */
		return ((c - orgc) << ZOOM_SHIFT) / reduction;
	}
}

static void
PostProcessQueue (VIEW_STATE view_state, SIZE scroll_x,
		SIZE scroll_y)
{
	POINT delta;
	SIZE reduction;
	HELEMENT hElement;

#ifdef KDEBUG
	log_add (log_Debug, "PostProcess:");
#endif
	if (optMeleeScale == TFB_SCALE_STEP)
		reduction = zoom_out + ONE_SHIFT;
	else
		reduction = zoom_out << ONE_SHIFT;

	hElement = GetHeadElement ();
	while (hElement != 0)
	{
		ELEMENT_FLAGS state_flags;
		ELEMENT *ElementPtr;
		HELEMENT hNextElement;

		LockElement (hElement, &ElementPtr);

		state_flags = ElementPtr->state_flags;
		if (state_flags & PRE_PROCESS)
		{
			if (!(state_flags & COLLISION))
				ElementPtr->state_flags &= ~DEFY_PHYSICS;
			else
				ElementPtr->state_flags &= ~COLLISION;

			if (state_flags & POST_PROCESS)
			{
				delta.x = 0;
				delta.y = 0;
			}
			else
			{
				delta.x = scroll_x;
				delta.y = scroll_y;
			}
		}
		else
		{
			HELEMENT hPostElement;

			hPostElement = hElement;
			do
			{
				ELEMENT *PostElementPtr;

				LockElement (hPostElement, &PostElementPtr);
				if (!(PostElementPtr->state_flags & PRE_PROCESS))
					PreProcess (PostElementPtr);
				hNextElement = GetSuccElement (PostElementPtr);

				if (CollidingElement (PostElementPtr)
						&& !(PostElementPtr->state_flags & COLLISION))
					ProcessCollisions (GetHeadElement (), PostElementPtr,
							MAX_TIME_VALUE, PRE_PROCESS | POST_PROCESS);
				UnlockElement (hPostElement);
				hPostElement = hNextElement;
			} while (hPostElement != 0);

			scroll_x = 0;
			scroll_y = 0;
			delta.x = 0;
			delta.y = 0;
					/* because these are newly added elements that are
					 * already in adjusted coordinates */
			state_flags = ElementPtr->state_flags;
		}

		if (state_flags & DISAPPEARING)
		{
			hNextElement = GetSuccElement (ElementPtr);
			UnlockElement (hElement);
			RemoveElement (hElement);
			FreeElement (hElement);
		}
		else
		{
			GRAPHICS_PRIM ObjType;

			ObjType = GetPrimType (&DisplayArray[ElementPtr->PrimIndex]);
			if (view_state != VIEW_STABLE
					|| (state_flags & (APPEARING | CHANGING)))
			{
				POINT next;

				if (ObjType == LINE_PRIM)
				{
					SIZE dx, dy;

					dx = ElementPtr->next.location.x
							- ElementPtr->current.location.x;
					dy = ElementPtr->next.location.y
							- ElementPtr->current.location.y;

					next.x = WRAP_X (ElementPtr->current.location.x + delta.x);
					next.y = WRAP_Y (ElementPtr->current.location.y + delta.y);
					DisplayArray[ElementPtr->PrimIndex].Object.Line.first.x =
							CalcDisplayCoord (next.x, SpaceOrg.x, reduction);
					DisplayArray[ElementPtr->PrimIndex].Object.Line.first.y =
							CalcDisplayCoord (next.y, SpaceOrg.y, reduction);

					next.x += dx;
					next.y += dy;
					DisplayArray[ElementPtr->PrimIndex].Object.Line.second.x =
							CalcDisplayCoord (next.x, SpaceOrg.x, reduction);
					DisplayArray[ElementPtr->PrimIndex].Object.Line.second.y =
							CalcDisplayCoord (next.y, SpaceOrg.y, reduction);
				}
				else
				{
					next.x = WRAP_X (ElementPtr->next.location.x + delta.x);
					next.y = WRAP_Y (ElementPtr->next.location.y + delta.y);
					DisplayArray[ElementPtr->PrimIndex].Object.Point.x =
							CalcDisplayCoord (next.x, SpaceOrg.x, reduction);
					DisplayArray[ElementPtr->PrimIndex].Object.Point.y =
							CalcDisplayCoord (next.y, SpaceOrg.y, reduction);

					if (ObjType == STAMP_PRIM || ObjType == STAMPFILL_PRIM)
					{
						if (view_state == VIEW_CHANGE
								|| (state_flags & (APPEARING | CHANGING)))
						{
							COUNT index, scale = GSCALE_IDENTITY;
						
							if (optMeleeScale == TFB_SCALE_STEP)
								index = zoom_out;
							else
								CALC_ZOOM_STUFF (&index, &scale);

							ElementPtr->next.image.frame = SetEquFrameIndex (
									ElementPtr->next.image.farray[index],
									ElementPtr->next.image.frame);

							if (optMeleeScale == TFB_SCALE_TRILINEAR &&
									index < 2 && scale != GSCALE_IDENTITY)
							{
								// enqueues drawcommand to assign next
								// (smaller) zoom level image as mipmap,
								// needed for trilinear scaling

								FRAME frame = ElementPtr->next.image.frame;
								FRAME mmframe = SetEquFrameIndex (
										ElementPtr->next.image.farray[
										index + 1], frame);

								// TODO: This is currently hacky, this code
								//   really should not dereference FRAME.
								//   Perhaps make mipmap part of STAMP prim?
								if (frame && mmframe)
								{
									HOT_SPOT mmhs = GetFrameHot (mmframe);
									TFB_DrawScreen_SetMipmap (frame->image,
											mmframe->image, mmhs.x, mmhs.y);
								}
							}
						}
						DisplayArray[ElementPtr->PrimIndex].Object.Stamp.frame =
								ElementPtr->next.image.frame;
					}
				}

				ElementPtr->next.location = next;
			}

			PostProcess (ElementPtr);

			if (ObjType < NUM_PRIMS)
				InsertPrim (&DisplayLinks, ElementPtr->PrimIndex, END_OF_LIST);

			hNextElement = GetSuccElement (ElementPtr);
			UnlockElement (hElement);
		}

		hElement = hNextElement;
	}
#ifdef KDEBUG
	log_add (log_Debug, "PostProcess: exit");
#endif
}

void
InitDisplayList (void)
{
	COUNT i;

	if (optMeleeScale == TFB_SCALE_STEP)
	{
		zoom_out = MAX_VIS_REDUCTION + 1;
		opt_max_zoom_out = MAX_VIS_REDUCTION;
	}
	else
	{
		zoom_out = MAX_ZOOM_OUT + (1 << ZOOM_SHIFT);
		opt_max_zoom_out = MAX_ZOOM_OUT;
	}

	ReinitQueue (&disp_q);

	for (i = 0; i < MAX_DISPLAY_PRIMS; ++i)
		SetPrimLinks (&DisplayArray[i], END_OF_LIST, i + 1);
	SetPrimLinks (&DisplayArray[i - 1], END_OF_LIST, END_OF_LIST);
	DisplayFreeList = 0;
	DisplayLinks = MakeLinks (END_OF_LIST, END_OF_LIST);
}

UWORD nth_frame = 0;

void
RedrawQueue (BOOLEAN clear)
{
	SIZE scroll_x, scroll_y;
	VIEW_STATE view_state;

	SetContext (StatusContext);

	view_state = PreProcessQueue (&scroll_x, &scroll_y);
	PostProcessQueue (view_state, scroll_x, scroll_y);

	if (optStereoSFX)
		UpdateSoundPositions ();

	SetContext (SpaceContext);
	if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE
		|| !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
	{
		BYTE skip_frames;

		skip_frames = HIBYTE (nth_frame);
		if (skip_frames != (BYTE)~0
			&& (skip_frames == 0 || (--nth_frame & 0x00FF) == 0))
		{
			nth_frame += skip_frames;
			if (clear)
				ClearDrawable (); // this is for BATCH_BUILD_PAGE effect, but not scaled by SetGraphicScale

			if (optMeleeScale != TFB_SCALE_STEP)
			{
				COUNT index, scale;

				CALC_ZOOM_STUFF (&index, &scale);
				SetGraphicScale (scale);
			}

			DrawBatch (DisplayArray, DisplayLinks, 0);//BATCH_BUILD_PAGE);
			SetGraphicScale (0);
		}

		FlushSounds ();
	}
	else
	{	// sfx queue needs to be flushed when aborting
		ProcessSound ((SOUND)~0, NULL);
		FlushSounds ();
	}

	DisplayLinks = MakeLinks (END_OF_LIST, END_OF_LIST);
}

// Set the hTarget field to 0 for all elements in the display list that
// have hTarget set to ElementPtr.
void
Untarget (ELEMENT *ElementPtr)
{
	HELEMENT hElement, hNextElement;

	for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
	{
		HELEMENT hTarget;
		ELEMENT *ListPtr;

		LockElement (hElement, &ListPtr);
		hNextElement = GetSuccElement (ListPtr);

		hTarget = ListPtr->hTarget;
		if (hTarget)
		{
			ELEMENT *TargetElementPtr;

			LockElement (hTarget, &TargetElementPtr);
			if (TargetElementPtr == ElementPtr)
				ListPtr->hTarget = 0;
			UnlockElement (hTarget);
		}

		UnlockElement (hElement);
	}
}

void
RemoveElement (HLINK hLink)
{
	if (optStereoSFX)
	{
		ELEMENT *ElementPtr;

		LockElement (hLink, &ElementPtr);
		if (ElementPtr != NULL)
			RemoveSoundsForObject(ElementPtr);
		UnlockElement (hLink);
	}
	RemoveQueue (&disp_q, hLink);
}


