/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2007  Warzone Resurrection Project

	Warzone 2100 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.

	Warzone 2100 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 Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * Generate a 'meta' route through the gateways to guide the normal routing
 *
 */

// print out the gateways examined while generating the route
//#define DEBUG_GROUP0
// print out the final route
//#define DEBUG_GROUP1

#include "lib/framework/frame.h"

BOOL	gwrDoMessage;

#include "map.h"
#include "gateway.h"
#include "gatewayroute.h"
#include "fpath.h"

// the open list
static GATEWAY	*psOpenList;

// estimate the distance to the target from a gateway
SDWORD gwrEstimate(GATEWAY *psGate, SDWORD x, SDWORD y)
{
	SDWORD	gwx,gwy, dx,dy;

	gwx = (psGate->x1 + psGate->x2)/2;
	gwy = (psGate->y1 + psGate->y2)/2;

	dx = gwx - x;
	dy = gwy - y;

	dx = abs( dx );
	dy = abs( dy );

	return dx > dy ? dx + dy/2 : dx/2 + dy;
}


// add a gateway to the open list
void gwrOpenAdd(GATEWAY *psGate)
{
	psGate->flags &= ~GWR_CLOSED;
	psGate->flags |= GWR_OPEN;
	psGate->psOpen = psOpenList;
	psOpenList = psGate;
}


// get the next gateway from the open list
GATEWAY *gwrOpenGet(void)
{
	SDWORD		minDist, dist;
	GATEWAY		*psCurr, *psPrev, *psParent = NULL, *psFound = NULL;

	if (psOpenList == NULL)
	{
		return NULL;
	}

	psPrev = NULL;
	minDist = SDWORD_MAX;
	for (psCurr = psOpenList; psCurr; psCurr=psCurr->psOpen)
	{
		dist = psCurr->dist + psCurr->est;
		if (dist < minDist)
		{
			minDist = dist;
			psParent = psPrev;
			psFound = psCurr;
		}
		psPrev = psCurr;
	}

	// remove the found gateway from the list
	if (psParent == NULL)
	{
		psOpenList = psOpenList->psOpen;
	}
	else
	{
		psParent->psOpen = psFound->psOpen;
	}
	psFound->psOpen = NULL;
	psFound->flags &= ~(GWR_OPEN|GWR_CLOSED);

	return psFound;
}


// check whether a gateway should be considered for routing
// (i.e. whether it is inside the current scroll limits)
BOOL gwrConsiderGateway(GATEWAY *psGate)
{
	return (psGate->x1 >= scrollMinX) && (psGate->x1 <= scrollMaxX) &&
		   (psGate->x2 >= scrollMinX) && (psGate->x2 <= scrollMaxX) &&
		   (psGate->y1 >= scrollMinY) && (psGate->y1 <= scrollMaxY) &&
		   (psGate->y2 >= scrollMinY) && (psGate->y2 <= scrollMaxY) &&
		   !(psGate->flags & GWR_BLOCKED);
}


// check whether all the tiles on a gateway are blocked
BOOL gwrBlockedGateway(GATEWAY *psGate, SDWORD player, UDWORD terrain)
{
//	SDWORD	pos;
	BOOL	blocked;
	MAPTILE	*psTile;

	blocked = FALSE;

	psTile = mapTile( (psGate->x1+psGate->x2)/2,
					  (psGate->y1+psGate->y2)/2);
	if ( (TERRAIN_TYPE(psTile) == TER_WATER) &&
		 !(terrain & GWR_TER_WATER))
	{
		blocked = TRUE;
	}
	if ( (TERRAIN_TYPE(psTile) != TER_WATER) &&
		 !(terrain & GWR_TER_LAND))
	{
		blocked = TRUE;
	}
	if (psGate->flags & GWR_IGNORE)
	{
		blocked = TRUE;
	}

/*	blocked = TRUE;
	if (psGate->x1 == psGate->x2)
	{
		for(pos = psGate->y1; pos <= psGate->y2; pos += 1)
		{
			psTile = mapTile(psGate->x1, pos);
			if (!fpathBlockingTile(psGate->x1, pos) &&
				TEST_TILE_VISIBLE(player, psTile))
			{
				blocked = FALSE;
			}
		}
	}
	else
	{
		for(pos = psGate->x1; pos <= psGate->x2; pos += 1)
		{
			psTile = mapTile(pos, psGate->y1);
			if (!fpathBlockingTile(pos, psGate->y1) &&
				TEST_TILE_VISIBLE(player, psTile))
			{
				blocked = FALSE;
			}
		}
	}*/

	return blocked;
}


// A* findpath for the gateway system
SDWORD gwrAStarRoute(SDWORD player, UDWORD terrain,
					 SDWORD sx, SDWORD sy, SDWORD fx, SDWORD fy,
					 GATEWAY **ppsRoute)
{
	SDWORD		tileSX,tileSY,tileFX,tileFY;
	SDWORD		currDist, retval;
	GATEWAY		*psCurr, *psNew, *psRoute, *psParent, *psNext, *psNearest, *psPrev;
	SDWORD		zone, finalZone, link, finalLink;
	BOOL		add;

	*ppsRoute = NULL;
	tileSX = sx >> TILE_SHIFT; tileSY = sy >> TILE_SHIFT;
	tileFX = fx >> TILE_SHIFT; tileFY = fy >> TILE_SHIFT;

	// reset the flags on the gateways
	for(psCurr = psGateways; psCurr; psCurr=psCurr->psNext)
	{
		psCurr->flags &= ~GWR_RESET_MASK;
		psCurr->dist = SWORD_MAX;
		for(link = 0; link < (psCurr->zone1Links + psCurr->zone2Links); link++)
		{
			psCurr->psLinks[link].flags &= ~GWRL_RESET_MASK;
		}
	}

	// add all the gateways next to the start point
	zone = gwGetZone(tileSX, tileSY);
	finalZone = gwGetZone(tileFX, tileFY);
	if (zone == finalZone)
	{
		DBP1(("Route same zone\n"));
		return GWR_SAMEZONE;
	}
	if (zone == 0 || finalZone == 0)
	{
		DBP1(("Route no zone info\n"));
		return GWR_NOZONE;
	}
	psOpenList = NULL;
	for(psNew = psGateways; psNew; psNew=psNew->psNext)
	{
		add = FALSE;
		if (psNew->zone1 == zone)
		{
			psNew->flags |= GWR_ZONE1;
			add = TRUE;
		}
		else if (psNew->zone2 == zone)
		{
			psNew->flags |= GWR_ZONE2;
			add = TRUE;
		}
		else if ((psNew->flags & GWR_WATERLINK) &&
				 gwZoneInEquiv(psNew->zone1, zone))
		{
			psNew->flags |= GWR_ZONE2;
			add = TRUE;
		}

		if (gwrBlockedGateway(psNew, player, terrain))
		{
			psNew->flags |= GWR_BLOCKED;
			add = FALSE;
		}

		if (add && gwrConsiderGateway(psNew))
		{
			psNew->dist = (SWORD)gwrEstimate(psNew, tileSX,tileSY);
			psNew->est = (SWORD)gwrEstimate(psNew, tileFX,tileFY);
			psNew->psRoute = NULL;
			gwrOpenAdd(psNew);
		}
	}

	// search for a route
	psRoute = NULL;
	psNearest = NULL;
	while (psOpenList != NULL)
	{
		psCurr = gwrOpenGet();
// 		debug( LOG_NEVER, "processing gateway (%d,%d)->(%d,%d)\n", psCurr->x1, psCurr->y1, psCurr->x2, psCurr->y2 );

		if (psCurr->zone1 == finalZone || psCurr->zone2 == finalZone ||
			((psCurr->flags & GWR_WATERLINK) && gwZoneInEquiv(psCurr->zone1, finalZone)))
		{
			// reached the target
			psRoute = psCurr;
// 			debug( LOG_NEVER, "Found route\n" );
			break;
		}

		if (psNearest == NULL || psCurr->est < psNearest->est)
		{
			psNearest = psCurr;
		}

		if (psCurr->flags & GWR_WATERLINK)
		{
			// water link gatway - route can continue to any other
			// gateway apart from the one it came from
			zone = psCurr->zone1;
			link = 0;
			finalLink = psCurr->zone1Links + psCurr->zone2Links;
		}
		else if (psCurr->flags & GWR_ZONE1)
		{
			// route came from zone1 continue with zone2
			zone = psCurr->zone2;
			link = psCurr->zone1Links;
			finalLink = psCurr->zone1Links + psCurr->zone2Links;
		}
		else
		{
			zone = psCurr->zone1;
			link = 0;
			finalLink = psCurr->zone1Links;
		}

		for (; link < finalLink; link += 1)
		{
			// skip any link that is known to be blocked
			if (psCurr->psLinks[link].flags & GWRL_BLOCKED)
			{
				continue;
			}

			psNew = psCurr->psLinks[link].psGateway;
			currDist = psCurr->dist + psCurr->psLinks[link].dist;

				// don't loop back on the route
			if ((psNew == psCurr->psRoute) ||
				// skip any gates outside the current scroll limits
				!gwrConsiderGateway(psNew) ||
				// skip any gate already visited node by a shorter route
				(psNew->dist <= currDist))
			{
				continue;
			}

			// Now insert the gateway into the appropriate list
			if ((psNew->flags & GWR_RESET_MASK) == 0)
			{
				// Not in open or closed lists - add to the open list
				psNew->dist = (SWORD)currDist;
				psNew->est = (SWORD)gwrEstimate(psNew, tileFX,tileFY);
				psNew->psRoute = psCurr;
				psNew->flags |= (psNew->zone1 == zone) ? GWR_ZONE1 : GWR_ZONE2;
				gwrOpenAdd(psNew);
// 				debug( LOG_NEVER, "new gateway (%d,%d)->(%d,%d) dist %d est %d\n", psNew->x1, psNew->y1, psNew->x2, psNew->y2, psNew->dist, psNew->est );
			}
			else if (psNew->flags & GWR_OPEN)
			{
				// already in the open list but this is shorter
// 				debug( LOG_NEVER, "new route to open gateway (%d,%d)->(%d,%d) dist %d->%d est %d\n", psNew->x1, psNew->y1, psNew->x2, psNew->y2, currDist, psNew->dist, psNew->est );
				psNew->dist = (SWORD)currDist;
				psNew->psRoute = psCurr;
			}
			else if (psNew->flags & GWR_CLOSED)
			{
				// already in the closed list but this is shorter
// 				debug( LOG_NEVER, "new route to closed gateway (%d,%d)->(%d,%d) dist %d->%d est %d\n", psNew->x1, psNew->y1, psNew->x2, psNew->y2, currDist, psNew->dist, psNew->est );
				psNew->dist = (SWORD)currDist;
				psNew->psRoute = psCurr;
				gwrOpenAdd(psNew);
			}

			psNew->flags &= ~(GWR_ZONE1 | GWR_ZONE2);
			psNew->flags |= (psNew->zone1 == zone) ? GWR_ZONE1 : GWR_ZONE2;
		}

		psCurr->flags &= ~(GWR_CLOSED|GWR_OPEN);
		psCurr->flags |= GWR_CLOSED;
	}

	retval = GWR_OK;
	if (psRoute == NULL)
	{
// 		debug( LOG_NEVER, "Partial route\n" );
		psRoute = psNearest;
		retval = GWR_NEAREST;
	}

	// get the route in the correct order if one was found
	// (it is currently in reverse order).
	if (psRoute)
	{
		psParent = NULL;
		psPrev = NULL;
		for(psCurr=psRoute; psCurr; psCurr=psNext)
		{
			psNext = psCurr->psRoute;
			if (!(psCurr->flags & GWR_WATERLINK))
			{
				psCurr->flags |= GWR_INROUTE;
				for(link = 0; link < (psCurr->zone1Links + psCurr->zone2Links); link++)
				{
					// NB link flags are reversed because the route order is being reversed
					if (psCurr->psLinks[link].psGateway == psPrev)
					{
						psCurr->psLinks[link].flags |= GWRL_CHILD;
					}
					else if	(psCurr->psLinks[link].psGateway == psNext)
					{
						psCurr->psLinks[link].flags |= GWRL_PARENT;
					}
				}
				psCurr->psRoute = psParent;
				psParent = psCurr;
			}

			// NB psPrev is not quite the same as psParent as it includes water link gateways
			psPrev = psCurr;
		}
		psRoute = psParent;

		DBP1(("Final route:\n"));
		for(psCurr=psRoute; psCurr; psCurr=psCurr->psRoute)
		{
			DBP1(("   (%d,%d)->(%d,%d) dist %d est %d\n",
				psCurr->x1, psCurr->y1, psCurr->x2, psCurr->y2, psCurr->dist, psCurr->est));
		}

		*ppsRoute = psRoute;
	}

	if (psRoute == NULL)
	{
		retval = GWR_FAILED;
	}

	return retval;
}





