/*******************************************************************************
 * Copyright 2015 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.mygdx.game.utilities;

import com.badlogic.gdx.math.*;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.math.collision.Ray;
import com.mygdx.game.settings.GameSettings;

/**
 * @author jsjolund
 */
public class CameraController {

	private final GhostCamera camera;

	// World data
	private final Plane worldGroundPlane = new Plane(Vector3.Y, 0);
	private final Vector3 worldDragCurrent = new Vector3();
	private final Vector3 worldDragLast = new Vector3();
	private final Vector3 worldGroundTarget = new Vector3();
	// Temporary
	private final Ray ray = new Ray();
	private final Vector3 tmp1 = new Vector3();
	private final Vector3 tmp2 = new Vector3();
	private final Quaternion deltaRotation = new Quaternion();
	private float zoom = GameSettings.CAMERA_MAX_ZOOM;
	private BoundingBox worldBoundingBox;
	
	private Matrix4 followTarget;

	public CameraController(GhostCamera camera) {
		this.camera = camera;
	}

	public void setWorldBoundingBox(BoundingBox worldBoundingBox) {
		this.worldBoundingBox = new BoundingBox(worldBoundingBox);
		Vector3 min = new Vector3();
		Vector3 max = new Vector3();
		// Set height of bounding box to zero (y dimension)
		this.worldBoundingBox.getMax(max).y = 0;
		this.worldBoundingBox.getMin(min).y = 0;
		this.worldBoundingBox.set(min, max);

		ray.set(camera.targetPosition, camera.targetDirection);
		if (!Intersector.intersectRayBounds(ray, this.worldBoundingBox, worldGroundTarget)) {
			// TODO: What happens if the center of camera is not aimed at bounding box?
			// Probably move the camera until it is...
		}

	}
	
	public void setFollowTarget(Matrix4 followTarget) {
		this.followTarget = followTarget;
	}

	public void processDragPan(Ray dragCurrentRay, Ray lastDragProcessedRay) {
		followTarget = null;
		// TODO:
		// Can probably be optimized, but simply storing worldDragLast.set(worldDragCurrent)
		// caused jitter for some reason.
		Intersector.intersectRayPlane(dragCurrentRay, worldGroundPlane, worldDragCurrent);
		Intersector.intersectRayPlane(lastDragProcessedRay, worldGroundPlane, worldDragLast);
		tmp1.set(worldDragLast).sub(worldDragCurrent);
		tmp1.y = 0;

		ray.origin.set(camera.targetPosition).add(tmp1);
		ray.direction.set(camera.targetDirection);
		if (Intersector.intersectRayBoundsFast(ray, worldBoundingBox)) {
			camera.targetPosition.add(tmp1);
			worldGroundTarget.add(tmp1);
		}
	}

	public void processDragRotation(Vector2 cursorDelta) {
		tmp1.set(camera.targetDirection).crs(camera.targetUp).nor();
		deltaRotation.setEulerAngles(cursorDelta.x, cursorDelta.y * tmp1.x, cursorDelta.y * tmp1.z);
		camera.rotateAround(worldGroundTarget, deltaRotation);
	}

	public void processZoom(float amount) {
		zoom += GameSettings.CAMERA_ZOOM_STEP * amount;
		zoom = MathUtils.clamp(zoom, GameSettings.CAMERA_MIN_ZOOM, GameSettings.CAMERA_MAX_ZOOM);
		camera.targetPosition.set(camera.targetDirection).nor().scl(-zoom).add(worldGroundTarget);
	}

	public void processKeyboardPan(Vector2 keysMoveDirection, float deltaTime) {
		followTarget = null;
		tmp1.set(camera.targetDirection).crs(camera.targetUp).scl(keysMoveDirection.x);
		tmp1.add(tmp2.set(camera.targetDirection).scl(keysMoveDirection.y));
		tmp1.y = 0;
		tmp1.nor().scl(deltaTime * GameSettings.CAMERA_MAX_PAN_VELOCITY);

		ray.origin.set(camera.targetPosition).add(tmp1);
		ray.direction.set(camera.targetDirection);
		if (Intersector.intersectRayBoundsFast(ray, worldBoundingBox)) {
			camera.targetPosition.add(tmp1);
			worldGroundTarget.add(tmp1);
		}
	}

	public void processTouchDownLeft(Ray ray) {
		Intersector.intersectRayPlane(ray, worldGroundPlane, worldDragCurrent);
		worldDragLast.set(worldDragCurrent);
	}

	public void processTouchDownRight() {
//		ray.set(camera.position, camera.direction);
//		Intersector.intersectRayPlane(ray, worldGroundPlane, worldGroundTarget);
	}

	public void update() {
		if (followTarget != null) {
			camera.targetPosition.add(tmp1.set(followTarget.getTranslation(tmp2)).sub(worldGroundTarget));
			worldGroundTarget.set(tmp2);
		}
	}

}