/**
 * A Ship Pathfinder, forked from the default pathfinder
 *  This ship pathfinder tries to find a buildable / existing route for
 *  ship vehicles. You can changes the costs below using for example
 *  shippf.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 seas, set cost.canal to cost.max_cost.
 */
 require("aystarw.nut")
 require("aystarw2.nut")
class Ship
{
	estimate_multiplier = 2
		
	_max_cost = null;              ///< The maximum cost for a route.
	_cost_tile = null;             ///< The cost for a single tile.
	_cost_diagonal = null;         ///< The cost that is added for corners on the sea (diagonal route bonus!)
	_cost_turn = null;             ///< The cost that is added to _cost_tile if the direction changes.
	_cost_slope = null;            ///< The extra cost if a tile is sloped (we need to build a lock).
	_cost_canal = null;            ///< The extra cost for a canal tile.
	_pathfinder = null;            ///< A reference to the used AyStar object.
	_pathfinder2 = null;            ///< A reference to the used AyStar object.

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

	constructor()
	{
		this._max_cost = 10000000;
		this._cost_tile = 50;
		this._cost_diagonal = -10;
		this._cost_turn = 10;
		this._cost_slope = 50;
		this._cost_canal = 50;
		this._pathfinder = AyStarW(this, this._Cost, this._Estimate, this._Neighbours);
		this._pathfinder2 = AyStarW2(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(source, goal, ignored_tiles = []) {
		this._pathfinder.InitializePath([source, 0xFF], goal, ignored_tiles);
		this._pathfinder2.InitializePath([source, 0xFF], goal, 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 Ship.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 "turn":              this._main._cost_turn = val; break;
			case "diagonal":          this._main._cost_diagonal = val; break;
			case "slope":             this._main._cost_slope = val; break;
			case "canal":             this._main._cost_canal = 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 "turn":              return this._main._cost_turn;
			case "diagonal":          return this._main._cost_diagonal;
			case "slope":             return this._main._cost_slope;
			case "canal":             return this._main._cost_canal;
			default: throw("the index '" + idx + "' does not exist");
		}
	}

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

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

function Ship::_GetBridgeNumSlopes(end_a, end_b)
{
	return false
}

function Ship::_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;

	/* 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;
	
	/* Check if the new tile is a new canal tile. */
	if (!AITile.IsWaterTile(new_tile)) {
		cost += self._cost_canal;
		//costs of locks on slopes
		if (AITile.GetSlope(new_tile) != AITile.SLOPE_FLAT) {
			cost += self._cost_slope;
		}
		
		if (prev_path != null) {
			prevprev_tile = prev_path.GetTile();
			/* Check for a turn */
			if ((prev_tile - prevprev_tile) != (new_tile - prev_tile)) {
				cost += self._cost_turn;
			}
		}
	} else {
	
		if (prev_path != null) {
			prevprev_tile = prev_path.GetTile();
			/* Check for a turn */
			if ((prev_tile - prevprev_tile) != (new_tile - prev_tile)) {
				cost += self._cost_diagonal;
			}
		}
	}

	return path.GetCost() + cost;
}

function Ship::_Estimate(self, cur_tile, cur_direction, goal)
{
	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. */
	local dx = abs(AIMap.GetTileX(cur_tile) - AIMap.GetTileX(goal));
	local dy = abs(AIMap.GetTileY(cur_tile) - AIMap.GetTileY(goal));
	
	if ((dx+dy) < 12)
		return (dx+dy) * self._cost_tile
	//if (AITile.GetMinHeight(cur_tile) == 0)
	if (AITile.IsWaterTile(cur_tile))
		return self.estimate_multiplier*(dx+dy+abs(dx-dy)) * self._cost_tile
	return self.estimate_multiplier*(dx+dy+dx+dy+12) * self._cost_tile
	return min_cost;
}

function Ship::_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 prev_path = path.GetParent()
	local prev_tile = null
	if (prev_path != null) {
		prev_tile = prev_path.GetTile()
	}
	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;
		//Do not plan towards a tile that can not (easily) be used for water
		if (!AITile.IsBuildable(next_tile) && !AITile.IsWaterTile(next_tile)) continue;
		// don't reuse docks and depots
		if (AIMarine.IsDockTile(next_tile) || AIMarine.IsWaterDepotTile(next_tile)) continue;
		//make sure we do valid moves around existing locks
		local maxTileHeight = AITile.GetMaxHeight(next_tile)
		if (maxTileHeight > 0) {
			if (maxTileHeight > 5) continue; // too high!
			local slope = AITile.GetSlope(cur_node)
			//we can't use sloping rivers
			if ( AITile.GetSlope(next_tile) != AITile.SLOPE_FLAT && AITile.IsWaterTile(next_tile) && !AIMarine.IsLockTile(next_tile)) continue;
			//Don't do corners on around slopes (locks)
			if (cur_node - offset != prev_tile && (slope != AITile.SLOPE_FLAT ||
				AITile.GetSlope(next_tile) != AITile.SLOPE_FLAT || (prev_tile != null && AITile.GetSlope(prev_tile) != AITile.SLOPE_FLAT))) continue;
			//do not allow terraforming (yet)
			if (slope != AITile.SLOPE_FLAT) {
				if (slope != AITile.SLOPE_NW && slope != AITile.SLOPE_SW && slope != AITile.SLOPE_NE && slope != AITile.SLOPE_SE) continue;
				if (AITile.GetSlope(next_tile) != AITile.SLOPE_FLAT || AITile.GetSlope(next_tile + offset) != AITile.SLOPE_FLAT) continue;
				if (AITile.IsWaterTile(next_tile) && !AITile.IsWaterTile(next_tile+offset)) continue; //no blocking of other canals
				if (AITile.IsWaterTile(prev_tile) && !AITile.IsWaterTile(prev_tile-offset)) continue; //no blocking of other canals
				if (AIMarine.IsLockTile(cur_node + dXY(-1,1)) || AIMarine.IsLockTile(cur_node + dXY(1,1))) continue;   //no blocking of other locks
				if (AIMarine.IsLockTile(cur_node + dXY(1,-1)) || AIMarine.IsLockTile(cur_node + dXY(-1,-1))) continue; //no blocking of other locks
				/*LogTile(AITile.IsWaterTile(next_tile),next_tile)
				LogTile(AITile.IsWaterTile(next_tile+offset),next_tile+offset)
				LogTile(AITile.IsWaterTile(prev_tile),prev_tile)
				LogTile(AITile.IsWaterTile(prev_tile-offset),prev_tile-offset)
				Log("=====")*/
			}
		}
		tiles.push([next_tile, self._GetDirection(cur_node, next_tile)]);
	}
	return tiles;
}

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


function Ship::_GetTunnelsBridges(last_node, cur_node, bridge_dir)
{
	return [] //no bridges or tunnels
}

function Ship::_CheckTunnelBridge(current_tile, new_tile)
{
	return false //no bridges or tunnels
}
