import { Euler, Matrix4, PerspectiveCamera, Plane, Quaternion, Vector3, Vector4 } from "three";

const SURFACE_UP = new Vector3(0, 1, 0);
const CAMERA_FORWARD = new Vector3(0, 0, -1);

function createRotationQuaternion(rotationDegrees) {
    return new Quaternion().setFromEuler(new Euler((rotationDegrees.x * Math.PI) / 180, (rotationDegrees.y * Math.PI) / 180, (rotationDegrees.z * Math.PI) / 180, "XYZ"));
}

export function updatePlanarReflectionCamera(surface, sourceCamera, reflectionCamera, reflectionMatrix, clipBias = 0.003) {
    const rotation = createRotationQuaternion(surface.rotationDegrees);
    const surfaceNormal = SURFACE_UP.clone().applyQuaternion(rotation).normalize();
    const surfaceCenter = new Vector3(surface.center.x, surface.center.y, surface.center.z).add(surfaceNormal.clone().multiplyScalar(surface.size.y * 0.5));
    const cameraWorldPosition = new Vector3().setFromMatrixPosition(sourceCamera.matrixWorld);
    const sourceRotationMatrix = new Matrix4().extractRotation(sourceCamera.matrixWorld);
    const lookAtPosition = CAMERA_FORWARD.clone().applyMatrix4(sourceRotationMatrix).add(cameraWorldPosition);
    const reflectedViewPosition = surfaceCenter.clone().sub(cameraWorldPosition);
    if (reflectedViewPosition.dot(surfaceNormal) > 0) {
        return false;
    }
    reflectedViewPosition.reflect(surfaceNormal).negate();
    reflectedViewPosition.add(surfaceCenter);
    const reflectedTarget = surfaceCenter.clone().sub(lookAtPosition);
    reflectedTarget.reflect(surfaceNormal).negate();
    reflectedTarget.add(surfaceCenter);
    reflectionCamera.position.copy(reflectedViewPosition);
    reflectionCamera.up.set(0, 1, 0).applyMatrix4(sourceRotationMatrix).reflect(surfaceNormal);
    reflectionCamera.near = sourceCamera.near;
    reflectionCamera.far = sourceCamera.far;
    reflectionCamera.aspect = sourceCamera.aspect;
    reflectionCamera.projectionMatrix.copy(sourceCamera.projectionMatrix);
    reflectionCamera.projectionMatrixInverse.copy(sourceCamera.projectionMatrixInverse);
    reflectionCamera.lookAt(reflectedTarget);
    reflectionCamera.updateMatrixWorld();
    reflectionCamera.matrixWorldInverse.copy(reflectionCamera.matrixWorld).invert();
    reflectionMatrix.set(0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1);
    reflectionMatrix.multiply(reflectionCamera.projectionMatrix);
    reflectionMatrix.multiply(reflectionCamera.matrixWorldInverse);
    const clipPlane = new Plane().setFromNormalAndCoplanarPoint(surfaceNormal, surfaceCenter).applyMatrix4(reflectionCamera.matrixWorldInverse);
    const clipVector = new Vector4(clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.constant);
    const projectionElements = reflectionCamera.projectionMatrix.elements;
    const q = new Vector4((Math.sign(clipVector.x) + projectionElements[8]) / projectionElements[0], (Math.sign(clipVector.y) + projectionElements[9]) / projectionElements[5], -1, (1 + projectionElements[10]) / projectionElements[14]);
    clipVector.multiplyScalar(2 / clipVector.dot(q));
    projectionElements[2] = clipVector.x;
    projectionElements[6] = clipVector.y;
    projectionElements[10] = clipVector.z + 1 - clipBias;
    projectionElements[14] = clipVector.w;
    reflectionCamera.projectionMatrixInverse.copy(reflectionCamera.projectionMatrix).invert();
    return true;
}