class Controls {
    constructor(object) {

        // object is the object controlled by the controls, in our case, the camera.
        this.object = object;

        // 3D coordinates where the camera is looking at
        this.target = new THREE.Vector3(0, 0, 0);

        // Unit vectors of the local camera frame
        // forward is in the z-direction
        // left point towards the x-direction
        // (a vector towards y is not necessary)
        // in the direction of the angles
        this.forward = new THREE.Vector3(0,0,0);
        this.left = new THREE.Vector3(0,0,0);

        // speed at which the camera will translate
        this.speed = 50;

        // speed at which the camera will rotate
        this.sensitivity = 0.004;

        // angles of the camera
        this.angles = {
            delta: 0,
            theta: 0,
        };

        // the position of the camera is this.object.position

        this.vectorsFromAngles();

        // currently pressed keys
        this.keys = {
            up: false,
            down: false,
            left: false,
            right: false,
        };

        // whether the mouse is being pressed or not
        this.mouseClicked = false;

        // whether there is a transition running or not
        //   if not, this.t is NaN
        //   otherwise, this.t is in [0, 1]
        this.t = NaN;

        // calls this.keyChange(true) when a key is pressed
        document.addEventListener('keydown', this.keyChange(true));

        // calls this.keyChange(false) when a key is released
        document.addEventListener('keyup', this.keyChange(false));

        // calls this.mouseDown(e) when a mouse button is pressed
        document.addEventListener('mousedown', (e) => this.mouseDown(e));

        // sets this.mouseClicked to false when a mouse button is released
        document.addEventListener('mouseup', () => this.mouseClicked = false);

        // calls this.mouseMove(e) when the mouse is moved
        document.addEventListener('mousemove', (e) => this.mouseMove(e));
    }

    // rotates the camera to make it look to its target.
    look() {
        this.object.lookAt(this.target);
    }

    // updates this.forward, this.left and this.target
    // from this.angles.delta and this.angles.theta

    //  this.forward is the vector moving direction
    //  this.left is the vector moving sideways to the left
    //  this.target is the point that the camera is looking
    vectorsFromAngles() {
        // TODO Part 1
    }


    // computes this.angles.delta and this.angles.theta from this.forward
    // (may be useless)
    anglesFromVectors() {
        // TODO Part 2
    }

    // updates this.keys when a key is pressed or released
    keyChange(setting) {
        return (event) => {
            switch (event.keyCode) {
                case 37: case 81: this.keys.left = setting;          break;
                case 38: case 90: this.keys.up = setting;            break;
                case 39: case 68: this.keys.right = setting;         break;
                case 40: case 83:  this.keys.down = setting;          break;
                case 13:
                    console.log("Position:", this.object.position);
                    console.log("Target:", this.target);
                    break;
            }
        };
    }

    // sets mouseClicked to true, and searches for a bookmark being clicked
    mouseDown(event) {
        this.mouseClicked = true;

        let mouse = {
            x: ( event.clientX / window.innerWidth ) * 2 - 1,
            y: - ( event.clientY / window.innerHeight ) * 2 + 1,
        };

        // update the picking ray with the camera and mouse position
        raycaster.setFromCamera(mouse, camera);

        // calculate objects intersecting the picking ray
        var intersects = raycaster.intersectObjects(scene.children, true);

        if (this.testIntersection(intersects)) {
            this.bookmarkClicked(intersects[0].object.parent);
        }

    }

    // tests that a bookmark is clicked or not
    //
    // this function filters out other objects of the scene.
    testIntersection(intersects) {
        if (intersects.length === 0) {
            return;
        }

        if (intersects[0].object === undefined) {
            return;
        }

        if (! (intersects[0].object.parent instanceof Bookmark)) {
            return;
        }

        if (this.object.position.distanceTo(intersects[0].object.parent.position) < 10) {
            return;
        }

        this.bookmarkClicked(intersects[0].object.parent);
    }

    // initializes the paramters for the transition to a bookmark.
    bookmarkClicked(bookmark) {
        // TODO Part 2
    }

    // changes this.angles depending of event.movementX and event.movementY
    //
    // event.movementX indicates how many pixels the mouse has moved to the right
    // event.movementY indicates how many piels the mouse has moved to the bottom
    // both those values can be negative to indicate a motion in the opposite direction
    mouseMove(event) {
        // TODO Part 1
    }

    // called at every frame
    update() {

        // No transition : use keyboard and mouse to update the camera
        if (isNaN(this.t)) {

            // TODO Part 1

            if (this.keys.up) {
                // ...
            }

        } else {
            // Transition to bookmark
            // TODO Part 2

        }

        // update the camera parameters
        this.look();
    }
}

let container, controls;
let camera, scene, renderer;
let raycaster = new THREE.Raycaster();

init();
animate();

function init() {

    container = document.createElement('div');
    document.body.appendChild(container);
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 100, 50000);
    camera.position.y = 1000;
    camera.position.z = 1000;

    scene = new THREE.Scene();
    let ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
    scene.add(ambientLight);
    let pointLight = new THREE.PointLight(0xffffff, 0.8);
    camera.add(pointLight);
    scene.add(camera);

    let geometry = new THREE.PlaneGeometry(20000, 20000);
    for (let index = 0; index < geometry.faceVertexUvs[0].length; index++) {
        geometry.faceVertexUvs[0][index][0].multiplyScalar(10);
        geometry.faceVertexUvs[0][index][1].multiplyScalar(10);
        geometry.faceVertexUvs[0][index][2].multiplyScalar(10);
    }
    let texture = new THREE.TextureLoader().load('assets/windfall/water.png');
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    let material = new THREE.MeshBasicMaterial({map: texture});
    let sea = new THREE.Mesh(geometry, material);
    sea.rotation.x = -Math.PI / 2;
    scene.add(sea);

    // controls
    controls = new Controls(camera);

    // model
    let directory = "assets/windfall/";
    let modelName = "Windfall";

    let onProgress = function (xhr) {
        if (xhr.lengthComputable) {
            let percentComplete = xhr.loaded / xhr.total * 100;
            console.log(Math.round(percentComplete, 2) + '% downloaded');
        }
    };

    let onError = function () { };
    let manager = new THREE.LoadingManager();

    new THREE.MTLLoader(manager)
        .setPath(directory)
        .load(modelName + ".mtl", function (materials) {
            materials.preload();
            new THREE.OBJLoader(manager)
                .setMaterials(materials)
                .setPath(directory)
                .load(modelName + ".obj", function (object) {
                    scene.add(object);
                }, onProgress, onError);
        });

    renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColor(new THREE.Color(0x87ceeb));
    container.appendChild(renderer.domElement);

    window.addEventListener('resize', onWindowResize, false);

    // Add a bookmark to the scene
    // let position = { x: -3089.6612461510463, y: 3944.8226721499614, z: -2409.0350261969947 };
    // let target = { x: -3088.665295409939, y: 3944.824672148628, z: -2409.124904566436 };
    // let bookmark = new Bookmark(position, target);
    // scene.add(bookmark);
}

function animate() {
    requestAnimationFrame(animate);
    controls.update();

    for (let child of scene.children) {
        if (child instanceof Bookmark) {
            child.update(camera);
        }
    }

    renderer.render(scene, camera);
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

