package nl.andrewl.aos2_client;

import nl.andrewl.aos_core.MathUtils;
import nl.andrewl.aos_core.model.Player;
import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f;

/**
 * Represents the player camera in the game world.
 */
public class Camera {
	public static final Vector3f UP = new Vector3f(0, 1, 0);
	public static final Vector3f DOWN = new Vector3f(0, -1, 0);
	public static final Vector3f RIGHT = new Vector3f(1, 0, 0);
	public static final Vector3f LEFT = new Vector3f(-1, 0, 0);
	public static final Vector3f FORWARD = new Vector3f(0, 0, -1);
	public static final Vector3f BACKWARD = new Vector3f(0, 0, 1);

	/**
	 * The x, y, and z position of the camera in the world.
	 */
	private final Vector3f position;

	private final Vector3f velocity;

	/**
	 * The camera's angular orientation. X refers to the rotation about the
	 * vertical axis, while Y refers to the rotation about the horizontal axis.
	 * <p>
	 *     The Y axis orientation is limited to between 0 and PI, with 0
	 *     being looking straight down, and PI looking straight up.
	 * </p>
	 * <p>
	 *     The X axis orientation is limited to between 0 and 2 PI, with 0
	 *     being looking at the - Z axis.
	 * </p>
	 */
	private final Vector2f orientation;
	private final Matrix4f viewTransform;
	private final float[] viewTransformData = new float[16];

	public Camera() {
		this.position = new Vector3f();
		this.velocity = new Vector3f();
		this.orientation = new Vector2f(0, (float) (Math.PI / 2));
		this.viewTransform = new Matrix4f();
	}

	public void setToPlayer(Player p) {
		position.set(p.getEyePosition());
		velocity.set(p.getVelocity());
	}

	public void setToPlayerScopeView(Player p) {
		Vector3f pos = new Vector3f();
		Matrix4f tx = p.getHeldItemTransform();
		tx.transformPosition(pos);
		position.set(pos);
		velocity.set(p.getVelocity());
	}

	public void setOrientationToPlayer(Player p) {
		orientation.set(p.getOrientation());
	}

	public Matrix4f getViewTransform() {
		return viewTransform;
	}

	public float[] getViewTransformData() {
		return viewTransformData;
	}

	public Vector2f getOrientation() {
		return orientation;
	}

	public Vector3f getPosition() {
		return position;
	}

	public Vector3f getVelocity() {
		return velocity;
	}

	public void setPosition(float x, float y, float z) {
		if (position.x != x || position.y != y || position.z != z) {
			position.set(x, y, z);
			updateViewTransform();
		}
	}

	public void setVelocity(float x, float y, float z) {
		velocity.set(x, y, z);
	}

	public void setOrientation(float x, float y) {
		orientation.set(
				MathUtils.normalize(x, 0, Math.PI * 2),
				MathUtils.clamp(y, 0, (float) (Math.PI))
		);
		updateViewTransform();
	}

	public void setOrientationDegrees(float x, float y) {
		setOrientation((float) Math.toRadians(x), (float) Math.toRadians(y));
	}

	public void interpolatePosition(float dt) {
		Vector3f movement = new Vector3f(velocity).mul(dt);
		position.add(movement);
		updateViewTransform();
	}

	public void updateViewTransform() {
		viewTransform.identity();
		viewTransform.rotate(-orientation.y + ((float) Math.PI / 2), RIGHT);
		viewTransform.rotate(-orientation.x, UP);
		viewTransform.translate(-position.x, -position.y, -position.z);
		viewTransform.get(viewTransformData);
	}

	public Vector3f getViewVector() {
		float y = (float) (orientation.y + Math.PI / 2);
		return new Vector3f(
				(float) (Math.sin(orientation.x) * Math.cos(y)),
				(float) -Math.sin(y),
				(float) (Math.cos(orientation.x) * Math.cos(y))
		).normalize();
	}

	public void move(Vector3f relativeMotion) {
		Vector3f actualMotion = new Vector3f(relativeMotion).mul(0.1f);
		Matrix4f moveTransform = new Matrix4f();
		moveTransform.rotate(orientation.x, UP);
		moveTransform.transformDirection(actualMotion);
		position.add(actualMotion);
		updateViewTransform();
//		System.out.printf("Position: x=%.2f, y=%.2f, z=%.2f%n", position.x, position.y, position.z);
	}
}
