/**
	AStar.c
	Configurable pathfinder for graphs.

	@author Luchs	
*/

// Generic A* implementation.
static const AStar = new Global
{
	/* Overwrite these functions */
	/* ========================= */

	// Distance heuristic for selecting nodes. A good heuristic will speed up
	// A*, a bad heuristic can lead to non-optimal paths.
	distance = func(node, goal) { FatalError("This function needs to be implemented by an actual algorithm. The generic function does not exist and throws this error on purpose."); },

	// Cost between two neighboring nodes. You'll need a different
	// implementation if your goal isn't a regular node.
	cost = func() { return this->distance(...); },

	// Returns an array of neighboring nodes.
	successors = func(a) { FatalError("This function needs to be implemented by an actual algorithm. The generic function does not exist and throws this error on purpose."); },

	// Equality function for nodes. DeepEqual works well for for objects as
	// well as state proplists, but you may be able to supply a more efficient
	// implementation.
	node_equal = DeepEqual,

	// Goal identification. You'll need a different implementation if your goal
	// isn't a regular node.
	goal_equal = func(node, goal) { return this->node_equal(node, goal); },

	/* Use these functions */
	/* =================== */

	// Find a path from start to goal. Valid types for both are defined by the
	// functions above.
	FindPath = func(start, goal)
	{
		var state = {
			open = [[this->distance(start, goal), 0, start]],
			closed = [],
			goal = goal,
		};
		var current;
		while (GetLength(state.open))
		{
			current = MinHeap->Extract(state.open);
			if (this->goal_equal(current[2], goal))
			{
				// Reconstruct the path.
				var path = [current[2]];
				while (current = current[3])
					PushFront(path, current[2]);
				return path;
			}
			PushBack(state.closed, current[2]);
			_Expand(state, current);
		}
		return nil;
	},

	/* Internal functions */
	/* ================== */

	_Expand = func(proplist state, array current)
	{
		/* Log("open: %v, closed: %v", state.open, state.closed); */
		/* Log("current: %v", current); */
		for (var successor in this->successors(current[2]))
		{
			/* Log(" - successor: %v", successor); */
			// Skip successor if it's in the closed list.
			var i = 0, el;
			while ((el = state.closed[i++]) && !this->node_equal(el, successor));
			if (el)
				continue;
			var cost = current[1] + this->cost(current[2], successor);
			// Find successor in the open list.
			i = 0;
			while ((el = state.open[i++]) && !this->node_equal(el[2], successor));
			if (el && el[1] <= cost)
				continue;
			successor = [cost + this->distance(successor, state.goal), cost, successor, current];
			if (el)
			{
				el[:] = successor;
				MinHeap->DecreaseKey(state.open, i-1);
			}
			else
				MinHeap->Insert(state.open, successor);
		}
	},

};

// Sample implementation for searching paths on the map, i.e. "intelligent PathFree()".
//
// Graph nodes are a regular grid over the landscape with a configurable size
// (step). Nodes are represented as {x, y} proplists.
static const AStarMap = new AStar
{
	// This function is used both as heuristic (node to the goal) and as cost
	// function (two neighboring nodes).
	distance = func(proplist a, proplist b)
	{
		// Manhattan distance
		return Abs(a.x - b.x) + Abs(a.y - b.y);
	},

	// Returns all neighboring nodes (right/down/left/up) with a free path.
	successors = func(proplist a)
	{
		var successors = [], pt;
		if (pathfree(a, (pt = {x = a.x + this.step, y = a.y}))) PushBack(successors, pt);
		if (pathfree(a, (pt = {x = a.x, y = a.y + this.step}))) PushBack(successors, pt);
		if (pathfree(a, (pt = {x = a.x - this.step, y = a.y}))) PushBack(successors, pt);
		if (pathfree(a, (pt = {x = a.x, y = a.y - this.step}))) PushBack(successors, pt);
		return successors;
	},

	// Helper functions for successors()
	pathfree = func(proplist a, proplist b)
	{
		return PathFree(a.x, a.y, b.x, b.y);
	},

	// Start and goal may not be a multiple of `step` apart.
	goal_equal = func(proplist node, proplist goal)
	{
		var nx = node.x / step, ny = node.y / step;
		var gx = goal.x / step, gy = goal.y / step;
		return (nx == gx || nx == gx + 1) && (ny == gy || ny == gy + 1) && pathfree(node, goal);
	},

	step = 10

};

/* Binary Min-Heap */
static const MinHeap = new Global
{
	// A heap is an array of [key, value] array-tuples.
	Heapify = func(array heap, int i)
	{
		var min = i, size = GetLength(heap);
		var left = 2*i+1, right = 2*i+2;
		if (left < size && heap[left][0] < heap[min][0])
			min = left;
		if (right < size && heap[right][0] < heap[min][0])
			min = right;
		if (min != i)
		{
			ArraySwap(heap, min, i);
			Heapify(heap, min);
		}
	},

	BuildHeap = func(array a)
	{
		for (var i = GetLength(a)/2-1; i >= 0; --i)
			Heapify(a, i);
	},

	DecreaseKey = func(array heap, int i)
	{
		var parent;
		while (i > 0 && heap[i][0] < heap[(parent = (i-1)/2)][0])
		{
			ArraySwap(heap, i, parent);
			i = parent;
		}
	},

	Insert = func(array heap, array kv)
	{
		var i = GetLength(heap);
		heap[i] = kv;
		DecreaseKey(heap, i);
	},

	Extract = func(array heap)
	{
		var item = heap[0], last = PopBack(heap);
		if (GetLength(heap))
		{
			heap[0] = last;
			Heapify(heap, 0);
		}
		return item;
	},

	ArraySwap = func(array a, int i, int j)
	{
		var tmp = a[i];
		a[i] = a[j];
		a[j] = tmp;
	},

};
