/*
 * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*global window, THREE*/

// create "app" namespace if not exists.
window.app = window.app || {
    pages: {}
};

/**
 * "Camera move" WebGL example page.
 * Creates data object describing page.
 *
 * @module camera
 * @requires lib/threejs
 * @requires {@link app}
 * @namespace app.pages.camera
 * @memberof app
 */

(function cameraPage(app) {

    'use strict';

     /**
     * Floor edge size.
     *
     * @memberof camera
     * @private
     * @const {number}
     */
    var FLOOR_SIZE = 3000,

        /**
         * Radius of the camera rotation around Y axis.
         *
         * @memberof camera
         * @private
         * @const {number}
         */
        RADIUS = 300,

        /**
         * Velocity of camera movement.
         *
         * @memberof camera
         * @private
         * @const {number}
         */
        VELOCITY = 10,

        /**
         * The probability that camera will make a random turn.
         *
         * @memberof camera
         * @private
         * @const {number}
         */
        TURN_PROBABILITY = 0.25,

        /**
         * Max angular velocity of the camera.
         *
         * @memberof camera
         * @private
         * @const {number}
         */
        MAX_ANGULAR_VELOCITY = Math.PI / 120,

        /**
         * Angular velocity fraction of the camera.
         *
         * @memberof camera
         * @private
         * @const {number}
         */
        ANGULAR_VELOCITY_FRACTION = 0.008,

        /**
         * Reference to the scene.
         *
         * @memberof camera
         * @public
         * @type {THREE.Scene}
         */
        scene = null,

        /**
         * Reference to the camera.
         *
         * @memberof camera
         * @public
         * @type {THREE.PerspectiveCamera}
         */
        camera = null,

        /**
         * Reference to the floor.
         *
         * @memberof camera
         * @private
         * @type {THREE.Mesh}
         */
        floor = null,

        /**
         * Current angle of the camera rotation around Y axis.
         *
         * @memberof camera
         * @private
         * @type {number}
         */
        yAngle = 0,

        /**
         * Desired angle of the camera rotation around Y axis.
         *
         * @memberof camera
         * @private
         * @type {number}
         */
        desiredYAngle = 0,

        /**
         * The point that camera look at.
         *
         * @memberof camera
         * @private
         * @type {object}
         */
        centerPoint = {
            x: 0,
            y: 0,
            z: 0
        },

        /**
         * Timestamp of the moment when camera turned last time.
         *
         * @memberof camera
         * @private
         * @type {number}
         */
        lastTurnTimestamp = 0;

    /**
     * Creates the scene with fog.
     *
     * @memberof camera
     * @private
     */
    function createScene() {
        scene = new THREE.Scene();
        scene.fog = new THREE.Fog(0x0, 50, 500);
    }

    /**
     * Creates and sets up the perspective camera.
     *
     * @memberof camera
     * @private
     */
    function createCamera() {
        camera = new THREE.PerspectiveCamera(70,
            window.innerWidth / window.innerHeight, 1, 1000);
        camera.position.y = 200;
    }

    /**
     * Creates floor and adds it to the scene.
     *
     * @memberof camera
     * @private
     */
    function createFloor() {
        var geometry = new THREE.PlaneGeometry(FLOOR_SIZE, FLOOR_SIZE),
            texture = new THREE.TextureLoader()
                .load('images/camera/asphalt.jpg'),
            material = null;

        texture.wrapS = THREE.RepeatWrapping;
        texture.wrapT = THREE.RepeatWrapping;
        texture.repeat.set(16, 16);
        material = new THREE.MeshBasicMaterial({
            map: texture
        });
        floor = new THREE.Mesh(geometry, material);
        floor.position.set(0, 0, 0);
        floor.rotation.x = -Math.PI / 2;
        scene.add(floor);
    }

    /**
     * Initializes scene 3D.
     *
     * @memberof camera
     * @private
     */
    function init() {
        lastTurnTimestamp = new Date().getTime();
        createScene();
        createCamera();
        createFloor();
    }

    /**
     * Tries to change desired angle and nears the camera rotation angle to the
     * desired angle.
     * The desired angle can be changed randomly at frequency of one second with
     * probability defined by the TURN_PROBABILITY constant value.
     *
     * @memberof camera
     * @private
     */
    function updateAngle() {
        var timestamp = new Date().getTime(),
            dt = timestamp - lastTurnTimestamp;

        if (dt > 1000) {
            if (Math.random() < TURN_PROBABILITY) {
                desiredYAngle = Math.random() * Math.PI * 2;
            }
            lastTurnTimestamp = timestamp;
        }

        yAngle += Math.min(MAX_ANGULAR_VELOCITY,
            ANGULAR_VELOCITY_FRACTION * (desiredYAngle - yAngle));
    }

    /**
     * Calculates a new position of the camera and changes the point that camera
     * looks at.
     *
     * @memberof camera
     * @private
     */
    function recalculatePositions() {
        var sin = Math.sin(yAngle),
            cos = Math.cos(yAngle);

        camera.position.x += VELOCITY * sin;
        camera.position.z -= VELOCITY * cos;
        centerPoint.x = camera.position.x + RADIUS * sin;
        centerPoint.z = camera.position.z - RADIUS * cos;
        camera.lookAt(centerPoint);
    }

    /**
     * Moves the floor if view field of the camera exceeds the floor edges.
     *
     * @memberof camera
     * @private
     */
    function moveFloor() {
        if (floor.position.x + FLOOR_SIZE / 4 < centerPoint.x) {
            floor.position.x += FLOOR_SIZE / 2;
        }
        if (floor.position.x - FLOOR_SIZE / 4 > centerPoint.x) {
            floor.position.x -= FLOOR_SIZE / 2;
        }
        if (floor.position.z + FLOOR_SIZE / 4 < centerPoint.z) {
            floor.position.z += FLOOR_SIZE / 2;
        }
        if (floor.position.z - FLOOR_SIZE / 4 > centerPoint.z) {
            floor.position.z -= FLOOR_SIZE / 2;
        }
    }

    /**
     * Animation function.
     *
     * @memberof camera
     * @public
     */
    function recalculate() {
        updateAngle();
        recalculatePositions();
        moveFloor();
    }

    init();

    app.pages.camera = {
        number: 4,
        title: 'Camera and move',
        scene: scene,
        camera: camera,
        animate: recalculate,
        backgroundColor: '#000'
    };

}(window.app));
