var noop = function () { };
var $doc = $(document);
var slice = [].slice;

// Global view, responsible for changing sect views
var AppView = Backbone.View.extend({
    initialize: function (options) {
        app.bind('change', _.bind(this.changeSect, this));
    },

    changeSect: function () {
        var newSect = app.sects.get(app.get('activeSect'));
        var newSectView = newSect.view;

        if (!app.hasChanged('activeSect')) {
            newSectView.activate(null, newSect);
        } else {
            var oldSect = app.sects.get(app.previous('activeSect'));
            var oldSectView = oldSect.view;

            newSectView.activate(oldSect, newSect);
            oldSectView.deactivate(oldSect, newSect);
        }
    }
});

// Base class for sect view & page view
var View = Backbone.View.extend({
    initialize: function (options) {
        // Associate view with its model
        // Views like proj-nav doesn't have a model
        if (this.model) {
            this.model.view = this;
        }

        // If init method has been overridden
        // make sure it's called for only once
        if (this.init !== noop) {
            var _init = this.init;

            this.init = function () {
                var args = _.toArray(arguments);
                var next = args.pop();
                _init.apply(this, args);
                this.init = noop;
                next();
            };
        }

        this.queue('init', 'show', 'hide');
    },

    // Wrap methods so that calling them causes them to be queued in doc
    queue: function () {
        _.each(arguments, function (method) {
            var _method = this[method];
            if (_method === noop) return;
            this[method] = function () {
                var args = [_method, this].concat(_.toArray(arguments));
                $doc.queue(_.bind.apply(_, args));
            };
        }, this);
    },

    // These methods are intended to be overridden
    init: noop,
    show: noop,
    hide: noop
})

// Sect view, responsible for changing page views within it
var SectView = View.extend({
    activate: function (oldSect, newSect) {
        this.init();

        var newPage = newSect.pages.get(newSect.get('activePage'));
        var newPageView = newPage.view;
        var options = { sectChanged: !!oldSect };

        // If active page in that sect didn't change
        if (!newSect.hasChanged('activePage')) {
            newPageView.activate(null, newPage, options);
        } else {
            var oldPage = newSect.pages.get(newSect.previous('activePage'));
            var oldPageView = oldPage.view;

            // Deactive old page view & active new page view
            oldPageView.deactivate(oldPage, newPage, options);
            newPageView.activate(oldPage, newPage, options);
        }

        // If active sect changed
        if (oldSect) this.show();
    },

    deactivate: function (oldSect, newSect) {
        var page = oldSect.pages.get(oldSect.get('activePage'));
        var pageView = page.view;
        this.hide();
        pageView.deactivate(page, null);
    },

    // Scroll into view
    show: function (next) {
        $('#page').animate({ top: -this.model.id * 100 + '%' }, next);
    }
});

// Page view, responsible for animating itself
var PageView = View.extend({
    initialize: function (options) {
        View.prototype.initialize.apply(this, arguments);

        // container is for scrolling, el is for showing/hiding
        this.container = options.container || this.el;
        this.queue('setTitle', 'loadImages', 'scrollToTop');
    },

    events: {
        'mouseover .back': 'showTooltip',
        'mouseout .back': 'hideTooltip',
        'mouseup .back': 'hideTooltip',
        'click .back-to-top': 'scrollToTopHandler'
    },

    scrollToTopHandler: function () {
        this.scrollToTop(true);
    },

    activate: function (oldPage, newPage, options) {
        this.setTitle();
        this.loadImages();
        this.init();

        // If change from antoher page in the same sect
        if (oldPage) {
            // Animate if active sect hasn't changed and app has inited
            this.show(!options.sectChanged && app.inited);
        }
    },

    deactivate: function (oldPage, newPage, options) {
        // If change to antoher page in the same sect
        if (newPage) {
            this.hide(!options.sectChanged && app.inited);
        }

        // Scroll to top without animation
        this.scrollToTop(false);
    },

    setTitle: function (next) {
        document.title = this.$('h1.title').text() + ' - ' + App.title;
        next();
    },

    // Can only be called once
    loadImages: function (next) {
        $('img', this.el).each(function (i, img) {
            var $img = $(img);
            $img.attr('src', $img.data('src'));
        });

        this.loadImages = noop;
        next();
    },

    // Use fade-in effect by default, child class can override
    // this function to use other kinds of animations
    show: function (animate, next) {
        this.fadeIn(animate, next);
    },

    hide: function (animate, next) {
        this.fadeOut(animate, next);
    },

    fadeIn: function (animate, next) {
        var $el = $(this.el);

        if (animate) {
            // el may contain multiple elements
            // only call next() when last element's animation has completed
            $el.fadeIn(_.after($el.length, next));
        } else {
            $el.show();
            next();
        }
    },

    fadeOut: function (animate, next) {
        var $el = $(this.el);

        if (animate) {
            $el.fadeOut('fast', _.after($el.length, next));
        } else {
            $el.hide();
            next();
        }
    },

    scrollToTop: function (animate, next) {
        var $container = $(this.container);

        if (animate) {
            $container.animate({ scrollTop: 0 }, next);
        } else {
            $container.scrollTop(0);
            next();
        }
    },

    showTooltip: function () {
        var $tooltip = this.$('.tooltip');

        if (!$tooltip.length) {
            var $backButton = this.$('.back');

            var tooltip = _.template($('#tooltip-tpl').html(), {
                text: $backButton.text()
            });

            $tooltip = $(tooltip).insertAfter($backButton);
        }

        if (!$tooltip.is(':visible')) {
            $tooltip.animate({
                opacity: 'show',
                marginRight: '-=5'
            }, 'fast');
        }
    },

    hideTooltip: function () {
        var $tooltip = this.$('.tooltip');

        if ($tooltip.is(':visible')) {
            $tooltip.animate({
                opacity: 'hide',
                marginRight: '+=5'
            }, 'fast');
        }
    }
});

var HSectView = SectView.extend({
    hide: function (next) {
        $('#nav .portfolio-back a, #nav .contact-back a')
			.attr('href', '#' + this.model.get('activePage'));
        next();
    }
});
var HomeView = PageView.extend({
    setTitle: function (next) {
        document.title = App.title;
        next();
    }
});
var AboutView = PageView.extend({
    init: function () {
        var $el = $(this.el);

        $el.css('visibility', 'hidden').show();

        this.$('.skills dd').each(function (i, skill) {
            var $skill = $(skill);

            $skill.width($skill.width() * $(skill).text() / 100)
        });

        $el.hide().css('visibility', 'visible');
    }
});

var PSectView = SectView.extend({
    init: function () {
        // Add a nav button to allow user go back to other sect
        $('#nav .about')
			.clone().toggleClass('about portfolio-back')
			.show().appendTo('#nav ul');

        // Clone thumbnails, make them ready for being used in slider
        var $thumbnails = $('#portfolio .thumbnails');
        $thumbnails.clone().toggleClass('thumbnails thumbnail-list')
			.insertAfter($thumbnails);
    },

    hide: function (next) {
        // When user leaves pSect, portfolio nav should link to
        // whatever page that is currently active in pSect
        $('#nav .portfolio a')
			.attr('href', '#' + pSect.get('activePage'));
        next();
    }
});
var PortfolioView = PageView.extend({
    initialize: function () {
        PageView.prototype.initialize.apply(this, arguments);
        this.queue('changeClass');
    },

    init: function () {
        // Add filter
        var $thumbnails = this.$('.thumbnails');
        var filter = _.template($('#filter-tpl').html(), {});
        $(filter).insertBefore($thumbnails);

        var $el = $(this.el);

        var wasHidden = $el.css('display') == 'none';

        if (wasHidden) {
            $el.css('visibility', 'hidden').show();
        }

        // Clicking an item in filter slides the thumb
        var $filter = this.$('.filter').lava();

        if (wasHidden) {
            $el.hide().css('visibility', 'visible');
        }

        var $clone = $thumbnails.clone();

        // Make filter shuffle thumbnails
        $('li', $filter).click(function () {
            var $li = $(this);
            var className = '.' + App.txt2name($li.text());
            var $filtered = $clone.children($li.index() == 0 ?
				'' : className);

            $thumbnails.quicksand($filtered, {
                adjustHeight: 'dynamic',
                attribute: function (item) {
                    return $('a', item).attr('href');
                }
            });
        });
    },

    activate: function () {
        this.changeClass();
        PageView.prototype.activate.apply(this, arguments);
    },

    changeClass: function (next) {
        $(this.el).filter('header')
			.removeClass('list-mode')
			.addClass('overview-mode');
        next();
    }
});
var ProjectView = PageView.extend({
    activate: function (oldPage, newPage, options) {
        this.setTitle();
        this.loadImages();
        this.init();

        // If change from antoher page in the same sect
        if (oldPage) {
            // Animate if sect has changed and app has inited
            var animate = !options.sectChanged && app.inited;

            // If change from portfolio page
            if (oldPage.view instanceof PortfolioView) {
                this.show('fadeIn', animate);
                projNavView.activate(oldPage, newPage, options);

                // If change from project page
            } else {
                projNavView.activate(oldPage, newPage, options);
                var side = this._side(oldPage, newPage);
                this.show('slideIn', side, animate);
            }
        }
    },

    deactivate: function (oldPage, newPage, options) {
        projNavView.deactivate(oldPage, newPage, options);

        // If change to antoher page in the same sect
        if (newPage) {
            var animate = !options.sectChanged && app.inited;

            // If change to portfolio page
            if (newPage.view instanceof PortfolioView) {
                this.hide('fadeOut', animate);

                // If change to project page
            } else {
                var side = this._side(oldPage, newPage);
                this.hide('slideOut', side, animate);
            }
        }

        this.scrollToTop(false);
    },

    // Is the old project at left or right side of the new project
    _side: function (oldPage, newPage) {
        return oldPage.get('index') < newPage.get('index') ? 'left' : 'right';
    },

    show: function (fx) {
        var args = slice.call(arguments, 1);
        this[fx].apply(this, args);
    },

    hide: function (fx) {
        var args = slice.call(arguments, 1);
        this[fx].apply(this, args);
    },

    slideIn: function (toSide, animate, next) {
        var $el = $(this.el);

        if (animate) {
            var width = $el.width();
            var startProps = {
                position: 'absolute',
                top: $(projNavView.el).outerHeight(true)
            };
            startProps[toSide] = '50%';
            startProps['margin-' + toSide] = width * 0.5;

            var endProps = {
                opacity: 'show'
            };
            endProps['margin-' + toSide] = -width * 0.5; ;

            var resetProps = {
                position: 'static',
                marginLeft: 'auto',
                marginRight: 'auto',
                top: 'auto'
            };
            resetProps[toSide] = 'auto';

            $el.css(startProps).animate(endProps, function () {
                $el.css(resetProps);
                next();
            });
        } else {
            $el.show();
            next();
        }
    },

    // Call next() immediately
    slideOut: function (toSide, animate, next) {
        var $el = $(this.el);

        if (animate) {
            var width = $el.width();
            var startProps = {
                position: 'relative'
            };
            startProps[toSide] = 0;

            var endProps = {
                opacity: 'hide'
            };
            endProps[toSide] = -width;

            var resetProps = {
                position: 'static'
            };
            resetProps[toSide] = 'auto';

            $el.css(startProps).animate(endProps, function () {
                $el.css(resetProps);
            });

            next();
        } else {
            $el.hide();
            next();
        }
    }
});
var ProjNavView = PageView.extend({
    initialize: function () {
        PageView.prototype.initialize.apply(this, arguments);
        this.queue('changeClass');
    },

    init: function (index) {
        // Add pagination to slider
        var $el = $(this.el);
        var pagination = _.template($('#pagination-tpl').html(), {})

        $(pagination).insertAfter($('.thumbnail-list', $el));

        $el.css('visibility', 'hidden').show();
        // Set up slider
        $('.proj-nav', $el).slider({
            container: '.thumbnail-list',
            active: index
        });
        $el.hide().css('visibility', 'visible');
    },

    activate: function (oldPage, newPage, options) {
        this.loadImages();
        var index = newPage.get('index');

        // If change from antoher page in the same sect
        if (oldPage) {
            var animate = !options.sectChanged && app.inited;

            // If change from portfolio page
            if (oldPage.view instanceof PortfolioView) {
                this.changeClass();
                this.init(index);
                this.show('slideIn', index, animate);

                // If change from project page
            } else {
                this.show('activateThumbnail', index, animate)
            }
        }
    },

    deactivate: function (oldPage, newPage, options) {
        // If change to antoher page in the same sect
        if (newPage) {
            // If change to portfolio page
            if (newPage.view instanceof PortfolioView) {
                var animate = !options.sectChanged && app.inited;
                this.hide('slideOut', animate);
            }
        }
    },

    changeClass: function (next) {
        $(this.el).removeClass('overview-mode').addClass('list-mode');
        next();
    },

    show: function (fx, index, animate, next) {
        if (fx === 'activateThumbnail') {
            this[fx](index, animate);
            next();
        } else {
            this[fx](index, animate, next);
        }
    },

    hide: function (fx, animate, next) {
        this[fx](animate, next);
    },

    activateThumbnail: function (index, animate) {
        var $el = $(this.el);

        $el.find('.proj-nav').slider('activate', index, animate)
			.find('.thumbnail-list .image')
				.filter('.active')
					.removeClass('active')
				.end()
				.eq(index).addClass('active');
    },

    slideIn: function (index, animate, next) {
        var $el = $(this.el);

        if (animate) {
            // Fix IE6,7's hiding bug
            $('.thumbnail-list', $el).show();

            $el.css({ marginTop: -$el.outerHeight() }).show();
            this.activateThumbnail(index);
            $el.animate({ marginTop: 0 }, next);
        } else {
            $el.show();
            this.activateThumbnail(index);
            next();
        }
    },

    slideOut: function (animate, next) {
        var $el = $(this.el);

        if (animate) {
            $el.animate({ marginTop: -$el.outerHeight() }, function () {
                // Fix IE6,7's hiding bug
                $('.thumbnail-list', $el).hide();

                $el.hide().css({ marginTop: 0 });
                next();
            });
        } else {
            $el.hide();
            next();
        }
    }
});

var CSectView = SectView.extend({
    init: function () {
        // Add nav button
        $('#nav .about')
			.clone().toggleClass('about contact-back')
			.show().appendTo('#nav ul');
    }
});
var ContactView = PageView.extend({
    events: {
        'mouseover .social li': 'showTooltip',
        'mouseout .social li': 'hideTooltip',
        'click .back-to-top': 'scrollToTopHandler'
    },

    init: function () {
        // Google map
//        this.$('.map figure').gmaps({
//            address: this.$('dd.address').text()
//        });

        // Contact form
        var indicators = _.template($('#indicators-tpl').html(), {})
        this.$('form').append(indicators).h5f().submit(function (event) {
            event.preventDefault();

            var $form = $(this);

            if ($form.data('sending')) {
                return;
            } else {
                $form.data('sending', true);
            }

            var $progress = $('.indicators .progress', $form);
            var $sucess = $('.indicators .success', $form);
            var $error = $('.indicators .error', $form);

            $sucess.stop(true, true).hide();
            $error.stop(true, true).hide();
            $progress.stop(true, true).hide().fadeIn();
            $.ajax({
                url: $form.attr('action'),
                type: $form.attr('method'),
                data: $form.serialize(),

                success: function () {
                    $progress.fadeOut('fast', function () {
                        $sucess.fadeIn().delay(5000).fadeOut();
                    });
                },

                error: function () {
                    $progress.fadeOut('fast', function () {
                        $error.fadeIn().delay(5000).fadeOut();
                    });
                },

                complete: function () {
                    $form.data('sending', false);
                }
            })
        });
    },

    showTooltip: function (event) {
        var $tooltip = this.$('.tooltip');
        var $li = $(event.target).closest('li');

        if (!$tooltip.length) {
            var tooltip = _.template($('#tooltip-tpl').html(), {
                text: $li.text()
            });

            $tooltip = $(tooltip).appendTo(this.$('.social'));

            $tooltip.css({
                marginBottom: parseInt($tooltip.css('marginBottom')) + 5
            });
        } else {
            $('.content', $tooltip).text($li.text());
        }

        var $arrow = $('.arrow', $tooltip)

        $tooltip.css({ right: 0 });
        var topOffset = $li.position().top - $tooltip.outerHeight()
			- $arrow.height()
        var leftOffset = $li.position().left + $li.width() / 2;
        var totalWidth = $li.parent().width() - parseInt($li.css('marginLeft'));
        var rightOffset = totalWidth - leftOffset

        var tooltipWidth = $tooltip.outerWidth()

        $tooltip.css({ top: topOffset });

        if (tooltipWidth / 2 > rightOffset) {
            $tooltip.css({ right: 0 });
            $arrow.css({ right: rightOffset });
        } else if (tooltipWidth / 2 > leftOffset) {
            $tooltip.css({ right: totalWidth - tooltipWidth });
            $arrow.css({ right: tooltipWidth - leftOffset })
        } else {
            $tooltip.css({ right: rightOffset - tooltipWidth / 2 });
            $arrow.css({ right: '50%' });
        }

        $tooltip.stop(true, true).animate({
            opacity: 'show',
            marginTop: '+=5'
        }, 'fast');
    },

    hideTooltip: function (event) {
        var $tooltip = this.$('.tooltip');

        $tooltip.animate({
            opacity: 'hide',
            marginTop: '-=5'
        }, 'fast');
    }
});
