/*
 *      Copyright (c) 2015 Samsung Electronics Co., Ltd
 *
 *      Licensed under the Flora License, Version 1.1 (the "License");
 *      you may not use this file except in compliance with the License.
 *      You may obtain a copy of the License at
 *
 *              http://floralicense.org/license/
 *
 *      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, window*/

/**
 * Scroll helper module.
 * Used UnitBezier from http://stackoverflow.com/a/11697909
 *
 * @module helpers/scroll
 * @namespace helpers/scroll
 */

define({
    name: 'helpers/scroll',
    def: function helpersScroll() {
        'use strict';

        /**
         * Bezier curve to animate. (ease-out).
         *
         * @memberof helpers/scroll
         * @private
         * @type {{X1: number, Y1: number, X2: number, Y2: number}}
         */
        var bezierValue = {X1: 0.215, Y1: 0.61, X2: 0.355, Y2: 1},

            /**
             * Starting position.
             *
             * @memberof helpers/scroll
             * @private
             * @type {number}
             */
            startPosition = 0,

            /**
             * Ending position.
             *
             * @memberof helpers/scroll
             * @private
             * @type {number}
             */
            endPosition = 0,

            /**
             * Animation start time.
             *
             * @memberof helpers/scroll
             * @private
             * @type {number}
             */
            startTime = 0,

            /**
             * Animation duration time.
             *
             * @memberof helpers/scroll
             * @private
             * @type {number}
             */
            durationTime = 0,

            /**
             * Element to animate.
             *
             * @memberof helpers/scroll
             * @private
             * @type {HTMLElement}
             */
            targetElement = null,

            /**
             * Animation request id.
             *
             * @memberof helpers/scroll
             * @private
             * @type {number}
             */
            requestId = 0,

            /**
             * Precision.
             *
             * @memberof helpers/scroll
             * @private
             * @type {number}
             */
            delta = 1e-12,

            /**
             * Bezier curve polynomial coefficients holder.
             *
             * @memberof helpers/scroll
             * @private
             * @type {object}
             */
            polynomial = {
                cx: 0,
                bx: 0,
                ax: 0,
                cy: 0,
                by: 0,
                ay: 0
            };

        /**
         * Inits polynomials.
         *
         * @memberof helpers/scroll
         * @private
         */
        function initBezier() {
            polynomial.cx = 3 * bezierValue.X1;
            polynomial.bx = 3 * bezierValue.X2 - 6 * bezierValue.X1;
            polynomial.ax = 1 + 3 * (bezierValue.X1 - bezierValue.X2);

            polynomial.cy = 3 * bezierValue.Y1;
            polynomial.by = 3 * bezierValue.Y2 - 6 * bezierValue.Y1;
            polynomial.ay = 1 + 3 * (bezierValue.Y1 - bezierValue.Y2);
        }

        /**
         * Solves X curve.
         *
         * @memberof helpers/scroll
         * @private
         * @param {number} x Time step.
         */
        function solveCurveX(x) {
            var t0 = 0,
                t1 = 0,
                t2 = x,
                x2 = 0,
                d2 = 0,
                i = 0;

            for (i = 0; i < 8; i += 1) {
                x2 = -x + ((polynomial.ax * t2 + polynomial.bx) * t2 +
                    polynomial.cx) * t2;
                if (Math.abs(x2) < delta) {
                    return t2;
                }
                d2 = (3.0 * polynomial.ax * t2 + 2.0 * polynomial.bx) * t2 +
                    polynomial.cx;
                if (Math.abs(d2) < delta) {
                    break;
                }
                t2 -= x2 / d2;
            }

            t0 = 0;
            t1 = 1;
            t2 = x;

            if (t2 < t0) {
                return t0;
            }
            if (t2 > t1) {
                return t1;
            }

            while (t0 < t1) {
                x2 = ((polynomial.ax * t2 + polynomial.bx) * t2 +
                    polynomial.cx) * t2;
                if (Math.abs(x2 - x) < delta) {
                    return t2;
                }

                if (x > x2) {
                    t0 = t2;
                } else {
                    t1 = t2;
                }

                t2 = 0.5 * (t1 + t0);
            }

            return t2;
        }

        /**
         * Finds new T as a function of Y along curve X.
         *
         * @memberof helpers/scroll
         * @private
         * @param {number} x Time step.
         */
        function solve(x) {
            var sx = solveCurveX(x);

            return sx * ((polynomial.ay * sx + polynomial.by) * sx +
                polynomial.cy);
        }

        /**
         * Animates element by cubic bezier.
         *
         * @memberof helpers/scroll
         * @private
         */
        function animate() {
            var timeStep = (Date.now() - startTime) / durationTime,
                timeDiff = 0;

            if (timeStep <= 1) {
                timeDiff = solve(timeStep);

                // Lerp using solved T
                targetElement.scrollLeft = (
                    startPosition - startPosition * timeDiff
                ) + (endPosition * timeDiff);

                requestId = window.requestAnimationFrame(animate);
            } else {
                targetElement.scrollLeft = endPosition;
            }
        }

        /**
         * Invokes scroll function and stops previous if present.
         *
         * @memberof helpers/scroll
         * @public
         * @param {HTMLElement} element Target element.
         * @param {number} to Scroll value.
         * @param {number} duration Scroll time in ms.
         */
        function scrollTo(element, to, duration) {
            if (requestId) {
                window.cancelAnimationFrame(requestId);
                requestId = 0;
            }

            startPosition = element.scrollLeft;
            endPosition = to;
            startTime = Date.now();
            durationTime = duration;
            targetElement = element;

            initBezier();

            requestId = window.requestAnimationFrame(animate);
        }

        return {
            scrollTo: scrollTo
        };
    }
});
