//     jquery.sausage.js 1.0.0
//     (c) 2011 Christopher Cliff
//     Freely distributed under the MIT license.
//     For all details and documentation:
//     http://christophercliff.github.com/sausage

(function($, undefined){
    
    $.widget('cc.sausage', {
        
        // # Options
        //
        //
        
        options: {
            
            // ### page `string`
            //
            // Sets the string to be used to select page elements.
            // 
            // Example:
            // 
            //      $(window)
            //          .sausage({
            //              page: '.my-page-selector'
            //          })
            //          ;
            //
            page: '.page',
            
            // ### content `function`
            // 
            // Sets the content of the sausage elements. Use `i` and `$page` to render content dynamically.
            // 
            // Example:
            // 
            //      $(window)
            //          .sausage({
            //              content: function (i, $page) {
            //                  return '<div>' + $page.data('name') + '</div>';
            //          }
            //      })
            //      ;
            //
            content: function (i, $page) {
                return '<span class="sausage-span">' + (i + 1) + '</span>';
            },
            
            // ### heightSelector `string`
            // 
            // Sets the selector to use to determine the page height.  This can be useful for
            // pages using absolute positioning.  The default is body.
            // 
            // Example:
            // 
            //      $(window)
            //          .sausage({
            //              content: function (i, $page) {
            //                  return '<div>' + $page.data('name') + '</div>';
            //          }
            //      })
            //      ;
            //
            heightSelector: 'body'
            
        },
        
        // # Private Methods
        //
        //
        
        // ## ._create()
        //
        //
        _create: function () {
            
            var self = this,
                $el = self.element;
            
            // Use $el for the outer element.
            self.$outer = $el;
            // Use `body` for the inner element if the outer element is `window`. Otherwise, use the first child of `$el`.
            self.$inner = $.isWindow(self.element.get(0)) ? $('body') : $el.children(':first-child');
            self.$sausages = $('<div class="sausage-set"/>');
            self.sausages = self.$sausages.get(0);
            self.offsets = [];
            
            self.$sausages
                .appendTo(self.$inner)
                ;
            
            // Trigger the `create` event.
            self._trigger('create');
            
            return;
        },
        
        // ## ._init()
        //
        //
        _init: function () {
            
            var self = this;
            
            self.draw();
            self._update();
            self._events();
            self._delegates();
            
            // Add a CSS class for styling purposes.
            self.$sausages
                .addClass('sausage-set-init')
                ;
            
            self.blocked = false;
            
            // Trigger the `init` event.
            self._trigger('init');
            
            return;
        },
        
        // ## ._events()
        //
        //
        _events: function () {
            
            var self = this;
            
            self.hasScrolled = false;
            
            $(window)
                .bind('resize.sausage', function(){
                    
                    self.draw();
                    
                })
                .bind('scroll.sausage', function(e){
                    
                    self.hasScrolled = true;
                    
                })
                ;
            
            // [Prevent crazy amounts of scroll events from being fired](http://ejohn.org/blog/learning-from-twitter/) by setting an interval and listening.
            setInterval(function(){
                
                if (!self.hasScrolled)
                {
                    return;
                }
                
                self.hasScrolled = false;
                self._update();
                
            }, 250);
            
            return;
        },
        
        // ## ._getCurrent()
        //
        //
        _getCurrent: function () {
            
            var self = this,
                st = self.$outer.scrollTop() + self._getHandleHeight(self.$outer, self.$inner)/4,
                h_win = self.$outer.height(),
                h_doc = self.$inner.height(),
                i = 0;
            
            for (l = self.offsets.length; i < l; i++)
            {
                if (!self.offsets[i + 1])
                {
                    return i;
                }
                else if (st <= self.offsets[i])
                {
                    return i;
                }
                else if (st > self.offsets[i] && st <= self.offsets[i + 1])
                {
                    return i;
                }
            }
            
            return i;
        },
        
        // ## ._delegates()
        //
        //
        _delegates: function () {
            
            var self = this;
            
            self.$sausages
                .delegate('.sausage', 'hover', function(){
                    
                    if (self.blocked)
                    {
                        return;
                    }
                    
                    $(this)
                        .toggleClass('sausage-hover')
                        ;
                    
                })
                .delegate('.sausage', 'mouseout', function(){
                    
                    if (self.blocked)
                    {
                        return;
                    }
                    
                    $(this)
                        .removeClass('sausage-hover')
                        ;
                    
                })
                .delegate('.sausage', 'click', function(e){
                    e.preventDefault();
                    
                    if (self.blocked)
                    {
                        return;
                    }
                    
                    var $sausage = $(this),
                        val = $sausage.index(),
                        o = self.$inner.find(self.options.page).eq(val).offset().top;
                    
                    self.$outer
                        .scrollTop(o)
                        ;
                    
                    // Trigger the `onClick` event.
                    // 
                    // Example:
                    // 
                    //      $(window)
                    //          .sausage({
                    //              onClick: function (e, o) {
                    //                  alert('You clicked the sausage at index: ' + o.i);
                    //          }
                    //      })
                    //      ;
                    //
                    self._trigger('onClick', e, {
                        $sausage: $sausage,
                        i: val
                    });
                    
                    if ($sausage.hasClass('current'))
                    {
                        return;
                    }
                    
                    // Trigger the `onUpdate` event.
                    self._trigger('onUpdate', e, {
                        $sausage: $sausage,
                        i: val
                    });
                })
                ;
            
            return;
        },
        
        // ## ._update()
        //
        //
        _update: function () {
            
            var self = this;
                i = self._getCurrent(),
                c = 'sausage-current';
            
            if (i === self.current || self.blocked)
            {
                return;
            }
            
            self.current = i;
            
            self.$sausages.children().eq(i)
                .addClass(c)
            .siblings()
                .removeClass(c)
                ;
            
            // Trigger the `update` event.
            self._trigger('update');
            
            return;
        },
        
        // ### _getHandleHeight()
        // 
        // 
        _getHandleHeight: function ($outer, $inner) {
            
            var h_outer = $outer.height(),
                h_inner = $inner.height();
            
            return h_outer/h_inner*h_outer;
        },
        
        // # Public Methods
        //
        //
        
        // ### draw `.sausage("draw")`
        // 
        // Creates the sausage UI elements.
        draw: function () {
            
            var self = this,
                h_win = self.$outer.height(),
                h_doc = self.$inner.height(),
                $items = self.$inner.find(self.options.page),
                $page,
                s = [],
                offset_p,
                offset_s;
            
            self.offsets = [];
            self.count = $items.length;
            
            // Detach from DOM while making changes.
            self.$sausages
                .detach()
                .empty()
                ;
            
            var height = 0;
            $(self.options.heightSelector).each(function() {
                height += $(this).outerHeight();
            });
            
            h_doc = height;
            h_win = h_win - 15;
            
            // Calculate the element heights and push to an array.
            for (var i = 0; i < self.count; i++)
            {
                $page = $items.eq(i);
                offset_p = $page.offset();
                offset_s = (offset_p.top/h_doc*h_win);
                s.push('<div class="sausage' + ((i === self.current) ? ' sausage-current' : '') + '" style="height:' + ($page.outerHeight()/h_doc*h_win) + 'px;top:' + offset_s + 'px;">' + self.options.content(i, $page) + '</div>');
                
                // Create `self.offsets` for calculating current sausage.
                self.offsets.push(offset_p.top);
            }
            
            // Use Array.join() for speed.
            self.sausages.innerHTML = s.join('');
            
            // And reattach.
            self.$sausages
                .appendTo(self.$inner)
                ;
            
            return;
        },
        
        // ### block `.sausage("block")`
        // 
        // Blocks the UI to prevent users from interacting with the sausage UI. Useful when loading data and updating the DOM.
        block: function () {
            
            var self = this,
                c = 'sausage-set-blocked';
            
            self.blocked = true;
            
            // Add a CSS class for styling purposes.
            self.$sausages
                .addClass(c)
                ;
            
            return;
        },
        
        // ### unblock `.sausage("unblock")`
        // 
        // Unblocks the UI once loading and DOM manipulation are complete.
        unblock: function () {
            
            var self = this,
                c = 'sausage-set-blocked';
            
            self.$sausages
                .removeClass(c)
                ;
            
            self.blocked = false;
            
            return;
        },
        
        // ### destroy `.sausage("destroy")`
        // 
        // Removes the sausage instance from the DOM.
        destroy: function () {
            
            var self = this;
            
            self.$sausages
                .remove()
                ;
            
            $(window).unbind('.sausage');
            
            return;
        }
        
    });
    
})(jQuery);
