// 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 static micropolisj.engine.TileConstants.*;

class TerrainBehavior extends TileBehavior
{
	final B behavior;

	TerrainBehavior(Micropolis city, B behavior)
	{
		super(city);
		this.behavior = behavior;
	}

	static enum B
	{
		FIRE,
		FLOOD,
		RADIOACTIVE,
		ROAD,
		RAIL,
		EXPLOSION;
	}

	@Override
	public void apply()
	{
		switch (behavior) {
		case FIRE:
			doFire();
			return;
		case FLOOD:
			doFlood();
			return;
		case RADIOACTIVE:
			doRadioactiveTile();
			return;
		case ROAD:
			doRoad();
			return;
		case RAIL:
			doRail();
			return;
		case EXPLOSION:
			doExplosion();
			return;
		default:
			assert false;
		}
	}

	void doFire()
	{
		city.firePop++;

		// one in four times
		if (PRNG.nextInt(4) != 0) {
			return;
		}

		final int [] DX = { 0, 1, 0, -1 };
		final int [] DY = { -1, 0, 1, 0 };

		for (int dir = 0; dir < 4; dir++)
		{
			if (PRNG.nextInt(8) == 0)
			{
				int xtem = xpos + DX[dir];
				int ytem = ypos + DY[dir];
				if (!city.testBounds(xtem, ytem))
					continue;

				int c = city.map[ytem][xtem];
				if (isCombustible(c)) {
					if (isZoneCenter(c)) {
						city.killZone(xtem, ytem, c);
						if ((c & LOMASK) > IZB) { //explode
							city.makeExplosion(xtem, ytem);
						}
					}
					city.setTile(xtem, ytem, (char)(FIRE + PRNG.nextInt(4)));
				}
			}
		}

		int cov = city.getFireStationCoverage(xpos, ypos);
		int rate = cov > 100 ? 1 :
			cov > 20 ? 2 :
			cov != 0 ? 3 : 10;

		if (PRNG.nextInt(rate+1) == 0) {
			city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT));
		}
	}

	/**
	 * Called when the current tile is a flooding tile.
	 */
	void doFlood()
	{
		final int [] DX = { 0, 1, 0, -1 };
		final int [] DY = { -1, 0, 1, 0 };

		if (city.floodCnt != 0)
		{
			for (int z = 0; z < 4; z++)
			{
				if (PRNG.nextInt(8) == 0) {
					int xx = xpos + DX[z];
					int yy = ypos + DY[z];
					if (city.testBounds(xx, yy)) {
						int c = city.getTile(xx, yy);
						int t = c & LOMASK;
						if (isCombustible(c) || c == DIRT ||
							(t >= WOODS5 && t < FLOOD))
						{
							if (isZoneCenter(c)) {
								city.killZone(xx, yy, c);
							}
							city.setTile(xx, yy, (char)(FLOOD + PRNG.nextInt(3)));
						}
					}
				}
			}
		}
		else {
			if (PRNG.nextInt(16) == 0) {
				city.setTile(xpos, ypos, DIRT);
			}
		}
	}

	/**
	 * Called when the current tile is a radioactive tile.
	 */
	void doRadioactiveTile()
	{
		if (PRNG.nextInt(4096) == 0)
		{
			// radioactive decay
			city.setTile(xpos, ypos, DIRT);
		}
	}

	static int [] TRAFFIC_DENSITY_TAB = { ROADBASE, LTRFBASE, HTRFBASE };

	/**
	 * Called when the current tile is a road tile.
	 */
	void doRoad()
	{
		city.roadTotal++;

		if (city.roadEffect < 30)
		{
			// deteriorating roads
			if (PRNG.nextInt(512) == 0)
			{
				if (!isConductive(rawTile))
				{
					if (city.roadEffect < PRNG.nextInt(32))
					{
						if (isOverWater(rawTile))
							city.setTile(xpos, ypos, RIVER);
						else
							city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT));
						return;
					}
				}
			}
		}

		if (!isCombustible(rawTile)) //bridge
		{
			city.roadTotal += 4;
			if (doBridge())
				return;
		}

		int tden;
		if ((rawTile & LOMASK) < LTRFBASE)
			tden = 0;
		else if ((rawTile & LOMASK) < HTRFBASE)
			tden = 1;
		else {
			city.roadTotal++;
			tden = 2;
		}

		int trafficDensity = city.getTrafficDensity(xpos, ypos);
		int newLevel = trafficDensity < 64 ? 0 :
			trafficDensity < 192 ? 1 : 2;
		
		assert newLevel >= 0 && newLevel < TRAFFIC_DENSITY_TAB.length;

		if (tden != newLevel)
		{
			int z = (((rawTile & LOMASK) - ROADBASE) & 15) + TRAFFIC_DENSITY_TAB[newLevel];
			z += rawTile & ALLBITS;

			city.setTile(xpos, ypos, (char) z);
		}
	}

	/**
	 * Called when the current tile is railroad.
	 */
	void doRail()
	{
		city.railTotal++;
		city.generateTrain(xpos, ypos);

		if (city.roadEffect < 30) { // deteriorating rail
			if (PRNG.nextInt(512) == 0) {
				if (!isConductive(rawTile)) {
					if (city.roadEffect < PRNG.nextInt(32)) {
						if (isOverWater(rawTile)) {
							city.setTile(xpos,ypos,RIVER);
						} else {
							city.setTile(xpos,ypos,(char)(RUBBLE + PRNG.nextInt(4)+BULLBIT));
						}
					}
				}
			}
		}
	}

	/**
	 * Called when the current tile is a road bridge over water.
	 * Handles the draw bridge. For the draw bridge to appear,
	 * there must be a boat on the water, the boat must be
	 * within a certain distance of the bridge, it must be where
	 * the map generator placed 'channel' tiles (these are tiles
	 * that look just like regular river tiles but have a different
	 * numeric value), and you must be a little lucky.
	 *
	 * @return true if the draw bridge is open; false otherwise
	 */
	boolean doBridge()
	{
		final int HDx[] = { -2,  2, -2, -1,  0,  1,  2 };
		final int HDy[] = { -1, -1,  0,  0,  0,  0,  0 };
		final char HBRTAB[] = {
			HBRDG1 | BULLBIT,       HBRDG3 | BULLBIT,
			HBRDG0 | BULLBIT,       RIVER,
			BRWH | BULLBIT,         RIVER,
			HBRDG2 | BULLBIT };
		final char HBRTAB2[] = {
			RIVER,                  RIVER,
			HBRIDGE | BULLBIT,      HBRIDGE | BULLBIT,
			HBRIDGE | BULLBIT,      HBRIDGE | BULLBIT,
			HBRIDGE | BULLBIT };

		final int VDx[] = {  0,  1,  0,  0,  0,  0,  1 };
		final int VDy[] = { -2, -2, -1,  0,  1,  2,  2 };
		final char VBRTAB[] = {
			VBRDG0 | BULLBIT,       VBRDG1 | BULLBIT,
			RIVER,                  BRWV | BULLBIT,
			RIVER,                  VBRDG2 | BULLBIT,
			VBRDG3 | BULLBIT };
		final char VBRTAB2[] = {
			VBRIDGE | BULLBIT,      RIVER,
			VBRIDGE | BULLBIT,      VBRIDGE | BULLBIT,
			VBRIDGE | BULLBIT,      VBRIDGE | BULLBIT,
			RIVER };

		if (tile == BRWV) {
			// vertical bridge, open
			if (PRNG.nextInt(4) == 0 && getBoatDis() > 340/16) {
				//close the bridge
				applyBridgeChange(VDx, VDy, VBRTAB, VBRTAB2);
			}
			return true;
		}
		else if (tile == BRWH) {
			// horizontal bridge, open
			if (PRNG.nextInt(4) == 0 && getBoatDis() > 340/16) {
				// close the bridge
				applyBridgeChange(HDx, HDy, HBRTAB, HBRTAB2);
			}
			return true;
		}

		if (getBoatDis() < 300/16 && PRNG.nextInt(8) == 0) {
			if ((rawTile & 1) != 0) {
				// vertical bridge
				if (xpos < city.getWidth()-1) {
					// look for CHANNEL tile to right of
					// bridge. the CHANNEL tiles are only
					// found in the very center of the
					// river
					if (city.getTile(xpos+1,ypos) == CHANNEL) {
						// vertical bridge, open it up
						applyBridgeChange(VDx, VDy, VBRTAB2, VBRTAB);
						return true;
					}
				}
				return false;
			}
			else {
				// horizontal bridge
				if (ypos > 0) {
					// look for CHANNEL tile just above
					// bridge. the CHANNEL tiles are only
					// found in the very center of the
					// river
					if (city.getTile(xpos, ypos-1) == CHANNEL) {
						// open it up
						applyBridgeChange(HDx, HDy, HBRTAB2, HBRTAB);
						return true;
					}
				}
				return false;
			}
		}

		return false;
	}

	/**
	 * Helper function for doBridge- it toggles the draw-bridge.
	 */
	private void applyBridgeChange(int [] Dx, int [] Dy, char [] fromTab, char [] toTab)
	{
	//FIXME- a closed bridge with traffic on it is not
	// correctly handled by this subroutine, because the
	// the tiles representing traffic on a bridge do not match
	// the expected tile values of fromTab

		for (int z = 0; z < 7; z++) {
			int x = xpos + Dx[z];
			int y = ypos + Dy[z];
			if (city.testBounds(x,y)) {
				if ((city.map[y][x] & LOMASK) == (fromTab[z] & LOMASK) ||
					(city.map[y][x] == CHANNEL)
					) {
					city.setTile(x, y, toTab[z]);
				}
			}
		}
	}

	/**
	 * Calculate how far away the boat currently is from the
	 * current tile.
	 */
	int getBoatDis()
	{
		int dist = 99999;
		for (Sprite s : city.sprites)
		{
			if (s.isVisible() && s.kind == SpriteKind.SHI)
			{
				int x = s.x / 16;
				int y = s.y / 16;
				int d = Math.abs(xpos-x) + Math.abs(ypos-y);
				dist = Math.min(d, dist);
			}
		}
		return dist;
	}

	void doExplosion()
	{
		// clear AniRubble
		city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT));
	}
}
