/*
 * 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 document, window, THREE */

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

/**
 * Cube page (colored and textured).
 *
 * @module cube
 * @requires lib/threejs
 * @requires {@link app}
 * @namespace app.pages.cube
 * @memberof app
 */

(function cubePage(app) {

    'use strict';

    /**
     * Possible cube modes.
     *
     * @memberof cube
     * @public
     * @const {object}
     */
    var MODES = {
            COLORED: 'colored',
            TEXTURED: 'textured'
        },

        /**
         * Edge length of the cube.
         *
         * @memberof cube
         * @private
         * @const {number}
         */
        EDGE_LENGTH = 200,

        /**
         * Page background color.
         *
         * @memberof cube
         * @public
         * @type {string}
         */
        BACKGROUND_COLOR = '#225',

        /**
         * Easing fraction.
         *
         * @memberof cube
         * @private
         * @const {number}
         */
        EASING_FRACTION = 0.05,

        /**
         * Z-axis rotation velocity.
         *
         * @memberof cube
         * @private
         * @const {number}
         */
        Z_VELOCITY = 0.01,

        /**
         * Velocity of Y-axis rotation.
         *
         * @memberof cube
         * @private
         * @const {number}
         */
        TOUCH_VELOCITY_FRACTION = 0.03,

        /**
         * Reference to the scene.
         *
         * @memberof cube
         * @public
         * @type {THREE.Scene}
         */
        scene = new THREE.Scene(),

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

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

        /**
         * The starting horizontal position of the touch event.
         *
         * @memberof cube
         * @private
         * @type {number}
         */
        startX = 0,

        /**
         * The Y-axis rotation angle when the touchstart event is invoked.
         *
         * @memberof cube
         * @private
         * @type {number}
         */
        startAngleY = 0,

        /**
         * The required Y-axis rotation angle.
         *
         * @memberof cube
         * @private
         * @type {number}
         */
        angleY = 0,

        /**
         * Indicates whether the cube is textured or colored.
         *
         * @memberof cube
         * @private
         * @type {string}
         */
        mode = '',

        /**
         * Textured material of the cube.
         *
         * @memberof cube
         * @private
         * @type {THREE.MeshFaceMaterial}
         */
        texturedMaterial = null;

    /**
     * Creates the perspective camera.
     *
     * @memberof cube
     * @private
     */
    function createCamera() {
        camera = new THREE.PerspectiveCamera(70,
            app.DEVICE_RADIUS / app.DEVICE_RADIUS, 1, 1000);
        camera.position.y = 150;
        camera.position.z = 500;
    }


    /**
     * Creates a cube with randomly colored walls.
     *
     * @memberof cube
     * @private
     * @returns {THREE.Mesh}
     */
    function createColoredCube() {
        var geometry = new THREE.BoxGeometry(EDGE_LENGTH, EDGE_LENGTH,
                EDGE_LENGTH),
            material = new THREE.MeshBasicMaterial({
                vertexColors: THREE.FaceColors,
                overdraw: 0.5
            }),
            len = geometry.faces.length,
            i = 0,
            hex = 0;

        for (i = 0; i < len; i += 2) {
            hex = Math.random() * 0xffffff;
            geometry.faces[i].color.setHex(hex);
            geometry.faces[i + 1].color.setHex(hex);
        }

        return new THREE.Mesh(geometry, material);
    }

    /**
     * Creates a cube with textured walls.
     *
     * @memberof cube
     * @private
     * @returns {THREE.Mesh}
     */
    function createTexturedCube() {
        var geometry = new THREE.BoxGeometry(EDGE_LENGTH, EDGE_LENGTH,
            EDGE_LENGTH);

        return new THREE.Mesh(geometry, texturedMaterial);
    }

    /**
     * Creates the cube and adds it to the scene.
     *
     * @memberof cube
     * @private
     */
    function createCube() {
        if (mode === MODES.TEXTURED) {
            cube = createTexturedCube();
        } else {
            cube = createColoredCube();
        }
        cube.position.y = 150;
        startAngleY = Math.PI / 2;
        angleY = Math.PI / 2;
        startX = 0;
        scene.add(cube);
    }

    /**
     * Initializes scene 3D.
     *
     * @memberof cube
     * @private
     */
    function initScene() {
        createCamera();
        createCube();
    }

    /**
     * Makes rotation step. The cube rotates around Y and Z axes.
     *
     * The rotation around the Y axis is controlled by the touch events. For
     * each step the rotation value is increased of a fraction of required
     * value. It makes the easing effect.
     *
     * The rotation around the Z axis is constant.
     *
     * @memberof cube
     * @public
     */
    function rotationStep() {
        cube.rotation.y += (angleY - cube.rotation.y) * EASING_FRACTION;
        cube.rotation.z += Z_VELOCITY;
    }

    /**
     * Handles touch start event.
     *
     * @memberof cube
     * @private
     * @param {Event} event
     */
    function onTouchStart(event) {
        var touch = event.touches[0];

        event.preventDefault();
        startX = touch.pageX;
        startAngleY = angleY;
    }

    /**
     * Handles touch move event.
     *
     * @memberof cube
     * @private
     * @param {Event} event
     */
    function onTouchMove(event) {
        var touch = event.touches[0];

        event.preventDefault();
        angleY = startAngleY + (touch.pageX - startX) * TOUCH_VELOCITY_FRACTION;
    }

    /**
     * Registers event listeners.
     *
     * @memberof cube
     * @private
     */
    function bindEvents() {
        document.addEventListener('touchstart', onTouchStart, false);
        document.addEventListener('touchmove', onTouchMove, false);
    }

    /**
     * Removes event listeners.
     *
     * @memberof cube
     * @public
     */
    function removeEvents() {
        document.removeEventListener('touchstart', onTouchStart, false);
        document.removeEventListener('touchmove', onTouchMove, false);
    }

    /**
     * Deletes the cube from the scene.
     *
     * @memberof cube
     * @private
     */
    function deleteCube() {
        if (cube) {
            scene.remove(cube);
        }
    }

    /**
     * Deletes the cube from the initialized scene and create a new one.
     *
     * @memberof cube
     * @private
     */
    function rebuildCube() {
        deleteCube();
        createCube();
    }

    /**
     * Sets mode of the displayed cube.
     *
     * @memberof cube
     * @private
     * @param {string} newMode
     */
    function setMode(newMode) {
        if (newMode === mode) {
            return;
        }
        mode = newMode;
        rebuildCube();
    }

    /**
     * Creates textured material for the cube. Material consists of three
     * textures. Each opposite walls of the cube have the same texture.
     *
     * @memberof cube
     * @private
     */
    function initTexturedMaterial() {
        var materials = [
                new THREE.MeshBasicMaterial({
                    map: new THREE.TextureLoader()
                        .load('images/cube/wall-1.jpg')
                }),
                new THREE.MeshBasicMaterial({
                    map: new THREE.TextureLoader()
                        .load('images/cube/wall-2.jpg')
                }),
                new THREE.MeshBasicMaterial({
                    map: new THREE.TextureLoader()
                        .load('images/cube/wall-3.jpg')
                })
            ],
            cubeWalls = [
                materials[0],
                materials[0],
                materials[1],
                materials[1],
                materials[2],
                materials[2]
            ];

        texturedMaterial = new THREE.MeshFaceMaterial(cubeWalls);
    }

    /**
     * Initializes pages.
     *
     * @memberof cube
     * @private
     */
    function init() {
        initScene();
        initTexturedMaterial();
    }

    /**
     * Binds events listener on page show and sets cube mode.
     *
     * @memberof cube
     * @public
     */
    function onShow() {
        bindEvents();
        setMode(app.currentPage.mode);
    }

    init();

    app.pages.cube = {
        number: 1,
        title: 'Colored Cube',
        mode: MODES.COLORED,
        onShow: onShow,
        onHide: removeEvents,
        scene: scene,
        camera: camera,
        animate: rotationStep,
        backgroundColor: BACKGROUND_COLOR
    };

    app.pages.texturedCube = {
        number: 2,
        title: 'Textured Cube',
        mode: MODES.TEXTURED,
        onShow: onShow,
        onHide: removeEvents,
        scene: scene,
        camera: camera,
        animate: rotationStep,
        backgroundColor: BACKGROUND_COLOR
    };

}(window.app));
