/*
 * TODO:
 * Compile jQuery templates
 * Use {{each}} in templates
 */

//Carousel constructor
shorts.Carousel = function(container, config) {
    
    //Config
    this.config = $.extend({
        filmsPerPage: 12,
        
        getFilmsUrl: '/ajax/films/{filter}',
        
        /**
         * This number is used for caching sets of random results.
         * The higher the number, the more random results there will be at the expense of DB hits
         * The lower the number, the more cache hits there will be, so speed etc. will increase
         */
        numRandomSets: 50,
        
        numWordsInSynopsis: 25,
        
        //Number of pages to display; should be odd
        numPaginatorItems: 21
    }, config);
    
    //Get handles on DOM elements
    this.$header        = $('.component-heading h2', container);
    this.$filterSelect  = $('#carouselFilter', container);
    this.$filmList      = $('.carousel-splash-list ul', container);
    this.$miniPager     = $('.pagination-top div', container);
    this.$pager         = $('.pagination ul', container);
    this.$shuffle       = $('.shuffle', container);
        
    //Get templates
    this.$filmTemplate       = $('.template', this.$filmList);
    this.$pagerItemTemplate  = $('.template', this.$pager);
    this.$miniPagerTemplate  = $('.template', this.$miniPager);
    
    //Register user events
    this.$filterSelect.change($.proxy(this.onFilterSelect, this));
    this.$shuffle.click($.proxy(this.onShuffleClick, this));
    
    //Setup params using saved state if available, falling back to defaults
    this.setPage($.bbq.getState('page') || 1);
    this.setFilter($.bbq.getState('filter') || this.$filterSelect.val());
    
    //Set the correct filter in the <select>
    this.$filterSelect.val(this.getFilter());
    
    //Handle URL hash changes
    $(window).bind('hashchange', $.proxy(this.onHashChange, this));
    
    //Start
    $(window).trigger('hashchange');
    
};

//Carousel methods
shorts.Carousel.prototype = {

    /**
     * Getters
     */
    getPage: function() {
        return parseInt($.bbq.getState('page'));
    },

    getFilter: function() {
        return $.bbq.getState('filter');
    },

    /**
     * Setters
     */
    setPage: function(page) {
        $.bbq.pushState({ page: page });
    },

    setFilter: function(filter) {
        $.bbq.pushState({ filter: filter });
    },
        
    /**
     * Returns the URL that should be used in links: i.e. the hash and all 
     * params required to preserve the carousel's state)
     *
     * @param {Object}        Params to overwrite the current defaults
     * @return {String}       The fragment, e.g. '#filter=title&page=1
     */
    getUrl: function(newParams) {
        var params = $.extend({
            page:   this.getPage(),
            filter: this.getFilter()
        }, newParams);
        
        var fragment = '#' + $.param(params);
        
        return fragment;
    },

    /**
     * Fetches films from the server
     */
    fetch: function() { 
        var filter = this.getFilter(),
            page = this.getPage();
        
        //Make sure the correct filter is chosen in the <select>
        this.$filterSelect.val(filter);
        
        //Some filters (e.g. genres) are actually a filter and id; split these out
        var parts = filter.split('_');
        var filterName = parts[0];
        var filterId = parts[1];
    
        //Create final request URL
        var url = this.config.getFilmsUrl.supplant({
            page:   page,
            filter: filterName,
            limit:  this.config.filmsPerPage
        });
        
        //Build querystring portion of URI
        var params = {
            page: page,
            limit: this.config.filmsPerPage
        };
        
        //Add params specific to a certain filter
        switch (filterName) {
            case 'random' :
                var randomSetNum = Math.ceil(Math.random() * this.config.numRandomSets);
                params.set = randomSetNum;
                break;
            
            case 'genre' :
                params.genre = filterId;
                break;
        }
        
        url += '?' + $.param(params);

        //Load films
        $.ajax({
            url: url,
            success: $.proxy(function(data) {
                this.addFilms(data.items);
                this.updateMiniPagers(data.pager);
                this.updateMainPager(data.pager);
                this.toggleShuffle();
            }, this)
        });
    },
    
    /**
     * Called when the URL changes (e.g. user clicks a carousel internal link, loads page)
     * Loads films
     */
    onHashChange: function(event) {
        var state = event.getState();
        this.setPage(state.page);
        this.setFilter(state.filter);
        
        this.fetch();
    },
    
    /**
     * Called when user changes the filter <select>
     */
    onFilterSelect: function(event) {
        this.setFilter($(event.target).val());
    },
    
    /**
     * Called when the user clicks the 'shuffle' button
     */
    onShuffleClick: function(event) { 
        event.preventDefault();
        
        this.fetch();
    },
    
    /**
     * Creates film elements and adds them to the DOM
     * 
     * TODO: 
     * Use {{each}} or other method instead of doing multiple DOM insertions,
     *
     * @param {Array}      Array of film objects as returned from the server
     */
    addFilms: function(films) {
        var $filmList = this.$filmList,
            $template = this.$filmTemplate,
            limitWords = shorts.helpers.limitWords,
            maxNumWords = this.config.numWordsInSynopsis;
        
        //Shuffle shortlist films
        if (this.$filterSelect.val() == 'shortlist') {
            films = this.shuffleArray(films);
        }
        
        //Clear existing films
        $filmList.html('');
            
        //Create HTML for new films
        $.each(films, function(index, film) {
            //Limit the the number of words in the synopsis to avoid overflowing from container
            film.synopsis = limitWords(film.synopsis, maxNumWords, '...');
            
            //Add the film
            $filmList.append(
                $template.tmpl(film)
            );  
        });
    },
    
    /**
     * Updates the HTML for the mini pager
     *
     * @param {Object}      The pager part of the ajax response
     */
    updateMiniPagers: function(pager) {
        //If on 'random', hide the pager
        if (this.getFilter() == 'random') {
            this.$miniPager.html('');
            return;
        }
        
        //Create data that will populate template
        var data = pager;
        
        //Add 'previous' URL
        if (pager.currentPage > 1)
            data.prevUrl = this.getUrl({page: pager.currentPage - 1 });
        
        //Add 'next' URL
        if (pager.currentPage < pager.lastPage)
            data.nextUrl = this.getUrl({page: pager.currentPage + 1 });
        
        //Add the HTML to the DOM
        this.$miniPager.html(
            this.$miniPagerTemplate.tmpl(data)
        );
    },
    
    /**
     * Creates the HTML for the main pagination UI
     *
     * @param {Object}      The pager part of the ajax response
     */
    updateMainPager: function(pager) {
        var $container = this.$pager,
            $template = this.$pagerItemTemplate;
    
        //Clear pager
        $container.html('');
    
        //If filter is 'random', don't show the pager
        if (this.getFilter() == 'random') return
        
        //Add 'previous' link
        if (pager.currentPage > 1) {
            
            $container.append($template.tmpl({
                cssClass:   'prev',
                url:        this.getUrl({ page: pager.currentPage - 1 }),
                text:       'Previous'
            }));
            
        }

        //Build traditional page range (x before/after current page)
        var total = this.config.numPaginatorItems;
        var range = Math.floor(total / 2);
        var firstPage = (function() {
            var n = pager.currentPage - range;
            if (n < 1) n = 1;
            return n;
        })();

        var lastPage = (function() {
            var n = pager.currentPage + range;
            if (n > pager.lastPage) n = pager.lastPage;
            return n;
        })();
        
        //Add pages if necessary to get total number of page links
        while ((lastPage - firstPage) < (total - 1)) {
            if (lastPage >= pager.lastPage && firstPage <= 1) break;
            if (lastPage < pager.lastPage)
                lastPage++;
            else
                firstPage--;
        }
        
        //Build page list
        for (var i = firstPage; i <= lastPage; i++) {            
            $container.append($template.tmpl({
                cssClass:   i == pager.currentPage ? 'selected' : '',
                url:        this.getUrl({ page: i }),
                text:       i
            }));
        }
        
        //Add 'next' link
        if (pager.currentPage < pager.lastPage) {
            
            $container.append($template.tmpl({
                cssClass:   'next',
                url:        this.getUrl({ page: pager.currentPage + 1}),
                text:       'Next'
            }));
            
        }
    },
    
    /**
     * Shows/hides the shuffle button depending on the filter selected
     * ('random' has the shuffle button, other filters don't)
     */
    toggleShuffle: function() {
        if (this.getFilter() == 'random') {
            this.$shuffle.show();
        } else { //Not 'random'
            this.$shuffle.hide();
        }
    },
    
    /**
     * Shuffle the members of an array
     * param {Array}
     * @return {Array}
     */
    shuffleArray: function(array) {
        var tmp, current, top = array.length;

        if(top) while(--top) {
            current = Math.floor(Math.random() * (top + 1));
            tmp = array[current];
            array[current] = array[top];
            array[top] = tmp;
        }

        return array;
    }
    
};

