(function($, window, Math, undefined) {

    var rollerCoaster,
        isAndroid = /android/.test(navigator.userAgent.toLowerCase()),
        specialNodes = /^(input|textarea|select|option|button|embed|object)$/,
        specialNodesForTouch = new RegExp('^(input|textarea|select|option|button|embed|object|a' + (isAndroid ? '' : '|img') + ')$');

    $.fn.rollerCoaster = rollerCoaster = function(options) {
        return this.each(function() {
            var instance = $.data(this, 'rollerCoaster');
            if (instance) {
                $.extend(true, instance.options, options)
                instance.init();
            } else {
                $.data(this, 'rollerCoaster', new rollerCoaster.prototype.create(options, this));
            }
        });
    };

    rollerCoaster.prototype = {
        /**
         * デフォルト設定
         */
        options: {
            elasticConstant: 0.16,  // 速度減衰係数
            delay: 80               // ドラッグ開始から要素が動き出すまでの遅延時間
        },
        
        /**
         * コンストラクタ
         * @param Object options
         * @param DOM element
         */
        create: function(options, elem) {
            var self = this,
                element = $(elem);

            this.originalElement     = element;
            this.options             = $.extend(true, {}, this.options, options);
            this.client              = 0;
            this.inertialVelocity    = 0;
            this.preventDefaultClick = false;
            this.sections            = [];
            // 
            this.basePosition        = 0;

            this.box = this.element = element;

            this.init();
        },
        
        /**
         * 初期化
         * rollerCoasterクラスを付与されたelementの子を対象に取る
         */
        init: function() {
            var self = this, scripts,
                element = this.element;

            this.sections = element.children();

            // それぞれの要素に対して表示位置・拡大率・透明度を指定
            var positionTop = 0, positionLeft = 0, zindex = 1000, tfScale = 1, opacity = 1, x, imgLeft;
            this.sections.each(function(i){
                if (i < 5) {
                    // 最初に表示する要素5つ分
                    positionLeft = i * 130;
                    positionTop = -0.000089 * Math.pow(positionLeft - 260, 2) + 38;
                    tfScale = 1;
                    opacity = 1;
                    imgLeft = -0.0808 * positionLeft + 42;
                } else {
                    // それ以降の要素
                    positionLeft = (9 - i) * 130;
                    x = 130 * 4 - positionLeft;
                    positionTop = 0.000089 * Math.pow(x - 260, 2) + 6;
                    tfScale = 0.8;
                    opacity = 0.15;
                    imgLeft = 0.0808 * x;
                }

                $(this).css({
                    top    : positionTop + 'px',
                    left   : positionLeft + 'px',
                    zIndex : zindex--,
                    opacity: opacity,
                    scale  : tfScale
                });
                $('img', $(this)).css({
                    left: imgLeft + 'px'
                });
            });
            
            // 関数バインド
            this.container = element.bind('touchstart.rollerCoaster mousedown.rollerCoaster', function(event) {
                return self.dragStart(event);
            }).bind('click.rollerCoaster', function(event) {
                return self.clickHandler(event);
            });
            
            // セレクタ設定
            var parentSection = element.parent().parent();
            var imax = this.sections.size() / 5;
            var li;
            for (i = 0; i < imax; i++) {
                li = document.createElement('li');
                // indexを記録し、後でclickされたときにどれが選択されたかわかるようにする
                $.data(li, 'rollerCoaster', {index:i});
                if (i == 0) $(li).addClass('current');
                $(li).bind('click', function(event) {
                    return self.clickSelector(event);
                });
                $('ul.primarySectionHeaderBtn', parentSection).append(li);
            }
            
        },
        
        /**
         * ドラッグ開始したとき
         * @param jQuery.Event event
         */
        dragStart: function(event) {
            var self = this,
                e = event.type === 'touchstart' ? event.originalEvent.touches[0] : event;
            if (event.type === 'touchstart' && event.originalEvent.touches.length > 1) {
                return;
            }
            this.timeStamp = event.timeStamp;
            this.client = e['clientX'];
            this.inertialVelocity = 0;

            if (this.trigger('dragStart', event) === false) return false;

            this.container.unbind('touchstart.rollerCoaster mousedown.rollerCoaster')
                .bind('touchmove.rollerCoaster mousemove.rollerCoaster', function(event) {
                    return self.drag(event);
                }).bind('touchend.rollerCoaster mouseup.rollerCoaster mouseleave.rollerCoaster', function(event) {
                    return self.dragStop(event);
                });

            if (!(event.type === 'touchstart' ? specialNodesForTouch : specialNodes).test(event.target.nodeName.toLowerCase())) {
                event.preventDefault();
            }
        },
        
        /**
         * ドラッグ中
         * @param jQuery.Event event
         */
        drag: function(event) {
            var self = this, velocity = 0,
                t = event.timeStamp - this.timeStamp,
                e = event.type === 'touchmove' ? event.originalEvent.touches[0] : event;

            if (event.type === 'touchmove' && event.originalEvent.touches.length > 1) {
                return;
            }

            if (this.trigger('drag', event) === false) return false;

            var c = e['clientX'];
            // 前回捕捉した位置からの変位
            var v = this.client - c;

            this.client           = c;
            this.timeStamp        = event.timeStamp;
            this.inertialVelocity = v / t;
            
            //this.slide(this.basePosition + v);
            setTimeout(function() {
                self.slide(self.basePosition + v);
            }, this.options.delay);
            this.preventDefaultClick = true;
        },
        
        /**
         * ドラッグ終了したとき
         * @param jQuery.Event event
         */
        dragStop: function(event) {
            var self = this;

            if (event.type === 'touchend' && event.originalEvent.touches.length > 1) {
                return;
            }

            if (this.trigger('dragStop', event) === false) return false;

            this.container.unbind('.rollerCoaster')
                .bind('touchstart.rollerCoaster mousedown.rollerCoaster', function(event) {
                    return self.dragStart(event);
                }).bind('click.rollerCoaster', function(event) {
                    return self.clickHandler(event);
                });
            
            //this.flick();
            setTimeout(function() {
                self.flick();
            }, this.options.delay);
        },

        /**
         * フリックしたとき
         * ドラッグ速度に応じて慣性で要素をスライドさせる
         */
        flick: function() {
            if (this.trigger('flick') === false) {
                clearInterval(this.inertia);
                this.scrollBack();
                return;
            }
            
            // 不要なフリックを行わない
            if (this.basePosition % 650 == 0) return;
            
            var self = this,
                options = this.options,
                inertialVelocity = this.inertialVelocity;

            clearInterval(this.inertia);
            
            var nearestPosition = 0;
            var currentIndex = 0;
            // 移動方向によるデフォルト行き先設定
            if (0 <= inertialVelocity) {
                // 左方向への移動
                currentIndex = parseInt((this.sections.size() - 1) / 5);
                nearestPosition = currentIndex * 650;
            } else {
                // 右方向への移動
                currentIndex = 0;
                nearestPosition = 0;
            }
            
            if (Math.abs(inertialVelocity) < 0.3) {
                // 十分速度が遅いときは、最寄りのPositionに寄せる
                var nearest = 0, distance = 0;
                this.sections.each(function(i) {
                    if (i % 5 != 0) return true;
                    // 5つセットの最初の要素
                    distance = 130 * i - self.basePosition;
                    if (Math.abs(distance) < nearest || i == 0) {
                        nearest = Math.abs(distance);
                        nearestPosition = 130 * i;
                        currentIndex = i / 5;
                    }
                });
                this.moveTo(nearestPosition);
            } else {
                // ある程度の速度があれば、フリックした方向にページ移動
                this.sections.each(function(i) {
                    if (i % 5 != 0) return true;
                    // 5つセットの最初の要素
                    if (0 < inertialVelocity) {
                        // 左方向への移動
                        if (self.basePosition < 130 * i) {
                            nearestPosition = 130 * i;
                            currentIndex = i / 5;
                            return false;
                        }
                    } else {
                        // 右方向への移動
                        if (self.basePosition < 130 * i) {
                            if (i == 0) return false;
                            i -= 5;
                            nearestPosition = 130 * i;
                            currentIndex = i / 5;
                            return false;
                        }
                    }
                });
                this.slowdown(nearestPosition);
            }
            var parentSection = this.element.parent().parent();
            var li = $('ul.primarySectionHeaderBtn', parentSection).children().eq(currentIndex);
            this.setCurrent(li);
            
            return this;
        },

        /**
         * 移動処理
         * 対応している全要素に対して実行する
         * @param numeric basePosition 移動先の位置
         */
        slide: function(basePosition) {
            var self = this;

            this.basePosition = basePosition;
            this.sections.each(function(i){
                // liの存在する座標（左端からどれだけ離れているか）
                var p = 130 * i - basePosition;
                // 画面外のliは動かさない
                //if (p < - 130 * 2 || p > 130 * 11) return;

                var opacity = 0, tfScale = 1;
                var pLeft = parseInt($(this).css('left').replace('px', ''));
                var pTop  = parseInt($(this).css('top').replace('px', ''));
                var imgLeft = 0;
                
                if (p < 0) {
                    // 画面外左
                    pLeft   = p;
                    pTop    = parseInt(-0.000089 * Math.pow(pLeft - 260, 2) + 38);
                    opacity = 0.0077 * pLeft + 1;
                    if (opacity < 0) opacity = 0;
                    tfScale = 1;
                    imgLeft = -0.0808 * pLeft + 42;
                } else if (p <= 130 * 4) {
                    // フロント5枚分の動作
                    pLeft   = p;
                    //pTop    = parseInt(-0.000089 * Math.pow(pLeft - 260, 2) + 38);
                    pTop    = self.setPositionTop(p);
                    opacity = 1;
                    tfScale = 1;
                    imgLeft = -0.0808 * pLeft + 42;
                } else if (p >= 130 * 5) {
                    // 6枚目以降(背後に薄く表示)の動作
                    var x = p - 130 * 5;
                    pLeft   = 130 * (9 - i) + basePosition;
                    //pTop = 0.000089 * Math.pow(x - 260, 2) + 6;
                    pTop    = self.setPositionTop(p);
                    opacity = 0.15;
                    tfScale = 0.8;
                    imgLeft = 0.0808 * x;
                } else {
                    // 5枚目から6枚目に移動するときの動作
                    var x = p - 130 * 4;
                    pLeft   = 130 * 4 - 50 * Math.pow(x - 65, 2) / Math.pow(65, 2) + 50;
                    pTop = -0.153 * x + 32;
                    opacity = -0.00654 * x + 1;
                    tfScale = -0.00153 * x + 1;
                }

                $(this).css({
                    left   : pLeft + 'px',
                    top    : pTop + 'px',
                    opacity: opacity,
                    scale  : tfScale
                });
                // 位置によって画像のx座標を変化させ、3Dっぽい効果
                $('img', $(this)).css({
                    left: imgLeft + 'px'
                });
            });
        },
        
        /**
         * basePositionによって要素のpositionTopを設定する
         * @param numeric p
         * @return numeric
         */
        setPositionTop: function(p) {
            if (p < 0) {
                // 画面外左
                
            } else if (p <= 130 * 4) {
                // フロント5枚分の動作
                if (p <  11) return 32;
                if (p <  35) return 33;
                if (p <  61) return 34;
                if (p <  92) return 35;
                if (p < 130) return 36;
                if (p < 185) return 37;
                if (p < 335) return 38;
                if (p < 390) return 37;
                if (p < 428) return 36;
                if (p < 459) return 35;
                if (p < 485) return 34;
                if (p < 509) return 33;
                return 32;
            } else if (p >= 130 * 5) {
                // 6枚目以降(背後に薄く表示)の動作
                var x = p - 650;
                if (x <  11) return 12;
                if (x <  35) return 11;
                if (x <  61) return 10;
                if (x <  92) return 9;
                if (x < 130) return 8;
                if (x < 185) return 7;
                if (x < 335) return 6;
                if (x < 390) return 7;
                if (x < 428) return 8;
                if (x < 459) return 9;
                if (x < 485) return 10;
                if (x < 509) return 11;
                return 12;
            } else {
                // 5枚目から6枚目に移動するときの動作
            }
        },
        
        /**
         * 目的座標まで移動させる（初速は0）
         * @param numeric goalPosition
         */
        moveTo: function(goalPosition) {
            var distance = goalPosition - this.basePosition;
            if (distance == 0) return;
            var self = this;
            
            // 1000msで現在地から目的地まで到達するように、2次関数的なイージングを行う
            var a = distance / 250000, v = 0, t = 0;
            var slidePosition = 0;
            var interval = 50;
            this.inertia = setInterval(function() {
                v = (t < 500) ? a * t : -a * (t - 1000);
                slidePosition = self.basePosition + v * interval;
                if ((v < 0 && distance > 0) || (v > 0 && distance < 0)) {
                    // 移動完了
                    self.slide(goalPosition);
                    clearInterval(self.inertia);
                } else {
                    self.slide(slidePosition);
                    t += interval;
                }
            }, interval);
        },
        
        /**
         * 目的座標まで減速させつつ移動させる
         * @param numeric goalPosition
         */
        slowdown: function(goalPosition) {
            var distance = goalPosition - this.basePosition;
            if (distance == 0) return;
            var v0 = this.inertialVelocity;
            if (Math.abs(v0) < 0.3) {
                return this.moveTo(goalPosition);
            }
            // 停止させつつ到達するように調整
            var t1 = 2 * distance / v0;
            if (t1 <= 0) {
                return this.moveTo(goalPosition);
            }
            var self = this;
            
            var slidePosition = 0;
            var a = -v0 / t1, t = 0, v;
            var interval = 50;
            this.inertia = setInterval(function() {
                v = a * t + v0;
                slidePosition = self.basePosition + v * interval;
                if ((v < 0 && goalPosition - self.basePosition > 0) || (v > 0 && goalPosition - self.basePosition < 0)) {
                    // 移動完了
                    self.slide(goalPosition);
                    clearInterval(self.inertia);
                } else {
                    self.slide(slidePosition);
                    t += interval;
                }
            }, interval);
        },
        
        trigger: function(type, event, data) {
            var callback = this.options[type];

            event = $.Event(event);
            event.type = (type === 'flick' ? type : 'flick' + type).toLowerCase();
            data = data || {};

            if (event.originalEvent) {
                for (var i = $.event.props.length, prop; i;) {
                    prop = $.event.props[--i];
                    event[prop] = event.originalEvent[prop];
                }
            }

            return !(callback && callback.call(this.element[0], event, data) === false || event.isDefaultPrevented());
        },

        clickHandler: function(event) {
            if (this.preventDefaultClick) {
                event.preventDefault();
                this.preventDefaultClick = false;
            }
        },
        
        /**
         * マークの選択状態を設定する
         * @param jQuery li
         */
        setCurrent: function(li) {
            if (li.hasClass('current')) return;
            li.siblings().removeClass('current');
            li.addClass('current');
        },
        
        /**
         * マークをクリックされたとき
         * 対応したliセットがフロントに表示されるまで移動させる
         * @param jQuery.Event event
         */
        clickSelector: function(event) {
            if ($(event.target).hasClass('current')) return;
            
            var data = $.data(event.target, 'rollerCoaster');
            
            // 非選択マークのcurrent設定を解除
            this.setCurrent($(event.target));
            
            clearInterval(this.inertia);
            
            var goalPosition = data.index * 650;
            this.moveTo(goalPosition);
        }
    };

    rollerCoaster.prototype.create.prototype = rollerCoaster.prototype;

})(jQuery, window, Math);

