/**
 * An AyStar implementation for Water.
 *  It solves graphs by finding the fastest route from one point to the other.
 * Some optimizations have been applied compared to the library implementation
 */
class AyStarW2
{
	_queue_class = import("queue.binary_heap", "", 1);
	_pf_instance = null;
	_cost_callback = null;
	_estimate_callback = null;
	_neighbours_callback = null;
	_check_direction_callback = null;
	_open = null;
	_closed = null;
	_goal = null;

	/**
	 * @param pf_instance An instance that'll be used as 'this' for all
	 *  the callback functions.
	 * @param cost_callback A function that returns the cost of a path. It
	 *  should accept four parameters, old_path, new_tile, new_direction and
	 *  cost_callback_param. old_path is an instance of AyStar.Path, and
	 *  new_node is the new node that is added to that path. It should return
	 *  the cost of the path including new_node.
	 * @param estimate_callback A function that returns an estimate from a node
	 *  to the goal node. It should accept four parameters, tile, direction,
	 *  goal_nodes and estimate_callback_param. It should return an estimate to
	 *  the cost from the lowest cost between node and any node out of goal_nodes.
	 *  Note that this estimate is not allowed to be higher than the real cost
	 *  between node and any of goal_nodes. A lower value is fine, however the
	 *  closer it is to the real value, the better the performance.
	 * @param neighbours_callback A function that returns all neighbouring nodes
	 *  from a given node. It should accept three parameters, current_path, node
	 *  and neighbours_callback_param. It should return an array containing all
	 *  neighbouring nodes, which are an array in the form [tile, direction].
	 */
	constructor(pf_instance, cost_callback, estimate_callback, neighbours_callback)
	{
		if (typeof(pf_instance) != "instance") throw("'pf_instance' has to be an instance.");
		if (typeof(cost_callback) != "function") throw("'cost_callback' has to be a function-pointer.");
		if (typeof(estimate_callback) != "function") throw("'estimate_callback' has to be a function-pointer.");
		if (typeof(neighbours_callback) != "function") throw("'neighbours_callback' has to be a function-pointer.");

		this._pf_instance = pf_instance;
		this._cost_callback = cost_callback;
		this._estimate_callback = estimate_callback;
		this._neighbours_callback = neighbours_callback;
	}

	/**
	 * Initialize a path search between sources and goal.
	 * @param sources The source nodes. This can an array of either [tile, direction]-pairs or AyStar.Path-instances.
	 * @param goal The target tile.
	 * @param ignored_tiles An array of tiles that cannot occur in the final path.
	 */
	function InitializePath(node, 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.
	 */
	function FindPath(iterations);
};

function AyStarW2::InitializePath(node, goal, ignored_tiles = [])
{
	this._open = this._queue_class();
	this._closed = AIList();

	if (typeof(node) == "array") {
		if (node[1] <= 0) throw("directional value should never be zero or negative.");

		local new_path = this.Path(null, node[0], node[1], this._cost_callback, this._pf_instance);
		this._open.Insert(new_path, new_path.GetCost() + this._estimate_callback(this._pf_instance, node[0], node[1], goal));
	} else {
		this._open.Insert(node, node.GetCost());
		throw("error")
	}

	this._goal = goal;

	foreach (tile in ignored_tiles) {
		this._closed.AddItem(tile, ~0);
	}
}

function AyStarW2::FindPath(iterations)
{
	if (this._open == null) throw("can't execute over an uninitialized path");
	local goal = this._goal
	while (this._open.Count() > 0 && (iterations-- > 0)) {
		/* Get the path with the best score so far */
		local path = this._open.Pop();
		local cur_tile = path._tile;
		/* Make sure we didn't already passed it */
		if (this._closed.HasItem(cur_tile)) {
			/* For water we don't care about directions, skip this entry */
			continue;
		} else {
			/* New entry, make sure we don't check it again */
			this._closed.AddItem(cur_tile, ~0);
		}
		/* Check if we found the end */
		if (cur_tile == goal) {
			this._CleanPath();
			return path;
		}
		/* Scan all neighbours */
		local neighbours = this._neighbours_callback(this._pf_instance, path, cur_tile);
		foreach (node in neighbours) {
			if (this._closed.HasItem(node[0])) continue;
			/* Calculate the new paths and add them to the open list */
			local new_path = this.Path(path, node[0], node[1], this._cost_callback, this._pf_instance);
			this._open.Insert(new_path, new_path.GetCost() + this._estimate_callback(this._pf_instance, node[0], node[1], goal));
		}
	}

	if (this._open.Count() > 0) return false;
	this._CleanPath();
	return null;
}

function AyStarW2::_CleanPath()
{
	this._closed = null;
	this._open = null;
	this._goal = null;
}

/**
 * The path of the AyStar algorithm.
 *  It is reversed, that is, the first entry is more close to the goal-nodes
 *  than his GetParent(). You can walk this list to find the whole path.
 *  The last entry has a GetParent() of null.
 */
class AyStarW2.Path
{
	_prev = null;
	_tile = null;
	_direction = null;
	_cost = null;
	//_length = null;

	constructor(old_path, new_tile, new_direction, cost_callback, pf_instance)
	{
		this._prev = old_path;
		this._tile = new_tile;
		this._direction = new_direction;
		this._cost = cost_callback(pf_instance, old_path, new_tile, new_direction);
		/*if (old_path == null) {
			this._length = 0;
		} else {
			this._length = old_path.GetLength() + AIMap.DistanceManhattan(old_path.GetTile(), new_tile);
		}*/
	};

	/**
	 * Return the tile where this (partial-)path ends.
	 */
	function GetTile() { return this._tile; }

	/**
	 * Return the direction from which we entered the tile in this (partial-)path.
	 */
	function GetDirection() { return this._direction; }

	/**
	 * Return an instance of this class leading to the previous node.
	 */
	function GetParent() { return this._prev; }

	/**
	 * Return the cost of this (partial-)path from the beginning up to this node.
	 */
	function GetCost() { return this._cost; }

	/**
	 * Return the length (in tiles) of this path.
	 */
	//function GetLength() { return this._length; }
};
