// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.

package micropolisj.engine;

import java.util.*;
import static micropolisj.engine.TileConstants.*;

/**
 * Contains the code for generating city traffic.
 */
class TrafficGen
{
	final Micropolis city;
	int mapX;
	int mapY;
	Micropolis.ZoneType sourceZone;

	int lastdir;
	Stack<CityLocation> positions = new Stack<CityLocation>();

	static final int MAX_TRAFFIC_DISTANCE = 30;

	public TrafficGen(Micropolis city)
	{
		this.city = city;
	}

	int makeTraffic()
	{
		if (findPerimeterRoad()) //look for road on this zone's perimeter
		{
			if (tryDrive())  //attempt to drive somewhere
			{
				// success; incr trafdensity
				setTrafficMem();
				return 1;
			}

			return 0;
		}
		else
		{
			// no road found
			return -1;
		}
	}

	void setTrafficMem()
	{
		while (!positions.isEmpty())
		{
			CityLocation pos = positions.pop();
			mapX = pos.x;
			mapY = pos.y;
			assert city.testBounds(mapX, mapY);

			int tile = city.getTile(mapX, mapY) & LOMASK;
			if (tile >= ROADBASE && tile < POWERBASE)
			{
				// check for rail
				int z = city.trfDensity[mapY/2][mapX/2];
				z += 50;

				//FIXME- why is this only capped to 240
				// by random chance. why is there no cap
				// the rest of the time?

				if (z > 240 && city.PRNG.nextInt(6) == 0)
				{
					z = 240;
					city.trafficMaxLocationX = mapX;
					city.trafficMaxLocationY = mapY;

					HelicopterSprite copter = (HelicopterSprite) city.getSprite(SpriteKind.COP);
					if (copter != null) {
						copter.destX = mapX;
						copter.destY = mapY;
					}
				}

				city.trfDensity[mapY/2][mapX/2] = z;
			}
		}
	}

	static final int [] PerimX = { -1, 0, 1,  2, 2, 2,  1, 0,-1, -2,-2,-2 };
	static final int [] PerimY = { -2,-2,-2, -1, 0, 1,  2, 2, 2,  1, 0,-1 };
	boolean findPerimeterRoad()
	{
		for (int z = 0; z < 12; z++)
		{
			int tx = mapX + PerimX[z];
			int ty = mapY + PerimY[z];

			if (roadTest(tx, ty))
			{
				mapX = tx;
				mapY = ty;
				return true;
			}
		}
		return false;
	}

	boolean roadTest(int tx, int ty)
	{
		if (!city.testBounds(tx, ty)) {
			return false;
		}

		char c = city.getTile(tx, ty);
		c &= LOMASK;

		if (c < ROADBASE)
			return false;
		else if (c > LASTRAIL)
			return false;
		else if (c >= POWERBASE && c < LASTPOWER)
			return false;
		else
			return true;
	}

	boolean tryDrive()
	{
		lastdir = 5;
		positions.clear();

		for (int z = 0; z < MAX_TRAFFIC_DISTANCE; z++) //maximum distance to try
		{
			if (tryGo(z))
			{
				// got a road
				if (driveDone())
				{
					// destination reached
					return true;
				}
			}
			else
			{
				// deadend, try backing up
				if (!positions.isEmpty())
				{
					positions.pop();
					z += 3;
				}
				else
				{
					return false;
				}
			}
		}

		// gone maxdis
		return false;
	}

	static final int [] DX = { 0, 1, 0, -1 };
	static final int [] DY = { -1, 0, 1, 0 };
	boolean tryGo(int z)
	{
		// random starting direction
		int rdir = city.PRNG.nextInt(4);

		for (int d = rdir; d < rdir + 4; d++)
		{
			int realdir = d % 4;
			if (realdir == lastdir)
				continue;

			if (roadTest(mapX + DX[realdir], mapY + DY[realdir]))
			{
				mapX += DX[realdir];
				mapY += DY[realdir];
				lastdir = (realdir + 2) % 4;

				if (z % 2 == 1)
				{
					// save pos every other move
					positions.push(new CityLocation(mapX, mapY));
				}

				return true;
			}
		}

		return false;
	}

	boolean driveDone()
	{
		int low, high;
		switch (sourceZone)
		{
		case RESIDENTIAL:
			low = COMBASE;
			high = NUCLEAR;
			break;
		case COMMERCIAL:
			low = LHTHR;
			high = PORT;
			break;
		case INDUSTRIAL:
			low = LHTHR;
			high = COMBASE;
			break;
		default:
			throw new Error("unreachable");
		}

		if (mapY > 0)
		{
			int tile = city.getTile(mapX, mapY-1) & LOMASK;
			if (tile >= low && tile <= high)
				return true;
		}
		if (mapX + 1 < city.getWidth())
		{
			int tile = city.getTile(mapX + 1, mapY) & LOMASK;
			if (tile >= low && tile <= high)
				return true;
		}
		if (mapY + 1 < city.getHeight())
		{
			int tile = city.getTile(mapX, mapY + 1) & LOMASK;
			if (tile >= low && tile <= high)
				return true;
		}
		if (mapX > 0)
		{
			int tile = city.getTile(mapX - 1, mapY) & LOMASK;
			if (tile >= low && tile <= high)
				return true;
		}
		return false;
	}
}
