/*
 * TerrainAnalysis inherits from Map
 * 
 * This creates a suitable passability map for pathfinding units and provides the findClosestPassablePoint() function.
 * This is intended to be a base object for the terrain analysis modules to inherit from. 
 */ 

function TerrainAnalysis(gameState){
	var passabilityMap = gameState.getMap();

	var obstructionMask = gameState.getPassabilityClassMask("pathfinderObstruction");
	obstructionMask |= gameState.getPassabilityClassMask("default");
	
	var obstructionTiles = new Uint16Array(passabilityMap.data.length); 
	for (var i = 0; i < passabilityMap.data.length; ++i) 
	{ 
		obstructionTiles[i] = (passabilityMap.data[i] & obstructionMask) ? 0 : 65535; 
	}
	
	this.Map(gameState, obstructionTiles);
};

copyPrototype(TerrainAnalysis, Map);

// Returns the (approximately) closest point which is passable by searching in a spiral pattern 
TerrainAnalysis.prototype.findClosestPassablePoint = function(startPoint, quick){
	var w = this.width;
	var p = startPoint;
	var direction = 1;
	
	if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length &&
			this.map[p[0] + w*p[1]] != 0){
		if (this.countConnected(p, 10) >= 10){
			return p;
		}
	}
	
	// search in a spiral pattern.
	for (var i = 1; i < w; i++){
		for (var j = 0; j < 2; j++){
			for (var k = 0; k < i; k++){
				p[j] += direction;
				if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length &&
						this.map[p[0] + w*p[1]] != 0){
					if (quick || this.countConnected(p, 10) >= 10){
						return p;
					}
				}
			}
		}
		direction *= -1;
	}
	
	return undefined;
};

// Counts how many accessible tiles there are connected to the start Point.  If there are >= maxCount then it stops.
// This is inefficient for large areas so maxCount should be kept small for efficiency.
TerrainAnalysis.prototype.countConnected = function(startPoint, maxCount, curCount, checked){
	curCount = curCount || 0;
	checked = checked || [];
	
	var w = this.width;
	
	var positions = [[0,1], [0,-1], [1,0], [-1,0]];
	
	curCount += 1; // add 1 for the current point
	checked.push(startPoint);
	if (curCount >= maxCount){
		return curCount;
	}
	
	for (var i in positions){
		var p = [startPoint[0] + positions[i][0], startPoint[1] + positions[i][1]];
		if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length &&
				this.map[p[0] + w*p[1]] != 0 && !(p in checked)){
			curCount += this.countConnected(p, maxCount, curCount, checked);
		}
	}
	return curCount;
};

/*
 * PathFinder inherits from TerrainAnalysis
 *  
 * Used to create a list of distinct paths between two points. 
 * 
 * Currently it works with a basic implementation which should be improved.
 * 
 * TODO: Make this use territories.
 */


function PathFinder(gameState){
	this.TerrainAnalysis(gameState);
	
	this.territoryMap = Map.createTerritoryMap(gameState);
}

copyPrototype(PathFinder, TerrainAnalysis);

/*
 * Returns a list of distinct paths to the destination.  Currently paths are distinct if they are more than 
 * blockRadius apart at a distance of blockPlacementRadius from the destination.  Where blockRadius and 
 * blockPlacementRadius are defined in walkGradient
 */
PathFinder.prototype.getPaths = function(start, end, mode){
	var s = this.findClosestPassablePoint(this.gamePosToMapPos(start));
	var e = this.findClosestPassablePoint(this.gamePosToMapPos(end));
	
	if (!s || !e){
		return undefined;
	}
	
	var paths = [];
	
	while (true){
		this.makeGradient(s,e);
		var curPath = this.walkGradient(e, mode);
		if (curPath !== undefined){
			paths.push(curPath);
		}else{
			break;
		}
		this.wipeGradient();
	}
	
	//this.dumpIm("terrainanalysis.png", 511);
	
	if (paths.length > 0){
		return paths;
	}else{
		return undefined;
	}
};

// Creates a potential gradient with the start point having the lowest potential
PathFinder.prototype.makeGradient = function(start, end){
	var w = this.width;
	var map = this.map;
	
	// Holds the list of current points to work outwards from
	var stack = [];
	// We store the next level in its own stack
	var newStack = [];
	// Relative positions or new cells from the current one.  We alternate between the adjacent 4 and 8 cells
	// so that there is an average 1.5 distance for diagonals which is close to the actual sqrt(2) ~ 1.41
	var positions = [[[0,1], [0,-1], [1,0], [-1,0]], 
	                 [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]]];
	
	//Set the distance of the start point to be 1 to distinguish it from the impassable areas
	map[start[0] + w*(start[1])] = 1;
	stack.push(start);
	
	// while there are new points being added to the stack
	while (stack.length > 0){
		//run through the current stack
		while (stack.length > 0){
			var cur = stack.pop();
			// stop when we reach the end point
			if (cur[0] == end[0] && cur[1] == end[1]){
				return;
			}
			
			var dist = map[cur[0] + w*(cur[1])] + 1;
			// Check the positions adjacent to the current cell
			for (var i = 0; i < positions[dist % 2].length; i++){
				var pos = positions[dist % 2][i];
				var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]);
				if (cell >= 0 && cell < this.length && map[cell] > dist){
					map[cell] = dist;
					newStack.push([cur[0]+pos[0], cur[1]+pos[1]]);
				}
			}
		}
		// Replace the old empty stack with the newly filled one.
		stack = newStack;
		newStack = [];
	}
	
};

// Clears the map to just have the obstructions marked on it.
PathFinder.prototype.wipeGradient = function(){
	for (var i = 0; i < this.length; i++){
		if (this.map[i] > 0){
			this.map[i] = 65535;
		}
	}
};

// Returns the path down a gradient from the start to the bottom of the gradient, returns a point for every 20 cells in normal mode
// in entryPoints mode this returns the point where the path enters the region near the destination, currently defined 
// by blockPlacementRadius.  Note doesn't return a path when the destination is within the blockpoint radius.
PathFinder.prototype.walkGradient = function(start, mode){
	var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]];
	
	var path = [[start[0]*this.cellSize, start[1]*this.cellSize]];
	
	var blockPoint = undefined;
	var blockPlacementRadius = 45;
	var blockRadius = 23;
	var count = 0;
	
	var cur = start;
	var w = this.width;
	var dist = this.map[cur[0] + w*cur[1]];
	var moved = false;
	while (this.map[cur[0] + w*cur[1]] !== 0){
		for (var i = 0; i < positions.length; i++){
			var pos = positions[i];
			var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]);
			if (cell >= 0 && cell < this.length && this.map[cell] > 0 &&  this.map[cell] < dist){
				dist = this.map[cell];
				cur = [cur[0]+pos[0], cur[1]+pos[1]];
				moved = true;
				count++;
				// Mark the point to put an obstruction at before calculating the next path
				if (count === blockPlacementRadius){
					blockPoint = cur;
				}
				// Add waypoints to the path, fairly well spaced apart.
				if (count % 40 === 0){
					path.unshift([cur[0]*this.cellSize, cur[1]*this.cellSize]);
				}
				break;
			}
		}
		if (!moved){
			break;
		}
		moved = false;
	}
	if (blockPoint === undefined){
		return undefined;
	}
	// Add an obstruction to the map at the blockpoint so the next path will take a different route.
	this.addInfluence(blockPoint[0], blockPoint[1], blockRadius, -10000, 'constant');
	if (mode === 'entryPoints'){
		// returns the point where the path enters the blockPlacementRadius
		return [blockPoint[0] * this.cellSize, blockPoint[1] * this.cellSize];
	}else{
		// return a path of points 20 squares apart on the route
		return path;
	}
};

// Would be used to calculate the width of a chokepoint
// NOTE: Doesn't currently work.
PathFinder.prototype.countAttached = function(pos){
	var positions = [[0,1], [0,-1], [1,0], [-1,0]];
	var w = this.width;
	var val = this.map[pos[0] + w*pos[1]];
	
	var stack = [pos];
	var used = {};
	
	while (stack.length > 0){
		var cur = stack.pop();
		used[cur[0] + " " + cur[1]] = true;
		for (var i = 0; i < positions.length; i++){
			var p = positions[i];
			var cell = cur[0]+p[0] + w*(cur[1]+p[1]);
			
		}
	}
};

/*
 * Accessibility inherits from TerrainAnalysis
 *  
 * Determines whether there is a path from one point to another.  It is initialised with a single point (p1) and then 
 * can efficiently determine if another point is reachable from p1.  Initialising the object is costly so it should be 
 * cached.
 */

function Accessibility(gameState, location){
	this.TerrainAnalysis(gameState);
	
	var start = this.findClosestPassablePoint(this.gamePosToMapPos(location));
	
	// Check that the accessible region is a decent size, otherwise obstacles close to the start point can create
	// tiny accessible areas which makes the rest of the map inaceesible.
	var iterations = 0;
	while (this.floodFill(start) < 20 && iterations < 30){
		this.map[start[0] + this.width*(start[1])] = 0;
		start = this.findClosestPassablePoint(this.gamePosToMapPos(location));
		iterations += 1;
	}
	
}

copyPrototype(Accessibility, TerrainAnalysis);

// Return true if the given point is accessible from the point given when initialising the Accessibility object. #
// If the given point is impassable the closest passable point is used.
Accessibility.prototype.isAccessible = function(position){
	var s = this.findClosestPassablePoint(this.gamePosToMapPos(position), true);
	
	return this.map[s[0] + this.width * s[1]] === 1;
};

// fill all of the accessible areas with value 1
Accessibility.prototype.floodFill = function(start){
	var w = this.width;
	var map = this.map;
	
	// Holds the list of current points to work outwards from
	var stack = [];
	// We store new points to be added to the stack temporarily in here while we run through the current stack
	var newStack = [];
	// Relative positions or new cells from the current one.
	var positions = [[0,1], [0,-1], [1,0], [-1,0]];
	
	// Set the start point to be accessible
	map[start[0] + w*(start[1])] = 1;
	stack.push(start);
	
	var count = 0;
	
	// while there are new points being added to the stack
	while (stack.length > 0){
		//run through the current stack
		while (stack.length > 0){
			var cur = stack.pop();
			
			// Check the positions adjacent to the current cell
			for (var i = 0; i < positions.length; i++){
				var pos = positions[i];
				var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]);
				if (cell >= 0 && cell < this.length && map[cell] > 1){
					map[cell] = 1;
					newStack.push([cur[0]+pos[0], cur[1]+pos[1]]);
					count += 1;
				}
			}
		}
		// Replace the old empty stack with the newly filled one.
		stack = newStack;
		newStack = [];
	}
	return count;
};