/* $Id$ */

/**
 * A Road Pathfinder, forked from the default pathfinder
 *  This road pathfinder tries to find a buildable / existing route for
 *  road vehicles. You can changes the costs below using for example
 *  roadpf.cost.turn = 30. Note that it's not allowed to change the cost
 *  between consecutive calls to FindPath. You can change the cost before
 *  the first call to FindPath and after FindPath has returned an actual
 *  route. To use only existing roads, set cost.no_existing_road to
 *  cost.max_cost.
 */
require("aystarr.nut")
class Road
{
	_estimate_multiplier = 1;
	_max_cost = null;              ///< The maximum cost for a route.
	_cost_tile = null;             ///< The cost for a single tile.
	_cost_no_existing_road = null; ///< The cost that is added to _cost_tile if no road exists yet.
	_cost_turn = null;             ///< The cost that is added to _cost_tile if the direction changes.
	_cost_slope = null;            ///< The extra cost if a road tile is sloped.
	_cost_bridge_per_tile = null;  ///< The cost per tile of a new bridge, this is added to _cost_tile.
	_cost_tunnel_per_tile = null;  ///< The cost per tile of a new tunnel, this is added to _cost_tile.
	_cost_coast = null;            ///< The extra cost for a coast tile.
	_pathfinder = null;            ///< A reference to the used AyStar object.
	_max_bridge_length = null;     ///< The maximum length of a bridge that will be build.
	_max_tunnel_length = null;     ///< The maximum length of a tunnel that will be build.
	_cost_level_crossing = null;   ///< The cost per level crossing.
	_cost_double_crossing = null;  ///< The cost of multiple level crossings following each other
	_cost_join_road_bonus = null;  ///< The bonus of going from new-to-build roads to existing roads
	_cost_existing_road_bonus = null;  ///< The bonus of going from new-to-build roads to existing roads

	cost = null;                   ///< Used to change the costs.
	_running = null;

	constructor()
	{
		this._estimate_multiplier = 1;
		this._max_cost = 10000000;
		this._cost_tile = 30;
		this._cost_no_existing_road = 70;
		this._cost_turn = 70;
		this._cost_slope = 100;
		this._cost_bridge_per_tile = 120;
		this._cost_tunnel_per_tile = 80;
		this._cost_coast = 20;
		this._max_bridge_length = 10;
		this._max_tunnel_length = 20;
		this._cost_level_crossing = 20;
		this._cost_double_crossing = 10000;
		this._cost_join_road_bonus = 190;
		this._cost_existing_road_bonus = 1400;
		this._pathfinder = AyStarR(this, this._Cost, this._Estimate, this._Neighbours);

		this.cost = this.Cost(this);
		this._running = false;
	}

	/**
	 * Initialize a path search between sources and goals.
	 * @param sources The source tiles.
	 * @param goals The target tiles.
	 * @param ignored_tiles An array of tiles that cannot occur in the final path.
	 * @see AyStar::InitializePath()
	 */
	function InitializePath(sources, goals, ignored_tiles = []) {
		local nsources = [];

		foreach (node in sources) {
			nsources.push([node, 0xFF]);
		}
		this._pathfinder.InitializePath(nsources, goals, ignored_tiles);
	}

	/**
	 * Try to find the path as indicated with InitializePath with the lowest cost.
	 * @param iterations After how many iterations it should abort for a moment.
	 *  This value should either be -1 for infinite, or > 0. Any other value
	 *  aborts immediatly and will never find a path.
	 * @return A route if one was found, or false if the amount of iterations was
	 *  reached, or null if no path was found.
	 *  You can call this function over and over as long as it returns false,
	 *  which is an indication it is not yet done looking for a route.
	 * @see AyStar::FindPath()
	 */
	function FindPath(iterations);
};

class Road.Cost
{
	_main = null;

	function _set(idx, val)
	{
		if (this._main._running) throw("You are not allowed to change parameters of a running pathfinder.");

		switch (idx) {
			case "multiplier":        this._main._estimate_multiplier = val; break;
			case "max_cost":          this._main._max_cost = val; break;
			case "tile":              this._main._cost_tile = val; break;
			case "no_existing_road":  this._main._cost_no_existing_road = val; break;
			case "turn":              this._main._cost_turn = val; break;
			case "slope":             this._main._cost_slope = val; break;
			case "bridge_per_tile":   this._main._cost_bridge_per_tile = val; break;
			case "tunnel_per_tile":   this._main._cost_tunnel_per_tile = val; break;
			case "coast":             this._main._cost_coast = val; break;
			case "level_crossing":    this._main._cost_level_crossing = val; break;
			case "double_crossing":   this._main._cost_double_crossing = val; break;
			case "join_bonus":        this._main._cost_join_road_bonus = val; break;
			case "existing_bonus":    this._main._cost_existing_road_bonus = val; break;
			case "max_bridge_length": this._main._max_bridge_length = val; break;
			case "max_tunnel_length": this._main._max_tunnel_length = val; break;
			default: throw("the index '" + idx + "' does not exist");
		}

		return val;
	}

	function _get(idx)
	{
		switch (idx) {
			case "multiplier":        return this._main._estimate_multiplier;
			case "max_cost":          return this._main._max_cost;
			case "tile":              return this._main._cost_tile;
			case "no_existing_road":  return this._main._cost_no_existing_road;
			case "turn":              return this._main._cost_turn;
			case "slope":             return this._main._cost_slope;
			case "bridge_per_tile":   return this._main._cost_bridge_per_tile;
			case "tunnel_per_tile":   return this._main._cost_tunnel_per_tile;
			case "coast":             return this._main._cost_coast;
			case "level_crossing":    return this._main._cost_level_crossing;
			case "double_crossing":   return this._main._cost_double_crossing;
			case "join_bonus":        return this._main._cost_join_road_bonus;
			case "existing_bonus":    return this._main._cost_existing_road_bonus;
			case "max_bridge_length": return this._main._max_bridge_length;
			case "max_tunnel_length": return this._main._max_tunnel_length;
			default: throw("the index '" + idx + "' does not exist");
		}
	}

	constructor(main)
	{
		this._main = main;
	}
};

function Road::FindPath(iterations)
{
	local test_mode = AITestMode();
	local ret = this._pathfinder.FindPath(iterations);
	this._running = (ret == false) ? true : false;
	return ret;
}

function Road::_GetBridgeNumSlopes(end_a, end_b)
{
	return ((AITile.GetSlope(end_a) != AITile.SLOPE_FLAT)?1:0) + ((AITile.GetSlope(end_b) != AITile.SLOPE_FLAT)?1:0)

	local slopes = 0;
	local direction = (end_b - end_a) / AIMap.DistanceManhattan(end_a, end_b);
	local slope = AITile.GetSlope(end_a);
	if (!((slope == AITile.SLOPE_NE && direction == 1) || (slope == AITile.SLOPE_SE && direction == -AIMap.GetMapSizeX()) ||
		(slope == AITile.SLOPE_SW && direction == -1) || (slope == AITile.SLOPE_NW && direction == AIMap.GetMapSizeX()) ||
		 slope == AITile.SLOPE_N || slope == AITile.SLOPE_E || slope == AITile.SLOPE_S || slope == AITile.SLOPE_W)) {
		slopes++;
	}

	local slope = AITile.GetSlope(end_b);
	direction = -direction;
	if (!((slope == AITile.SLOPE_NE && direction == 1) || (slope == AITile.SLOPE_SE && direction == -AIMap.GetMapSizeX()) ||
		(slope == AITile.SLOPE_SW && direction == -1) || (slope == AITile.SLOPE_NW && direction == AIMap.GetMapSizeX()) ||
		 slope == AITile.SLOPE_N || slope == AITile.SLOPE_E || slope == AITile.SLOPE_S || slope == AITile.SLOPE_W)) {
		slopes++;
	}
	return slopes;
}

function Road::_Cost(self, path, new_tile, new_direction)
{
	/* path == null means this is the first node of a path, so the cost is 0. */
	if (path == null) return 0;
	local prev_path = path.GetParent()
	local prev_tile = path.GetTile();
	local prevprev_tile = null;
	
	/* If the new tile is a bridge / tunnel tile, check whether we came from the other
	 * end of the bridge / tunnel or if we just entered the bridge / tunnel. */
	if (AIBridge.IsBridgeTile(new_tile)) {
		if (AIBridge.GetOtherBridgeEnd(new_tile) != prev_tile) return path.GetCost() + self._cost_tile;
		return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * self._cost_tile + self._GetBridgeNumSlopes(new_tile, prev_tile) * self._cost_slope;
	} else if (AITunnel.IsTunnelTile(new_tile)) {
		if (AITunnel.GetOtherTunnelEnd(new_tile) != prev_tile) return path.GetCost() + self._cost_tile;
		return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * self._cost_tile;
	}

	/* If the two tiles are more then 1 tile apart, the pathfinder wants a bridge or tunnel
	 * to be build. It isn't an existing bridge / tunnel, as that case is already handled. */
	local distance = AIMap.DistanceManhattan(new_tile, prev_tile);
	if (distance > 1) {
		/* Check if we should build a bridge or a tunnel. */
		if (AITunnel.GetOtherTunnelEnd(new_tile) == prev_tile) {
			return path.GetCost() + distance * (self._cost_tunnel_per_tile);
		} else {
			return path.GetCost() + distance * (self._cost_bridge_per_tile) + self._GetBridgeNumSlopes(new_tile, prev_tile) * self._cost_slope;
		}
	}

	/* Check for a turn. We do this by substracting the TileID of the current node from
	 * the TileID of the previous node and comparing that to the difference between the
	 * previous node and the node before that. */
	local cost = self._cost_tile;
	if (prev_path != null) {
		prevprev_tile = prev_path.GetTile();
		/* Check for a turn */
		if ((prev_tile - prevprev_tile) != (new_tile - prev_tile) && AIMap.DistanceManhattan(prevprev_tile, prev_tile) == 1) {
			cost += self._cost_turn;
		}
		/* Check if the last tile was sloped. */
		if (!AIBridge.IsBridgeTile(prev_tile) && !AITunnel.IsTunnelTile(prev_tile) && self._IsSlopedRoad(prevprev_tile, prev_tile, new_tile)) {
			cost += self._cost_slope;
		}

	}

	/* Check if the new tile is a coast tile. */
	if (AITile.IsCoastTile(new_tile)) {
		cost += self._cost_coast;
	}
	/* Check for level crossings. */
	if (AIRail.IsRailTile(new_tile)) {
		cost += self._cost_level_crossing;
		if (AIRail.IsRailTile(prev_tile)) {
			cost += self._cost_double_crossing;
		}
	}

	//Check if there is already a tram or roadway on the new tile, that should be a better option
	if (!HasAnyRoad(new_tile)) {
		cost += self._cost_no_existing_road;
		if (HasAnyRoad(prev_tile)) {
			cost += self._cost_join_road_bonus + self._cost_existing_road_bonus
		}
	} else if (!HasAnyRoad(prev_tile)) {
		cost -= self._cost_existing_road_bonus
	}

	return path.GetCost() + cost;
}

function Road::_Estimate(self, cur_tile, cur_direction, goal_tiles)
{
	local min_cost = self._max_cost;
	/* As estimate we multiply the lowest possible cost for a single tile with
	 * with the minimum number of tiles we need to traverse. */
	foreach (tile in goal_tiles) {
		local dx = abs(AIMap.GetTileX(cur_tile) - AIMap.GetTileX(tile));
		local dy = abs(AIMap.GetTileY(cur_tile) - AIMap.GetTileY(tile));
		
		if ((dx+dy) < 12)
			return (dx+dy) * self._cost_no_existing_road
		return self._estimate_multiplier*(dx+dy) * self._cost_no_existing_road

		min_cost = min(AIMap.DistanceManhattan(cur_tile, tile) * self._cost_tile, min_cost);
	}
	return min_cost;
}

function Road::_Neighbours(self, path, cur_node)
{
	/* self._max_cost is the maximum path cost, if we go over it, the path isn't valid. */
	if (path.GetCost() >= self._max_cost) return [];
	local tiles = [];
	local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
	                 AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0),
					 ];
	local prev_path = path.GetParent()
	local prev_tile = null
	local direction = path.GetDirection()
	local prev_direction = direction
	if (prev_path != null) {
		prev_direction = prev_path.GetDirection()
		prev_tile = prev_path.GetTile()
	}

	
	/* Check if the current tile is part of a bridge or tunnel. */
	if ((AIBridge.IsBridgeTile(cur_node) || AITunnel.IsTunnelTile(cur_node)) && AITile.HasTransportType(cur_node, AITile.TRANSPORT_ROAD)) {
		local other_end = AIBridge.IsBridgeTile(cur_node) ? AIBridge.GetOtherBridgeEnd(cur_node) : AITunnel.GetOtherTunnelEnd(cur_node);
		local next_tile = cur_node + (cur_node - other_end) / AIMap.DistanceManhattan(cur_node, other_end);
		if (AIRoad.AreRoadTilesConnected(cur_node, next_tile) || AITile.IsBuildable(next_tile) || HasAnyRoad(next_tile)) {
			tiles.push([next_tile, self._GetDirection(cur_node, next_tile, false)]);
		}
		/* The other end of the bridge / tunnel is a neighbour. */
		tiles.push([other_end, self._GetDirection(next_tile, cur_node, true) << 4]);
	} else if (prev_path != null && AIMap.DistanceManhattan(cur_node, prev_tile) > 1) {
		local other_end = prev_tile
		local next_tile = cur_node + (cur_node - other_end) / AIMap.DistanceManhattan(cur_node, other_end);
		if (AIRoad.AreRoadTilesConnected(cur_node, next_tile) || AIRoad.BuildRoad(cur_node, next_tile)) {
			tiles.push([next_tile, self._GetDirection(cur_node, next_tile, false)]);
		}
	} else {
		local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
		                 AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
		/* Check all tiles adjacent to the current tile. */
		foreach (offset in offsets) {
			local next_tile = cur_node + offset;
			// Don't turn back
			if (next_tile == prev_tile) continue;
			//Don't do corners on slopes
			if ((AITile.GetSlope(cur_node) != AITile.SLOPE_FLAT) && cur_node - offset != prev_tile) continue;
			//Do not plan towards non-buildable tile
			if (!AITile.IsBuildable(next_tile) && !AIRail.IsRailTile(next_tile) && !HasAnyRoad(next_tile)) continue;
				
			/* We add them to the to the neighbours-list if one of the following applies:
			 * 1) There already is a connections between the current tile and the next tile.
			 * 2) We can build a road to the next tile. */
			if (AIRoad.AreRoadTilesConnected(cur_node, next_tile)) {
				tiles.push([next_tile, self._GetDirection(cur_node, next_tile, false)]);
			} else if (AIRoad.BuildRoad(cur_node, next_tile)) {
				tiles.push([next_tile, self._GetDirection(cur_node, next_tile, false)]);
			}
		}
		if (prev_path != null) {
			local bridges = self._GetTunnelsBridges(prev_tile, cur_node, self._GetDirection(prev_tile, cur_node, true));
			foreach (tile in bridges) {
				tiles.push(tile);
			}
		}
	}
	return tiles;
}

function Road::_GetDirection(from, to, is_bridge)
{
	return Rail._dir(from, to)
}

/**
 * Get a list of all bridges and tunnels that can be build from the
 * current tile. Bridges will only be build starting on non-flat tiles
 * for performance reasons. Tunnels will only be build if no terraforming
 * is needed on both ends.
 */
function Road::_GetTunnelsBridges(last_node, cur_node, bridge_dir)
{
	local slope = AITile.GetSlope(cur_node);
	local delta = (cur_node - last_node)
	local next = cur_node + delta
	local nextnext = next + delta
	local tiles = [];
	//tunnels on flat ground
	if (AITile.IsBuildable(next) && !AITile.IsBuildable(nextnext) && AITile.IsBuildable(cur_node) &&
		(!AIRoad.IsRoadTile(nextnext) || AIBase.RandRange(10) < 4 || AIRail.IsRailTile(nextnext))) {
		for (local i = 0; i < this._max_tunnel_length; i++) {
			local target = cur_node + i * delta;
			if (AITile.GetSlope(target) != AITile.SLOPE_FLAT)
				break
			if (i > 2 && AITile.IsBuildable(target)) {
				local target2 = target + delta;
				if (AITile.GetSlope(target2) != AITile.SLOPE_FLAT)
					break
				if (AITile.IsBuildable(target2)) {
					tiles.push([target2, bridge_dir]);
				}
			}
		}
	}
	if (!AITile.IsBuildable(next) && !AITile.IsBuildable(nextnext) && (!AIRoad.IsRoadTile(nextnext) || AIBase.RandRange(10) < 4 || AIRail.IsRailTile(nextnext))) {
		for (local i = 2; i < this._max_bridge_length; i++) {
			local bridge_list = AIBridgeList_Length(i + 1);
			local target = cur_node + i * delta;
			if (!bridge_list.IsEmpty() && AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), cur_node, target)) {
				tiles.push([target, bridge_dir]);
				//if (tiles.len() > 1)
				break
			}
		}
	}

	if (slope != AITile.SLOPE_SW && slope != AITile.SLOPE_NW && slope != AITile.SLOPE_SE && slope != AITile.SLOPE_NE) return tiles;
	local other_tunnel_end = AITunnel.GetOtherTunnelEnd(cur_node);
	if (!AIMap.IsValidTile(other_tunnel_end)) return tiles;

	local tunnel_length = AIMap.DistanceManhattan(cur_node, other_tunnel_end);
	local prev_tile = cur_node + (cur_node - other_tunnel_end) / tunnel_length;
	if (AITunnel.GetOtherTunnelEnd(other_tunnel_end) == cur_node && tunnel_length >= 2 &&
			prev_tile == last_node && tunnel_length < _max_tunnel_length && AITunnel.BuildTunnel(AIVehicle.VT_ROAD, cur_node)) {
		tiles.push([other_tunnel_end, bridge_dir]);
	}
	return tiles;
}

function Road::_IsSlopedRoad(start, middle, end)
{
	//KISS: keep it simple, stupid
	return (AITile.GetSlope(middle) != AITile.SLOPE_FLAT)

	local NW = 0; //Set to true if we want to build a road to / from the north-west
	local NE = 0; //Set to true if we want to build a road to / from the north-east
	local SW = 0; //Set to true if we want to build a road to / from the south-west
	local SE = 0; //Set to true if we want to build a road to / from the south-east

	if (middle - AIMap.GetMapSizeX() == start || middle - AIMap.GetMapSizeX() == end) NW = 1;
	if (middle - 1 == start || middle - 1 == end) NE = 1;
	if (middle + AIMap.GetMapSizeX() == start || middle + AIMap.GetMapSizeX() == end) SE = 1;
	if (middle + 1 == start || middle + 1 == end) SW = 1;

	/* If there is a turn in the current tile, it can't be sloped. */
	if ((NW || SE) && (NE || SW)) return false;

	local slope = AITile.GetSlope(middle);
	/* A road on a steep slope is always sloped. */
	if (AITile.IsSteepSlope(slope)) return true;

	/* If only one corner is raised, the road is sloped. */
	if (slope == AITile.SLOPE_N || slope == AITile.SLOPE_W) return true;
	if (slope == AITile.SLOPE_S || slope == AITile.SLOPE_E) return true;

	if (NW && (slope == AITile.SLOPE_NW || slope == AITile.SLOPE_SE)) return true;
	if (NE && (slope == AITile.SLOPE_NE || slope == AITile.SLOPE_SW)) return true;

	return false;
}

function Road::_CheckTunnelBridge(current_tile, new_tile)
{
	if (!AIBridge.IsBridgeTile(new_tile) && !AITunnel.IsTunnelTile(new_tile)) return false;
	local dir = new_tile - current_tile;
	local other_end = AIBridge.IsBridgeTile(new_tile) ? AIBridge.GetOtherBridgeEnd(new_tile) : AITunnel.GetOtherTunnelEnd(new_tile);
	local dir2 = other_end - new_tile;
	if ((dir < 0 && dir2 > 0) || (dir > 0 && dir2 < 0)) return false;
	dir = abs(dir);
	dir2 = abs(dir2);
	if ((dir >= AIMap.GetMapSizeX() && dir2 < AIMap.GetMapSizeX()) ||
	    (dir < AIMap.GetMapSizeX() && dir2 >= AIMap.GetMapSizeX())) return false;

	return true;
}
