/* $Id: main.nut 15101 2009-01-16 00:05:26Z truebrain $ */
require("aystarr.nut")

function BuildSign(tile, text) {
	local mode = AIExecMode();
	AISign.BuildSign(tile, text);
}
	
/**
 * A Rail Pathfinder forked from the ChooChoo AI.
 */
class Rail
{
	estimate_multiplier = 3;
	
	_max_cost = null;              ///< The maximum cost for a route.
	_cost_tile = null;             ///< The cost for a single tile.
	_cost_diagonal_tile = null;    ///< The cost for a diagonal tile.
	_cost_turn = null;             ///< The cost that is added to _cost_tile if the direction changes.
	_cost_sharp_turn = null;       ///< The cost that is added to _cost_tile if to turns in the same direction are following each other
	_cost_slope = null;            ///< The extra cost if a rail 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.
	_cost_no_adj_rail = null;      ///< The extra cost for no rail in an adjacent tile.
	_cost_adj_obstacle = null;     ///< The extra cost for an obstacle in an adjacent 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 = null;                   ///< Used to change the costs.
	_running = null;
	_goals = null;
	_sources = null;
	
	follow = null;
	
	constructor()
	{
		this._max_cost = 10000000;
		this._cost_tile = 100;
		this._cost_diagonal_tile = 68 //63 gives too many diagonal track sections
		this._cost_turn = 20;
		this._cost_sharp_turn = 600;
		this._cost_slope = 100;
		this._cost_bridge_per_tile = 150;
		this._cost_tunnel_per_tile = 120;
		this._cost_coast = 20;
		this._cost_no_adj_rail = 0;
		this._cost_adj_obstacle = 20;
		this._max_bridge_length = 12;
		this._max_tunnel_length = 12;
		this._pathfinder = AyStarR(this, this._Cost, this._Estimate, this._Neighbours);
		
		this.cost = this.Cost(this);
		this._running = false;
		this.follow = null;
	}
	
	function setPathToFollow(path) {
		this.follow = path
	}

	/**
	 * 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) {
			local path = this._pathfinder.Path(null, node[1], 0xFF, this._Cost, this);
			path = this._pathfinder.Path(path, node[0], 0xFF, this._Cost, this);
			nsources.push(path);
		}
		this._goals = goals;
		this._sources = sources;
		this._pathfinder.InitializePath(nsources, goals, ignored_tiles);
		
		
		/*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 Rail.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 "max_cost":          this._main._max_cost = val; break;
			case "tile":              this._main._cost_tile = val; break;
			case "diagonal_tile":     this._main._cost_diagonal_tile = val; break;
			case "turn":              this._main._cost_turn = val; break;
			case "sharp_turn":        this._main._cost_sharp_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 "no_adj_rail":       this._main._cost_no_adj_rail = val; break;
			case "adj_obstacle":      this._main._cost_adj_obstacle = 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 "max_cost":          return this._main._max_cost;
			case "tile":              return this._main._cost_tile;
			case "diagonal_tile":     return this._main._cost_diagonal_tile;
			case "turn":              return this._main._cost_turn;
			case "sharp_turn":        return this._main._cost_sharp_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 "no_adj_rail":       return this._main._cost_no_adj_rail;
			case "adj_obstacle":      return this._main._cost_adj_obstacle;
			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 Rail::FindPath(iterations)
{
	local test_mode = null
	if (this.follow == null)
		test_mode = AITestMode()
	else {
		test_mode = AIExecMode()
		LogWarning("finding path in executive mode!")
	}
	local ret = this._pathfinder.FindPath(iterations);
	this._running = (ret == false) ? true : false;
	if (!this._running && ret != null) {
		foreach (goal in this._goals) {
			if (goal[0] == ret.GetTile()) {
				return this._pathfinder.Path(ret, goal[1], 0, this._Cost, this);
			}
		}
	}
	return ret;
}

function Rail::_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 Rail::_nonzero(a, b)
{
	return a != 0 ? a : b;
}

function Rail::_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 prevprev_path = null
	local prev_direction = path.GetDirection()
	local prevprev_direction = new_direction //defaults to the current direction
	local pprevprev_direction = new_direction //defaults to the current direction
	if (prev_path != null) {
		prevprev_path = prev_path.GetParent()
		prevprev_direction = prev_path.GetDirection()
		if (prevprev_path != null)
			pprevprev_direction = prevprev_path.GetDirection()
	}
	local prev_tile = path.GetTile();
	local prevprev_tile = null;
	local pprevprev_tile = null;
	local ppprevprev_tile = null;
	local pppprevprev_tile = null;
	local ppppprevprev_tile = null;
	if (prev_path != null) {
		prevprev_tile = prev_path.GetTile();
		if (prevprev_path != null) {
			pprevprev_tile = prevprev_path.GetTile();
			if (prevprev_path.GetParent() != null) {
				ppprevprev_tile = prevprev_path.GetParent().GetTile();
				if (prevprev_path.GetParent().GetParent() != null) {
					pppprevprev_tile = prevprev_path.GetParent().GetParent().GetTile();
					if (prevprev_path.GetParent().GetParent().GetParent() != null) {
						ppppprevprev_tile = prevprev_path.GetParent().GetParent().GetParent().GetTile();
					}
				}
			}
		}
	}

	/* 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. */
	if (AIMap.DistanceManhattan(new_tile, prev_tile) > 1) {
		/* Check if we should build a bridge or a tunnel. */
		local cost = path.GetCost();
		if (AITunnel.GetOtherTunnelEnd(new_tile) == prev_tile) {
			cost += AIMap.DistanceManhattan(new_tile, prev_tile) * (self._cost_tile + self._cost_tunnel_per_tile);
		} else {
			cost += AIMap.DistanceManhattan(new_tile, prev_tile) * (self._cost_tile + self._cost_bridge_per_tile);// + self._GetBridgeNumSlopes(new_tile, prev_tile) * self._cost_slope;
		}
		if (pprevprev_tile != null &&
				pprevprev_tile - prevprev_tile != max(AIMap.GetTileX(prev_tile) - AIMap.GetTileX(new_tile), AIMap.GetTileY(prev_tile) - AIMap.GetTileY(new_tile)) / AIMap.DistanceManhattan(new_tile, prev_tile)) {
			cost += self._cost_turn;
		}
		
		return cost;
	}

	local cost = self._cost_tile;
	

	
	local diagonal = prev_direction != new_direction
	if (diagonal)
		cost = self._cost_diagonal_tile
	local turn = prevprev_direction != new_direction
	if (turn) {
		cost += self._cost_turn
		local sharp_turn = pprevprev_direction != new_direction
		if (sharp_turn)
			cost += self._cost_sharp_turn
	}

	
	/* Check for track and obstacles in neighbour tiles. */
	local offsets = [
		AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
		AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];

	
	offsets.extend([
		AIMap.GetTileIndex(0, 2), AIMap.GetTileIndex(0, -2),
		AIMap.GetTileIndex(2, 0), AIMap.GetTileIndex(-2, 0)]);
	local obstacles = 0;
	foreach (offset in offsets) {
		local neighbour = new_tile + offset;
		if (!AITile.IsBuildable(neighbour) && !HasAnyRoad(neighbour)) {
			obstacles += 1;
		}
	}

	cost += obstacles*self._cost_adj_obstacle;
	
	/* Check if the new tile is a coast tile. */
	if (AITile.IsCoastTile(new_tile)) {
		cost += self._cost_coast;
	}

	/* Check if the last tile was sloped. */
	if (prevprev_tile != null && !AIBridge.IsBridgeTile(prev_tile) && !AITunnel.IsTunnelTile(prev_tile) &&
			AITile.GetSlope(prev_tile) != AITile.SLOPE_FLAT) {
		cost += self._cost_slope
	}

	/* We don't use already existing rail, so the following code is unused. It
	 *  assigns if no rail exists along the route. */
	/*
	if (prev_path != null && !AIRail.AreTilesConnected(prev_path.GetTile(), prev_tile, new_tile)) {
		cost += self._cost_no_existing_rail;
	}
	*/
	
	return path.GetCost() + cost;
}

function Rail::_Estimate(self, cur_tile, cur_direction, goal_tiles)
{
	/* 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[0]));
		local dy = abs(AIMap.GetTileY(cur_tile) - AIMap.GetTileY(tile[0]));
		
		if ((dx+dy) < 12)
			return (min(dx, dy) * self._cost_diagonal_tile * 2 + abs(dx-dy) * self._cost_tile)
		return self.estimate_multiplier*(min(dx, dy) * self._cost_diagonal_tile * 2 + abs(dx-dy) * self._cost_tile)
	}
}

function Rail::_Neighbours(self, path, cur_node)
{
	if (AITile.HasTransportType(cur_node, AITile.TRANSPORT_RAIL)) return [];
	/* 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 prevprev_path = null
	local prev_tile = 0
	local prevprev_tile = 0
	local direction = path.GetDirection()
	local prev_direction = direction
	local prevprev_direction = 0
	local distance = 1
	if (prev_path != null) {
		prev_direction = prev_path.GetDirection()
		prev_tile = prev_path.GetTile()
		prevprev_path = prev_path.GetParent()
		if (prevprev_path != null) {
			prevprev_direction = prevprev_path.GetDirection()
			prevprev_tile = prevprev_path.GetTile()
		} else {
			prevprev_direction = prev_direction
		}
		distance = AIMap.DistanceManhattan(cur_node, prev_tile)
		if (distance > 1) {
			local next_tile = cur_node + (cur_node - prev_tile) / distance;
			foreach (offset in offsets) {
				if (AIRail.BuildRail(cur_node, next_tile, next_tile + offset)) {
					tiles.push([next_tile, self._GetDirection(prev_tile, cur_node, next_tile, true)]);
				}
			}
		} else {
			// Check all tiles adjacent to the current tile.
			foreach (offset in offsets) {
				local next_tile = cur_node + offset;
				//Do not plan towards non-buildable tile
				if (!AITile.IsBuildable(next_tile) && !AIRail.IsRailTile(next_tile) && !HasAnyRoad(next_tile)) continue;
				// Don't turn back
				if (next_tile == prev_tile) continue;
				
				local next_direction = self._GetDirection(null, cur_node, next_tile, false)
				
				// Disallow 90 degree turns
				//if (prevprev_path != null && next_tile - cur_node == prevprev_tile - prev_tile) continue;
				if (next_tile - cur_node == prevprev_tile - prev_tile) continue;
				// Disallow double 45 degree turns
				if (next_direction == direction && next_direction != prev_direction && prevprev_direction == prev_direction) continue
				// We add them to the to the neighbours-list if we can build a rail to  them and no rail exists there.
				if (AIRail.BuildRail(prev_tile, cur_node, next_tile)) {
					tiles.push([next_tile, next_direction]);
				}
			}
			//Check for sharp turns directly in front of the bridge
			if (direction == prev_direction || prevprev_direction != prev_direction) {
				local bridges = self._GetTunnelsBridges(prev_tile, cur_node, self._GetDirection(null, prev_tile, cur_node, true));
				foreach (tile in bridges) {
					tiles.push(tile);
				}
			}
		}		
		
	} else {//prev_path == null
		foreach (offset in offsets) {
			local next_tile = cur_node + offset;
			//Do not plan towards non-buildable tile
			if (!AITile.IsBuildable(next_tile) && !AIRail.IsRailTile(next_tile) && !HasAnyRoad(next_tile)) continue;
			// Don't turn back
			if (next_tile == prev_tile) continue;
			
			local next_direction = self._GetDirection(null, cur_node, next_tile, false)
			//No more checks needed, push to the neighbours-list
			tiles.push([next_tile, next_direction])
		}
	}
	

	return tiles;
}

function Rail::_dir(from, to, canbeBridge = false)
{
	local diff = to - from
	if (diff == 1) return Direction.SW;
	if (diff == -1) return Direction.NE;
	if (diff == AIMap.GetMapSizeX()) return Direction.SE;
	if (diff == -AIMap.GetMapSizeX()) return Direction.NW;
	if (canbeBridge) {
		local distance = AIMap.DistanceManhattan(from, to);
		diff = diff / distance;
		if (diff == 1) return Direction.SW;
		if (diff == -1) return Direction.NE;
		if (diff == AIMap.GetMapSizeX()) return Direction.SE;
		if (diff == -AIMap.GetMapSizeX()) return Direction.NW;
	}
	throw("Shouldn't come here in _dir. from: " + from + " to: " + to);
}

function Rail::_GetDirection(pre_from, from, to, is_bridge)
{
	return this._dir(from, to)
	//if (is_bridge) {
		if (from - to == 1) return 1;
		if (from - to == -1) return 2;
		if (from - to == AIMap.GetMapSizeX()) return 4;
		if (from - to == -AIMap.GetMapSizeX()) return 8;
	//}
	return 1 << (4 + (pre_from == null ? 0 : 4 * this._dir(pre_from, from)) + this._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 Rail::_GetTunnelsBridges(last_node, cur_node, bridge_dir)
{
	local delta = (cur_node - last_node)
	local next = cur_node + delta
	local nextnext = next + delta
	//if (slope == AITile.SLOPE_FLAT && AITile.IsBuildable(next)) {
	local tiles = [];
	if (AITile.IsBuildable(next)) {
		if (AITile.IsBuildable(nextnext))
			return [];
		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]);
				}
			}
		}
		
			
	}

	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_RAIL, bridge_list.Begin(), cur_node, target)) {
			tiles.push([target, bridge_dir]);
			break
		}
	}

	local slope = AITile.GetSlope(cur_node);
	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_RAIL, cur_node)) {
		tiles.push([other_tunnel_end, bridge_dir]);
	}
	return tiles;
}


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


