module IQ.Image.SpaceFillingCurves {

	enum Direction {
		NONE = 0,
		UP,
		LEFT,
		RIGHT,
		DOWN,
	}

	// Check code against double-entrance into walk (walk=> callback => walk)
	export class HilbertCurveBase {
		private _x : number;
		private _y : number;
		private _d : number;
		private _width : number;
		private _height : number;
		private _callback : (x : number, y : number, d : number) => void;
		private _level : number;

		public walk(width : number, height : number, visitorCallback : (x : number, y : number, d : number) => void) : void {
			this._x        = 0;
			this._y        = 0;
			this._d        = 0;
			this._width    = width;
			this._height   = height;
			this._callback = visitorCallback;

			var maxBound = Math.max(width, height);

			this._level    = (Math.log(maxBound) / Math.log(2) + 1) | 0;
			this._walkHilbert(Direction.UP);
			this._visit(Direction.NONE);
		}

		_walkHilbert(direction) {
			if (this._level < 1) return;

			this._level--;
			switch (direction) {
				case Direction.LEFT:
					this._walkHilbert(Direction.UP);
					this._visit(Direction.RIGHT);
					this._walkHilbert(Direction.LEFT);
					this._visit(Direction.DOWN);
					this._walkHilbert(Direction.LEFT);
					this._visit(Direction.LEFT);
					this._walkHilbert(Direction.DOWN);
					break;

				case Direction.RIGHT:
					this._walkHilbert(Direction.DOWN);
					this._visit(Direction.LEFT);
					this._walkHilbert(Direction.RIGHT);
					this._visit(Direction.UP);
					this._walkHilbert(Direction.RIGHT);
					this._visit(Direction.RIGHT);
					this._walkHilbert(Direction.UP);
					break;

				case Direction.UP:
					this._walkHilbert(Direction.LEFT);
					this._visit(Direction.DOWN);
					this._walkHilbert(Direction.UP);
					this._visit(Direction.RIGHT);
					this._walkHilbert(Direction.UP);
					this._visit(Direction.UP);
					this._walkHilbert(Direction.RIGHT);
					break;

				case Direction.DOWN:
					this._walkHilbert(Direction.RIGHT);
					this._visit(Direction.UP);
					this._walkHilbert(Direction.DOWN);
					this._visit(Direction.LEFT);
					this._walkHilbert(Direction.DOWN);
					this._visit(Direction.DOWN);
					this._walkHilbert(Direction.LEFT);
					break;

				default:
					break;
			}
			this._level++;
		}

		private _visit(direction : Direction) : void {
			if (this._x >= 0 && this._x < this._width && this._y >= 0 && this._y < this._height) {
				this._callback(this._x, this._y, this._d);
				this._d++;
			}
			switch (direction) {
				case Direction.LEFT:
					this._x--;
					break;
				case Direction.RIGHT:
					this._x++;
					break;
				case Direction.UP:
					this._y--;
					break;
				case Direction.DOWN:
					this._y++;
					break;
			}
		}
	}
}
