Engine.PanZoomContainer = class extends Engine.Drawable
{
	constructor()
	{
		super();
		this.panLayer = new Engine.Drawable();
		this.maskBox = new Engine.Drawable();
		this.panPosition = new Vector2(0,0);
		this.panBounds = new Vector2();
		this.onZoomChanged = new Action;
		this._enabled = false;
		this.rubberBanding = new Vector2();
		this.requestedMinZoom = 0.25;
		this.requestedMaxZoom = 1.5;
		this.xTween = new Engine.SimpleTween();
		this.yTween = new Engine.SimpleTween();
		this.zoom = 1;
		this.minZoom = 0.25;
		this.maxZoom = 1.5;
		this.actualMinZoom = 0.5;
		this.orthoZoomSpeed = 0.005;
		this.mouseBlockers = [];//new List<Drawable>();
		this.dragging = false;
		this.mouseDownPos = new Vector2(0,0);
		this.panPosOnMouseDown = new Vector2(0,0);
		this.startMouseX = 0;
		this.startMouseY = 0;
		this.containerWidth = 500;
		this.containerHeight = 500;
		this.contents = null;
		this.boundsInnerPadding = 30;
		this.moveTween = new Engine.SimpleTween();
		
		this.addChild(this.maskBox);
		this.addChild(this.panLayer);
		
		this.panLayer.mask = this.maskBox.graphics;
		
		this.enabled = true;
		this.scrollWheelEnabled = true;
	}

	set enabled(value)
	{
		if (this._enabled == value) return;
		
		this._enabled = value;
		if (this._enabled)
		{
			Engine.Mouse.addEventListener("mousedown", this, this.mouseDown);
			Engine.Mouse.addEventListener("mousewheel", this, this.mouseWheel);
		}
		else
		{
			Engine.Mouse.removeEventListener("mousedown", this);
			Engine.Mouse.removeEventListener("mousewheel", this);
		}
	}

	mouseMove(e /*Vector2*/)
	{
		if (this.dragging)
		{
			e = Engine.Mouse.position;
			var deltaX = this.mouseDownPos.x - e.x;
			var deltaY = this.mouseDownPos.y - e.y;
			
			this.panPosition.x = this.panPosOnMouseDown.x - deltaX;
			this.panPosition.y = this.panPosOnMouseDown.y - deltaY;
			
			if (this.panPosition.x < this.panBounds.x && deltaX > 0)
			{
				this.rubberBanding.x = this.rubberBandingFunc(this.panBounds.x - this.panPosition.x);
				this.panPosition.x = this.panBounds.x - this.rubberBanding.x;
			}
			
			if (this.panPosition.x > -this.boundsInnerPadding && deltaX < 0)
			{
				this.rubberBanding.x = this.rubberBandingFunc(this.panPosition.x - this.boundsInnerPadding);
				this.panPosition.x = -this.boundsInnerPadding + this.rubberBanding.x;
			}
			
			if (this.panPosition.y < this.panBounds.y)
			{
				this.rubberBanding.y = this.rubberBandingFunc(this.panBounds.y - this.panPosition.y);
				this.panPosition.y = this.panBounds.y - this.rubberBanding.y;
			}
			
			if (this.panPosition.y > -this.boundsInnerPadding)
			{
				this.rubberBanding.y = this.rubberBandingFunc(this.panPosition.y - this.boundsInnerPadding);
				this.panPosition.y = -this.boundsInnerPadding + this.rubberBanding.y;
			}
			
			this.panLayer.x = this.panPosition.x;
			this.panLayer.y = this.panPosition.y;
		}
	}

	setZoomLevels(min /*float*/, max /*int*/)
	{
		this.requestedMinZoom = min;
		this.requestedMaxZoom = max;
		this.capZoomLevels();
	}

	rubberBandingFunc(distance /*float*/)
	{
		if (distance <= 0) return 0;
		return (1 + Math.log10(distance / 15)) * 15 * this.zoom;
	}

	mouseUp(e /*Vector2*/)
	{
		Engine.Mouse.removeEventListener("mouseup", this);
		Engine.Mouse.removeEventListener("mousemove", this);
		
		this.dragging = false;
		
		this.panLayer.x = this.panPosition.x;
		this.panLayer.y = this.panPosition.y;
		
		if (this.panPosition.x < this.panBounds.x)
		{
			this.xTween.init((x) => this.panPosition.x = x, Engine.SimpleTween.quadEaseOut, this.panPosition.x, this.panBounds.x, 0.5);
		}
		else if (this.panPosition.x > -this.boundsInnerPadding)
		{
			this.xTween.init((x) => this.panPosition.x = x, Engine.SimpleTween.quadEaseOut, this.panPosition.x, -this.boundsInnerPadding, 0.5);
		}
		
		if (this.panPosition.y < this.panBounds.y)
		{
			this.yTween.init((x) => this.panPosition.y = x, Engine.SimpleTween.quadEaseOut, this.panPosition.y, this.panBounds.y, 0.5);
		}
		else if (this.panPosition.y > -this.boundsInnerPadding)
		{
			this.yTween.init((x) => this.panPosition.y = x, Engine.SimpleTween.quadEaseOut, this.panPosition.y, -this.boundsInnerPadding, 0.5);
		}
	}

	setZoom(zoom /*float*/)
	{
		if (this.zoom == zoom)
		{
			return;
		}
		
		this.zoom = zoom;
		if (this.zoom > maxZoom) this.zoom = maxZoom;
		if (this.zoom < minZoom) this.zoom = minZoom;
		
		if (this.onZoomChanged != null) this.onZoomChanged.call(this.zoom);
	}

	mouseWheel(velocity)
	{
		this.panLayer.x = this.panPosition.x;
		this.panLayer.y = this.panPosition.y;
		
		var startZoom = this.zoom;
		
		var mouseWheel = -velocity * 0.1;
		if (this.scrollWheelEnabled && mouseWheel != 0) // forward
		{
			var mouseLocation = new Vector2(Engine.Mouse.position.x, Engine.Mouse.position.y);
			mouseLocation.x -= this.screenX;
			mouseLocation.y -= this.screenY;
			var globalToLocal = new Vector2((1 / this.zoom) * mouseLocation.x + (-1) * this.panLayer.x * (1 / this.zoom), (1 / this.zoom) * mouseLocation.y + (-1) * this.panLayer.y * (1 / this.zoom));
			
			this.zoom += mouseWheel * 1;
			if (this.zoom > this.maxZoom) this.zoom = this.maxZoom;
			if (this.zoom < this.minZoom) this.zoom = this.minZoom;
			
			var pannedX = this.panPosition.x / this.panBounds.x;
			var pannedY = this.panPosition.y / this.panBounds.y;
			
			this.scaleToFit(false);
			
			var globalToLocalZoomed = new Vector2((1 / this.zoom) * mouseLocation.x + (-1) * this.panLayer.x * (1 / this.zoom),(1 / this.zoom) * mouseLocation.y + (-1) * this.panLayer.y * (1 / this.zoom));
			
			var scaledDiff = new Vector2((globalToLocalZoomed.x - globalToLocal.x) * this.zoom, (globalToLocalZoomed.y - globalToLocal.y) * this.zoom) ;
			
			this.panPosition.x += scaledDiff.x;
			this.panPosition.y += scaledDiff.y;
			
			if (this.panPosition.x < this.panBounds.x) this.panPosition.x = this.panBounds.x;
			if (this.panPosition.x > -this.boundsInnerPadding) this.panPosition.x = -this.boundsInnerPadding;
			
			if (this.panPosition.y < this.panBounds.y) this.panPosition.y = this.panBounds.y;
			if (this.panPosition.y > -this.boundsInnerPadding) this.panPosition.y = -this.boundsInnerPadding;
			this.panLayer.x = this.panPosition.x;
			this.panLayer.y = this.panPosition.y;
			
			this.boundsInnerPadding = 30 * this.zoom;
			
			this.yTween.stop();
			this.xTween.stop();
		}
		if (startZoom != this.zoom && this.onZoomChanged != null) this.onZoomChanged.call(this.zoom);
	}

	update(timeElapsed /*float*/)
	{
		
		this.panLayer.x = this.panPosition.x;
		this.panLayer.y = this.panPosition.y;
		
		var startZoom = this.zoom;
		
		// If there are two touches on the device...
		/*
		if (Input.touchCount == 2)
		{
			this.dragging = false;
			
			// Store both touches.
			var touchZero = Input.GetTouch(0);
			var touchOne = Input.GetTouch(1);
			
			// Find the position in the previous frame of each touch.
			var touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
			var touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
			
			// Find the magnitude of the vector (the distance) between the touches in each frame.
			var prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
			var touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
			
			// Find the difference in the distances between each frame.
			float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
			
			// ... change the orthographic size based on the change in distance between the touches.
			zoom += -deltaMagnitudeDiff * orthoZoomSpeed;
			if (zoom > maxZoom) zoom = maxZoom;
			if (zoom < minZoom) zoom = minZoom;
			
			var pannedX = panPosition.x / panBounds.x;
			var pannedY = panPosition.y / panBounds.y;
			
			this.scaleToFit(false);
			
			var * = zoom;
			var * = zoom;
			
			this.panPosition.x = panBounds.x * pannedX;
			this.panPosition.y = panBounds.y * pannedY;
			
			this.panLayer.x = panPosition.x;
			this.panLayer.y = panPosition.y;
		}*/
		
		if (startZoom != this.zoom && this.onZoomChanged != null) this.onZoomChanged.call(this.zoom);
		
	}

	mouseDown(v /*Vector2*/)
	{
		var v = Engine.Mouse.position;
		//Make sure the mouse isn't over something that is suppose to block mouse events into this container.
		for (var i = 0; i < this.mouseBlockers.length; i++)
		{
			if (this.mouseBlockers[i].screenRect.contains(v.x, v.y)) return;
		}
		
		if (this.maskBox.graphics.getBounds() != null && this.maskBox.graphics.getBounds().contains(v.x, v.y))
		{
			this.startMouseX = Engine.Mouse.position.x;
			this.startMouseY = Engine.Mouse.position.y;
			
			Engine.Mouse.addEventListener("mouseup", this, this.mouseUp);
			Engine.Mouse.addEventListener("mousemove", this, this.mouseMove);
			
			this.mouseDownPos.x = this.startMouseX;
			this.mouseDownPos.y = this.startMouseY;
			
			this.panPosOnMouseDown.x = this.panPosition.x;
			this.panPosOnMouseDown.y = this.panPosition.y;
			
			this.dragging = true;
			
			this.yTween.stop();
			this.xTween.stop();
		}
	}

	setSize(width /*int*/, height /*int*/)
	{
		this.containerWidth = width;
		this.containerHeight = height;
		
		this.maskBox.setAsBox(this.maskBox.graphics, width, height, Colors.red, 1);
		
		this.capZoomLevels();
		this.scaleToFit(false);
	}

	capZoomLevels()
	{
		if (this.contents != null)
		{
			//if the min zoom is too small, make sure the item fits inside the bounds.
			this.minZoom = Math.max(this.containerWidth / (this.contents.width - this.boundsInnerPadding * 2), this.containerHeight / (this.contents.height - this.boundsInnerPadding * 2));
			if (this.requestedMinZoom > this.minZoom) this.minZoom = this.requestedMinZoom;
			
			//if the max zoom is too small... make the max zoom the min allowable zoom.
			if (this.requestedMaxZoom < this.minZoom) this.maxZoom = this.minZoom;
			
			this.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.zoom));
		}
	}

	setContents(contents /*Drawable*/)
	{
		this.contents = contents;
		this.panLayer.addChild(contents);
		this.capZoomLevels();
		this.scaleToFit();
		
		if (this.onZoomChanged != null) this.onZoomChanged.call(this.zoom);
	}

	scaleToFit(center /*bool*/)
	{
		if (center === undefined) center = true;

		this.scale.x = 1;
		this.scale.y = 1;
		
		var scale = 1;
		
		this.panLayer.additionalMouseRect = new Rect(0, 0, this.panLayer.width, this.panLayer.height);
		
		scale = this.zoom;
		
		this.panLayer.scale.x = scale;
		this.panLayer.scale.y = scale;
		
		if(this.panLayer.width < EngineSettings.screenWidth || this.panLayer.height < EngineSettings.screenHeight)
		{
			var scaleX = EngineSettings.screenWidth / this.panLayer.width;
			var scaleY = EngineSettings.screenHeight / this.panLayer.height;
			var scaleDiff = Math.max(scaleX, scaleY);
			this.panLayer.scale.x *= scaleDiff;
			this.panLayer.scale.y *= scaleDiff;
		}
		
		var backgroundHeight = this.panLayer.height;
		var backgroundWidth = this.panLayer.width;
		
		this.panBounds.x = (this.containerWidth - backgroundWidth) + this.boundsInnerPadding;
		this.panBounds.y = (this.containerHeight - backgroundHeight) + this.boundsInnerPadding;
		
		if (this.xTween != null) this.xTween.stop();
		if (this.yTween != null) this.yTween.stop();
		
		if (center) this.centerCamera();
	}

	centerCameraOnPosition(x /*float*/, y /*float*/)
	{
		
		this.panPosition.x = -(x * this.zoom - this.containerWidth / 2);
		this.panPosition.y = -(y * this.zoom - this.containerHeight / 2);
		
		if (this.panPosition.x < this.panBounds.x)
		{
			this.panPosition.x = this.panBounds.x;
		}
		
		if (this.panPosition.x > -this.boundsInnerPadding)
		{
			this.panPosition.x = -this.boundsInnerPadding;
		}
		
		if (this.panPosition.y < this.panBounds.y)
		{
			this.panPosition.y = this.panBounds.y;
		}
		
		if (this.panPosition.y > -this.boundsInnerPadding)
		{
			this.panPosition.y = -this.boundsInnerPadding;
		}
		
		this.panLayer.x = this.panPosition.x;
		this.panLayer.y = this.panPosition.y;
	}

	centerCamera()
	{
		this.panPosition.x = this.panBounds.x / 2;
		this.panPosition.y = this.panBounds.y / 2;
		
		this.panLayer.x = this.panPosition.x;
		this.panLayer.y = this.panPosition.y;
	}

	// new center is previous center + vector
	moveByDistance(v /*Vector2*/)
	{
		var center = new Vector2((-this.panPosition.x + this.containerWidth / 2) / this.zoom,(-this.panPosition.y + this.containerHeight / 2) / this.zoom);
		this.centerCameraOnPosition(center.x + v.x/this.zoom, center.y + v.y/this.zoom);
	}

	tweenByDistance(v /*Vector2*/)
	{
		var center = new Vector2((-this.panPosition.x + this.containerWidth / 2) / this.zoom,(-this.panPosition.y + this.containerHeight / 2) / this.zoom);
		this.moveTween.init((p) => this.centerCameraOnPosition(center.x + this.p * v.x / this.zoom, center.y + this.p * v.y / this.zoom), Engine.SimpleTween.easeLinear, 0, 1, 0.25);
	}



}