package speiger.src.coreengine.rendering.inputOld.camera;

import java.text.DecimalFormat;
import java.util.List;
import java.util.function.Consumer;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.math.MathUtils;
import speiger.src.coreengine.math.vector.floats.Vec2f;
import speiger.src.coreengine.math.vector.floats.Vec3f;
import speiger.src.coreengine.math.vector.ints.Vec2i;
import speiger.src.coreengine.math.vector.ints.Vec3i;
import speiger.src.coreengine.math.vector.matrix.Matrix4f;
import speiger.src.coreengine.rendering.inputOld.window.Window;

public class Camera
{
	
	Vec3f position = Vec3f.mutable();
	Vec3f lastPosition = Vec3f.mutable();
	Vec2f rotation = Vec2f.mutable();
	Vec2f lastRotation = Vec2f.mutable();
	boolean moved = false;
	boolean rotated = false;
	
	Matrix4f viewMatrix = createViewMatrix();
	Matrix4f invertedViewMatrix = new Matrix4f(viewMatrix).invert();
	
	ICameraController controller;
	Frustrum frustrum;
	boolean changed = false;
	List<Consumer<Camera>> listeners = new ObjectArrayList<>();
	
	public Camera(Window window)
	{
		frustrum = new Frustrum(this);
		window.addListener(frustrum, false);
		setController(controller);
	}
	
	public void setController(ICameraController cont)
	{
		cont = cont == null ? DefaultCameraController.INSTANCE : cont;
		if(controller != null)
		{
			controller.onRemoved(this);
		}
		controller = cont;
		cont.onAdded(this);
	}
	
	public void addListener(Consumer<Camera> listner)
	{
		listeners.add(listner);
	}
	
	public void onInput()
	{
		controller.onInputUpdate(this);
	}
	
	public void update(float particalTicks)
	{
		controller.updateCamera(this, particalTicks);
		moved = !lastPosition.equals(position);
		rotated = !rotation.equals(lastRotation);
		if(rotated || moved)
		{
			viewMatrix = createViewMatrix();
			invertedViewMatrix.load(viewMatrix).invert();
			lastPosition.set(position);
			lastRotation.set(rotation);
			frustrum.update();
			changed = true;
			for(Consumer<Camera> listener : listeners)
			{
				listener.accept(this);
			}
		}
		else
		{
			changed = false;
		}
	}
	
	public Matrix4f createViewMatrix()
	{
		Matrix4f viewMatrix = new Matrix4f();
		viewMatrix.rotateX(rotation.x());
		viewMatrix.rotateY(rotation.y());
		viewMatrix.translate(-position.x(), -position.y(), -position.z());
		return viewMatrix;
	}
	
	public boolean isChanged()
	{
		return changed;
	}
	
	public boolean wasMoved()
	{
		return moved;
	}
	
	public boolean wasRotated()
	{
		return rotated;
	}
	
	public Matrix4f getViewMatrix()
	{
		return viewMatrix;
	}
	
	public Matrix4f getInvertedViewMatrix()
	{
		return invertedViewMatrix;
	}
	
	public Frustrum getFrustrum()
	{
		return frustrum;
	}
	
	public ICameraController getController()
	{
		return controller;
	}
	
	@SuppressWarnings("unchecked")
	public <T extends ICameraController> T getController(Class<T> clz)
	{
		return (T)controller;
	}
	
	public Vec3f getPosition()
	{
		return position;
	}
	
	public Vec3f getLookDirection()
	{
        float f = MathUtils.cos(-rotation.y() * 0.017453292F - (float)Math.PI);
        float f1 = MathUtils.sin(-rotation.y() * 0.017453292F - (float)Math.PI);
        float f2 = -MathUtils.cos(-rotation.x() * 0.017453292F);
        float f3 = MathUtils.sin(-rotation.x() * 0.017453292F);
        return Vec3f.of(f1 * f2, f3, f * f2);
	}
	
	public Camera setCameraChanged()
	{
		changed = true;
		return this;
	}
	
	public void setPosition(Vec3f vec, boolean update)
	{
		position.set(vec);
		if(update)
		{
			controller.onPositionUpdate(this);
		}
	}
	
	public Vec2i getActualCenter(){return controller.getCenter(this);}
	public Vec2i getSolidPosition(){return Vec2i.of((int)position.x(), (int)position.z());}
	
	public void rotate(Vec2i vec){rotate(vec.y(), vec.x());}
	public void rotate(Vec2f vec){rotate(vec.y(), vec.x());}
	public void rotate(float x, float y){rotation.add(x, y);}
	
	public void move(Vec3f vec){move(vec.x(), vec.y(), vec.z());}
	public void move(Vec3i vec){move(vec.x(), vec.y(), vec.z());}
	public void move(float x, float y, float z){position.add(x, y, z);}
	
	public void moveRotated(float x, float y, float z)
	{
        if(z != 0) 
        {
            position.add(MathUtils.sin(Math.toRadians(getYaw())) * -1.0f * z, 0, MathUtils.cos(Math.toRadians(getYaw())) * z);
        }
        if(x != 0) 
        {
            position.add(MathUtils.sin(Math.toRadians(getYaw() - 90)) * -1.0f * x, 0, MathUtils.cos(Math.toRadians(getYaw() - 90)) * x);
        }
        if(y != 0)
        {
            position.add(0F, y, 0F);
        }
	}
	
	public Vec2f getRotation(){return rotation;}
	public float getYaw(){return rotation.y();}
	public void setYaw(float value){rotation.y(value);}
	
	public float pitch(){return rotation.x();}
	public void setPitch(float value){rotation.x(value);}
	
	@Override
	public String toString()
	{
		DecimalFormat format = new DecimalFormat("###,###");
		return "Camera[x="+format.format(position.x())+", y="+format.format(position.y())+", z="+format.format(position.z())+", Yaw="+format.format(getYaw())+", Pitch="+format.format(pitch())+"]";
	}
}
