package electrosphere.controls;

import org.joml.Quaternionf;
import org.joml.Vector3d;
import org.joml.Vector3f;

import electrosphere.client.targeting.crosshair.Crosshair;
import electrosphere.collision.CollisionEngine;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.camera.CameraEntityUtils;
import electrosphere.net.parser.net.message.EntityMessage;
import electrosphere.renderer.ui.events.MouseEvent;
import electrosphere.util.math.MathUtils;

/**
 * Handler for camera-related events and controls
 */
public class CameraHandler {

    //The first person camera perspective
    public static final int CAMERA_PERSPECTIVE_FIRST = 1;
    //The third person camera perspective
    public static final int CAMERA_PERSPECTIVE_THIRD = 3;
    
    //the horizontal mouse sensitivity
    float mouseSensitivityHorizontal = .1f;
    //the vertical mouse sensitivity
    float mouseSensitivityVertical = .08f;
    //the speed of the freecam
    float cameraSpeed;
    //the current yaw
    float yaw = 150;
    //the current pitch
    float pitch = 50;
    //the camera's rotation vector
    Vector3f cameraRotationVector = new Vector3f();
    //the radial offset of the camera
    Vector3f radialOffset = new Vector3f(0,1,0);
    //if set to true, the camera will track the player's entity
    boolean trackPlayerEntity = true;

    /**
     * Handles a mouse event
     * @param event The mouse event
     */
    public void handleMouseEvent(MouseEvent event){

        if(Globals.controlHandler != null && !Globals.controlHandler.isMouseVisible()){
            yaw = yaw + event.getDeltaX() * mouseSensitivityHorizontal;
            pitch = pitch + event.getDeltaY() * mouseSensitivityVertical;

            if (pitch >= 89.9f) {
                pitch = 89.9f;
            }
            if (pitch <= -89.9f) {
                pitch = -89.9f;
            }
        }

        updateGlobalCamera();
    }

    /**
     * Updates the radial offset
     * @param offset the radial offset
     */
    public void updateRadialOffset(Vector3f offset){
        radialOffset = offset;
    }

    /**
     * Updates the global camera
     */
    public void updateGlobalCamera(){
        Globals.profiler.beginCpuSample("updateGlobalCamera");
        if(Globals.playerCamera != null){
            cameraSpeed = 2.5f * (float)Globals.timekeeper.getMostRecentRawFrametime();

            if(Crosshair.getCrosshairActive()){
                
                // if(Globals.playerCharacter != null){
                //     Vector3d charPos = EntityUtils.getPosition(Globals.playerCharacter);
                //     CameraEntityUtils.setCameraCenter(Globals.playerCamera, new Vector3f((float)charPos.x,(float)charPos.y,(float)charPos.z));
                // }
                
                Vector3d characterPos = EntityUtils.getPosition(Globals.playerEntity);
                Vector3d targetPos = Crosshair.getTargetPosition();
                Vector3d diffed = new Vector3d(targetPos).sub(characterPos).mul(-1).normalize();
                cameraRotationVector.set((float)diffed.x, 0.5f, (float)diffed.z).normalize();
                
                yaw = (float)Math.toDegrees(Math.atan2(diffed.z, diffed.x));
                
                CameraEntityUtils.setCameraPitch(Globals.playerCamera, pitch);
                CameraEntityUtils.setCameraYaw(Globals.playerCamera, yaw);
                
            } else {
                CameraEntityUtils.setCameraPitch(Globals.playerCamera, pitch);
                CameraEntityUtils.setCameraYaw(Globals.playerCamera, yaw);

                // System.out.println(pitch);
                // if(Globals.playerCharacter != null){
                //     Vector3d charPos = EntityUtils.getPosition(Globals.playerCharacter);
                //     CameraEntityUtils.setCameraCenter(Globals.playerCamera, new Vector3f((float)charPos.x,(float)charPos.y,(float)charPos.z));
                // }

                Quaternionf pitchQuat = new Quaternionf().fromAxisAngleDeg(MathUtils.getLeftVectorf(), -pitch);
                Quaternionf yawQuat = new Quaternionf().fromAxisAngleDeg(MathUtils.getUpVectorf(), -yaw);
                // float yawRad = yaw / 180.0f * (float)Math.PI;
                // float pitchRad = pitch / 180.0f * (float)Math.PI;
                // float rollRad = 0.0f;
                // pitchQuat.mul(yawQuat);
                cameraRotationVector = pitchQuat.transform(MathUtils.getOriginVectorf());
                cameraRotationVector = yawQuat.transform(cameraRotationVector);
                cameraRotationVector.normalize();


                // cameraRotationVector.x = 0 + (float) Math.cos(yaw / 180.0f * Math.PI) * 1;
                // cameraRotationVector.y = 0 + (float) Math.sin(pitch / 180.0f * Math.PI) * 1;
                // cameraRotationVector.z = 0 + (float) Math.sin(yaw / 180.0f * Math.PI) * 1;
                // cameraRotationVector.normalize();
                // System.out.println(yaw + " " + pitch);
            }
            if(trackPlayerEntity && Globals.playerEntity != null){
                Vector3d entityPos = EntityUtils.getPosition(Globals.playerEntity);
                CameraEntityUtils.setCameraCenter(Globals.playerCamera, new Vector3f((float)entityPos.x,(float)entityPos.y,(float)entityPos.z).add(CameraEntityUtils.getOrbitalCameraRadialOffset(Globals.playerCamera)));
            }
            //update view matrix offset
            float xFactor = (float)Math.cos(yaw / 180.0f * Math.PI);
            float yFactor = (float)Math.sin(yaw / 180.0f * Math.PI);
            Vector3f radialOffset = CameraEntityUtils.getOrbitalCameraRadialOffset(Globals.playerCamera);
            Vector3f trueOffset = new Vector3f(radialOffset).mul(xFactor,1.0f,yFactor);
            CameraEntityUtils.setOrbitalCameraRadialOffset(Globals.playerCamera, trueOffset);
            cameraRotationVector.mul(CameraEntityUtils.getOrbitalCameraDistance(Globals.playerCamera));
            CameraEntityUtils.setCameraEye(Globals.playerCamera, cameraRotationVector);

            //tell the server that we changed where we're looking, if we're in first person
            int perspectiveVal = CameraHandler.CAMERA_PERSPECTIVE_FIRST;
            if(Globals.controlHandler.cameraIsThirdPerson()){
                perspectiveVal = CameraHandler.CAMERA_PERSPECTIVE_THIRD;
            }
            if(Globals.cameraHandler.getTrackPlayerEntity()){
                Globals.clientConnection.queueOutgoingMessage(
                    EntityMessage.constructupdateEntityViewDirMessage(
                        Globals.clientSceneWrapper.mapClientToServerId(Globals.playerEntity.getId()),
                        Globals.timekeeper.getNumberOfSimFramesElapsed(),
                        perspectiveVal,
                        yaw,
                        pitch
                    )
                );
            }

            //the view matrix
            Globals.viewMatrix = CameraEntityUtils.getCameraViewMatrix(Globals.playerCamera);

            //update the cursor on client side
            updatePlayerCursor();
        }
        Globals.profiler.endCpuSample();
    }

    /**
     * Updates the position of the player's in world cursor
     */
    private void updatePlayerCursor(){
        CollisionEngine collisionEngine = Globals.clientSceneWrapper.getCollisionEngine();
        Entity camera = Globals.playerCamera;
        if(
            collisionEngine != null &&
            camera != null &&
            Globals.playerCursor != null
        ){
            Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(camera));
            Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(camera));
            Vector3d cursorPos = collisionEngine.rayCastPosition(centerPos, new Vector3d(eyePos).mul(-1.0), 5.0);
            if(cursorPos != null){
                EntityUtils.getPosition(Globals.playerCursor).set(cursorPos);
            } else {
                EntityUtils.getPosition(Globals.playerCursor).set(new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-5.0)));
            }
        }
    }

    /**
     * Gets the yaw of the camera handler
     * @return the yaw
     */
    public float getYaw(){
        return yaw;
    }

    /**
     * Gets the pitch of the camera handler
     * @return the pitch
     */
    public float getPitch(){
        return pitch;
    }

    //set player tracking
    public void setTrackPlayerEntity(boolean track){
        trackPlayerEntity = track;
    }

    //get trackPlayerEntity
    public boolean getTrackPlayerEntity(){
        return trackPlayerEntity;
    }
    



}
