import FOV, { VisibilityCallback } from "./fov.js";

/** Octants used for translating recursive shadowcasting offsets */
const OCTANTS = [
	[-1,  0,  0,  1],
	[ 0, -1,  1,  0],
	[ 0, -1, -1,  0],
	[-1,  0,  0, -1],
	[ 1,  0,  0, -1],
	[ 0,  1, -1,  0],
	[ 0,  1,  1,  0],
	[ 1,  0,  0,  1]
];

/**
 * @class Recursive shadowcasting algorithm
 * Currently only supports 4/8 topologies, not hexagonal.
 * Based on Peter Harkins' implementation of Björn Bergström's algorithm described here: http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting
 * @augments ROT.FOV
 */
export default class RecursiveShadowcasting extends FOV {
	/**
	 * Compute visibility for a 360-degree circle
	 * @param {int} x
	 * @param {int} y
	 * @param {int} R Maximum visibility radius
	 * @param {function} callback
	 */
	compute(x: number, y: number, R: number, callback: VisibilityCallback) {
		//You can always see your own tile
		callback(x, y, 0, 1);
		for(let i = 0; i < OCTANTS.length; i++) {
			this._renderOctant(x, y, OCTANTS[i], R, callback);
		}
	}

	/**
	 * Compute visibility for a 180-degree arc
	 * @param {int} x
	 * @param {int} y
	 * @param {int} R Maximum visibility radius
	 * @param {int} dir Direction to look in (expressed in a ROT.DIRS value);
	 * @param {function} callback
	 */
	compute180(x: number, y: number, R: number, dir: number, callback: VisibilityCallback) {
		//You can always see your own tile
		callback(x, y, 0, 1);
		let previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 180 degrees
		let nextPreviousOctant = (dir - 2 + 8) % 8; //Need to retrieve the previous two octants to render a full 180 degrees
		let nextOctant = (dir+ 1 + 8) % 8; //Need to grab to next octant to render a full 180 degrees
		this._renderOctant(x, y, OCTANTS[nextPreviousOctant], R, callback);
		this._renderOctant(x, y, OCTANTS[previousOctant], R, callback);
		this._renderOctant(x, y, OCTANTS[dir], R, callback);
		this._renderOctant(x, y, OCTANTS[nextOctant], R, callback);
	};

	/**
	 * Compute visibility for a 90-degree arc
	 * @param {int} x
	 * @param {int} y
	 * @param {int} R Maximum visibility radius
	 * @param {int} dir Direction to look in (expressed in a ROT.DIRS value);
	 * @param {function} callback
	 */
	compute90(x: number, y: number, R: number, dir: number, callback: VisibilityCallback) {
		//You can always see your own tile
		callback(x, y, 0, 1);
		let previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 90 degrees
		this._renderOctant(x, y, OCTANTS[dir], R, callback);
		this._renderOctant(x, y, OCTANTS[previousOctant], R, callback);
	}

	/**
	 * Render one octant (45-degree arc) of the viewshed
	 * @param {int} x
	 * @param {int} y
	 * @param {int} octant Octant to be rendered
	 * @param {int} R Maximum visibility radius
	 * @param {function} callback
	 */
	_renderOctant(x: number, y: number, octant: number[], R: number, callback: VisibilityCallback) {
		//Radius incremented by 1 to provide same coverage area as other shadowcasting radiuses
		this._castVisibility(x, y, 1, 1.0, 0.0, R + 1, octant[0], octant[1], octant[2], octant[3], callback);
	}

	/**
	 * Actually calculates the visibility
	 * @param {int} startX The starting X coordinate
	 * @param {int} startY The starting Y coordinate
	 * @param {int} row The row to render
	 * @param {float} visSlopeStart The slope to start at
	 * @param {float} visSlopeEnd The slope to end at
	 * @param {int} radius The radius to reach out to
	 * @param {int} xx 
	 * @param {int} xy 
	 * @param {int} yx 
	 * @param {int} yy 
	 * @param {function} callback The callback to use when we hit a block that is visible
	 */
	_castVisibility(startX: number, startY: number, row: number, visSlopeStart: number, visSlopeEnd: number, radius: number, xx: number, xy: number, yx: number, yy: number, callback: VisibilityCallback) {
		if (visSlopeStart < visSlopeEnd) { return; }
		for (let i = row; i <= radius; i++) {
			let dx = -i - 1;
			let dy = -i;
			let blocked = false;
			let newStart = 0;

			//'Row' could be column, names here assume octant 0 and would be flipped for half the octants
			while (dx <= 0) {
				dx += 1;

				//Translate from relative coordinates to map coordinates
				let mapX = startX + dx * xx + dy * xy;
				let mapY = startY + dx * yx + dy * yy;

				//Range of the row
				let slopeStart = (dx - 0.5) / (dy + 0.5);
				let slopeEnd = (dx + 0.5) / (dy - 0.5);
			
				//Ignore if not yet at left edge of Octant
				if (slopeEnd > visSlopeStart) { continue; }
				
				//Done if past right edge
				if (slopeStart < visSlopeEnd) { break; }
					
				//If it's in range, it's visible
				if ((dx * dx + dy * dy) < (radius * radius)) {
					callback(mapX, mapY, i, 1);
				}
		
				if (!blocked) {
					//If tile is a blocking tile, cast around it
					if (!this._lightPasses(mapX, mapY) && i < radius) {
						blocked = true;
						this._castVisibility(startX, startY, i + 1, visSlopeStart, slopeStart, radius, xx, xy, yx, yy, callback);
						newStart = slopeEnd;
					}
				} else {
					//Keep narrowing if scanning across a block
					if (!this._lightPasses(mapX, mapY)) {

			
					//Block has ended
					blocked = false;
					visSlopeStart = newStart;
				}
			}
			if (blocked) { break; }
		}
	}
}

						newStart = slopeEnd;
						continue;
					}
	