/*
 * Copyright (c) 2012 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 define */

/**
 * Gravity model module.
 * Performs calculations for the ball in the gravity.
 */

define({
    name: 'models/gravity',
    requires: [
        'models/ball'
    ],
    def: function gravityInit(ballModel) {
        'use strict';

        /**
         * Ball model module object.
         * @type {Object}
         */
        var ball = ballModel,

            /**
             * Gravity ball width (in pixels).
             * @type {number}
             */
            ballWidth = 0,

            /**
             * Gravity ball height.
             * @type {number}
             */
            ballHeight = 0,

            /**
             * Gravity ball horizontal coordinate.
             * @type {number}
             */
            ballX = 0,

            /**
             * Gravity ball vertical coordinate.
             * @type {number}
             */
            ballY = 0,

            /**
             * Device motion event data.
             * @type {DeviceMotionEvent}
             */
            motionData = null,

            /**
             * Game field height.
             * @type {number}
             */
            gameHeight = 0,

            /**
             * Game field width.
             * @type {number}
             */
            gameWidth = 0,

            /**
             * Ball horizontal acceleration.
             * @type {number}
             */
            dX = 0,

            /**
             * Ball vertical acceleration.
             * @type {number}
             */
            dY = 0,

            /**
             * Determines if the ball is on the game field edges now.
             * @type {Object}
             */
            isBallOnEdges = {
                x: false,
                y: false
            },

            /**
             * Determines if the ball previously hits game field edges.
             * @type {Object}
             */
            wasBallOnEdges = {
                x: false,
                y: false
            },

            /**
             * @type {number}
             */
            cdd = -0.3,

            /**
             * Air resistance.
             * @type {number}
             */
            resistance = 0.98,

            /**
             * Bounce friction.
             * @type {number}
             */
            friction = 0.90,

            /**
             * Bounce side friction.
             * @type {number}
             */
            sideFriction = 0.95,

            /**
             * Coefficient of friction.
             * @type {number}
             */
            frictionC = 0.002;

        /**
         * Sets ball size.
         * @param {number} size
         */
        function setBallSize(size) {
            ballWidth = size;
            ballHeight = size;
        }

        /**
         * Sets game field size.
         * @param {Object} size
         */
        function setGameSize(size) {
            gameWidth = size.width;
            gameHeight = size.height;
        }

        /**
         * Pushes current ball data to ball model.
         */
        function pushBallData() {
            var data = {};
            data.ballX = ballX;
            data.ballY = ballY;
            data.dX = dX;
            data.dY = dY;
            ball.setBallData(data);
        }

        /**
         * Obtains ball data from ball model.
         */
        function pullBallData() {
            var data = ball.getBallData();
            ballX = data.ballX;
            ballY = data.ballY;
            dX = data.dX;
            dY = data.dY;
        }

        /**
         * Check if the ball coordinates are above the game field edges
         * and updates proper object.
         */
        function checkIfBallWasOnEdges() {
            wasBallOnEdges.x = false;
            wasBallOnEdges.y = false;

            if (ballX <= 0) {
                wasBallOnEdges.x = true;
            } else if ((ballX + ballWidth) >= gameWidth) {
                wasBallOnEdges.x = true;
            }
            if (ballY <= 0) {
                wasBallOnEdges.y = true;
            } else if ((ballY + ballHeight) >= gameHeight) {
                wasBallOnEdges.y = true;
            }
        }

        /**
         * Sets environmental properties for the gravity mode.
         */
        function setGravityParameters() {
            cdd = -0.3;
            resistance = 0.98;
            friction = 0.90;
            sideFriction = 0.95;
            frictionC = 0.002;
        }

        /**
         * Sets environmental properties for the sky mode.
         */
        function setSkyParameters() {
            cdd = 0.05;
            resistance = 0.90;
            friction = 0.98;
            sideFriction = 0.95;
            frictionC = 0.002;
        }

        /**
         * Calculates ball coordinates in gravity environment.
         */
        function calculateGravityBallPosition() {
            var x = 0,
                y = 0,
                ddx = 0,
                ddy = 0;

            if (motionData !== null) {
                x = -motionData.accelerationIncludingGravity.x;
                y = -motionData.accelerationIncludingGravity.y;
            }
            ddx = x * -cdd;
            ddy = y * cdd;
            dX += ddx;
            dY += ddy;
            dX *= resistance;
            dY *= resistance;
            ballX += dX;
            ballY += dY;
        }

        /**
         * Checks if the ball is within game field boundaries and hits the edge.
         */
        function checkGameFieldBoundaries() {
            var stickTop = 0,
                stickLeft = 0,
                stickBottom = 0,
                stickRight = 0;

            isBallOnEdges.x = false;
            isBallOnEdges.y = false;

            if (ballX < 0) {
                ballX = 0;
                dX = Math.abs(dX) * friction - frictionC;
                dY *= sideFriction;
                stickLeft = true;
            } else if ((ballX + ballWidth) > gameWidth) {
                ballX = gameWidth - ballWidth;
                dX = -Math.abs(dX) * friction + frictionC;
                dY *= sideFriction;
                stickRight = true;
                if (ballX < 0) {
                    ballX = 0;
                }
            }

            if (ballY < 0) {
                ballY = 0;
                dY = Math.abs(dY) * friction - frictionC;
                dX *= sideFriction;
                stickTop = true;
            } else if ((ballY + ballHeight) > gameHeight) {
                ballY = gameHeight - ballHeight;
                dY = -Math.abs(dY) * friction + frictionC;
                dX *= sideFriction;
                stickBottom = true;
                if (ballY < 0) {
                    ballY = 0;
                }
            }

            isBallOnEdges.x =
                (stickLeft || stickRight) && Math.abs(dX) > 1;
            isBallOnEdges.y =
                (stickTop || stickBottom) && Math.abs(dY) > 1;
        }

        /**
         * Returns if the ball is hitting the edge.
         * @return {boolean}
         */
        function isBallHittingEdge() {
            if ((!wasBallOnEdges.x && isBallOnEdges.x) ||
                (!wasBallOnEdges.y && isBallOnEdges.y)) {
                return true;
            }
            return false;
        }

        /**
         * Returns actual coordinates for ball in gravity or sky mode.
         * Returned object contains x and y property.
         * @return {Object}
         */
        function getBallPosition() {
            var result = {};
            motionData = ball.getMotionData();
            checkIfBallWasOnEdges();
            calculateGravityBallPosition();
            checkGameFieldBoundaries();
            result.x = ballX;
            result.y = ballY;
            return result;
        }

        /**
         * Sets up gravity model properties with given data.
         * @param {Object} data
         */
        function setup(data) {
            setBallSize(data.ballSize);
            setGameSize(data.gameSize);
            if (data.hasOwnProperty('sky')) {
                setSkyParameters();
            } else {
                setGravityParameters();
            }
            pullBallData();
        }

        /**
         * Returns gravity model data.
         * @return {Object}
         */
        function getData() {
            var data = getBallPosition();
            data.vibrate = isBallHittingEdge();
            pushBallData();
            return data;
        }

        return {
            setup: setup,
            getData: getData
        };
    }
});
