/**
 * @license 
 * jQuery Tools 1.2.5 Scrollable - New wave UI design
 * 
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 * 
 * http://flowplayer.org/tools/scrollable.html
 *
 * Since: March 2008
 * Date:    Wed Sep 22 06:02:10 2010 +0000 
 */
(function($) { 

   // static constructs
   $.tools = $.tools || {version: '1.2.5'};
   
   $.tools.scrollable = {
      
      conf: {  
         activeClass: 'active',
         circular: false,
         clonedClass: 'cloned',
         disabledClass: 'disabled',
         easing: 'swing',
         initialIndex: 0,
         item: null,
         items: '.items',
         keyboard: true,
         mousewheel: false,
         next: '.next',   
         prev: '.prev', 
         speed: 400,
         vertical: false,
         touch: true,
         wheelSpeed: 0
      } 
   };
               
   // get hidden element's width or height even though it's hidden
   function dim(el, key) {
      var v = parseInt(el.css(key), 10);
      if (v) { return v; }
      var s = el[0].currentStyle; 
      return s && s.width && parseInt(s.width, 10);   
   }

   function find(root, query) { 
      var el = $(query);
      return el.length < 2 ? el : root.parent().find(query);
   }
   
   var current;      
   
   // constructor
   function Scrollable(root, conf) {   
      
      // current instance
      var self = this, 
          fire = root.add(self),
          itemWrap = root.children(),
          index = 0,
          vertical = conf.vertical;
            
      if (!current) { current = self; } 
      if (itemWrap.length > 1) { itemWrap = $(conf.items, root); }
      
      // methods
      $.extend(self, {
            
         getConf: function() {
            return conf;   
         },       
         
         getIndex: function() {
            return index;  
         }, 

         getSize: function() {
            return self.getItems().size();   
         },

         getNaviButtons: function() {
            return prev.add(next);  
         },
         
         getRoot: function() {
            return root;   
         },
         
         getItemWrap: function() {
            return itemWrap;  
         },
         
         getItems: function() {
            return itemWrap.children(conf.item).not("." + conf.clonedClass);  
         },
                     
         move: function(offset, time) {
            return self.seekTo(index + offset, time);
         },
         
         next: function(time) {
            return self.move(1, time); 
         },
         
         prev: function(time) {
            return self.move(-1, time);   
         },
         
         begin: function(time) {
            return self.seekTo(0, time);  
         },
         
         end: function(time) {
            return self.seekTo(self.getSize() -1, time); 
         }, 
         
         focus: function() {
            current = self;
            return self;
         },
         
         addItem: function(item) {
            item = $(item);
            
            if (!conf.circular)  {
               itemWrap.append(item);
            } else {
               itemWrap.children("." + conf.clonedClass + ":last").before(item);
               itemWrap.children("." + conf.clonedClass + ":first").replaceWith(item.clone().addClass(conf.clonedClass));                 
            }
            
            fire.trigger("onAddItem", [item]);
            return self;
         },
         
         
         /* all seeking functions depend on this */      
         seekTo: function(i, time, fn) {  
            
            // ensure numeric index
            if (!i.jquery) { i *= 1; }
            
            // avoid seeking from end clone to the beginning
            if (conf.circular && i === 0 && index == -1 && time !== 0) { return self; }
            
            // check that index is sane            
            if (!conf.circular && i < 0 || i > self.getSize() || i < -1) { return self; }
            
            var item = i;
         
            if (i.jquery) {
               i = self.getItems().index(i); 
               
            } else {
               item = self.getItems().eq(i);
            }  
            
            // onBeforeSeek
            var e = $.Event("onBeforeSeek"); 
            if (!fn) {
               fire.trigger(e, [i, time]);            
               if (e.isDefaultPrevented() || !item.length) { return self; }         
            }  
   
            var props = vertical ? {top: -item.position().top} : {left: -item.position().left};  
            
            index = i;
            current = self;  
            if (time === undefined) { time = conf.speed; }   
            
            itemWrap.animate(props, time, conf.easing, fn || function() { 
               fire.trigger("onSeek", [i]);     
            });    
            
            return self; 
         }              
         
      });
            
      // callbacks   
      $.each(['onBeforeSeek', 'onSeek', 'onAddItem'], function(i, name) {
            
         // configuration
         if ($.isFunction(conf[name])) { 
            $(self).bind(name, conf[name]); 
         }
         
         self[name] = function(fn) {
            if (fn) { $(self).bind(name, fn); }
            return self;
         };
      });  
      
      // circular loop
      if (conf.circular) {
         
         var cloned1 = self.getItems().slice(-1).clone().prependTo(itemWrap),
             cloned2 = self.getItems().eq(1).clone().appendTo(itemWrap);
            
         cloned1.add(cloned2).addClass(conf.clonedClass);
         
         self.onBeforeSeek(function(e, i, time) {

            
            if (e.isDefaultPrevented()) { return; }
            
            /*
               1. animate to the clone without event triggering
               2. seek to correct position with 0 speed
            */
            if (i == -1) {
               self.seekTo(cloned1, time, function()  {
                  self.end(0);      
               });          
               return e.preventDefault();
               
            } else if (i == self.getSize()) {
               self.seekTo(cloned2, time, function()  {
                  self.begin(0);    
               });   
            }
            
         });
         
         // seek over the cloned item
         self.seekTo(0, 0, function() {});
      }
      
      // next/prev buttons
      var prev = find(root, conf.prev).click(function() { self.prev(); }),
          next = find(root, conf.next).click(function() { self.next(); }); 
      
      if (!conf.circular && self.getSize() > 1) {
         
         self.onBeforeSeek(function(e, i) {
            setTimeout(function() {
               if (!e.isDefaultPrevented()) {
                  prev.toggleClass(conf.disabledClass, i <= 0);
                  next.toggleClass(conf.disabledClass, i >= self.getSize() -1);
               }
            }, 1);
         }); 
         
         if (!conf.initialIndex) {
            prev.addClass(conf.disabledClass);  
         }
      }
         
      // mousewheel support
      if (conf.mousewheel && $.fn.mousewheel) {
         root.mousewheel(function(e, delta)  {
            if (conf.mousewheel) {
               self.move(delta < 0 ? 1 : -1, conf.wheelSpeed || 50);
               return false;
            }
         });         
      }
      
      // touch event
      if (conf.touch) {
         var touch = {};
         
         itemWrap[0].ontouchstart = function(e) {
            var t = e.touches[0];
            touch.x = t.clientX;
            touch.y = t.clientY;
         };
         
         itemWrap[0].ontouchmove = function(e) {
            
            // only deal with one finger
            if (e.touches.length == 1 && !itemWrap.is(":animated")) {         
               var t = e.touches[0],
                   deltaX = touch.x - t.clientX,
                   deltaY = touch.y - t.clientY;
   
               self[vertical && deltaY > 0 || !vertical && deltaX > 0 ? 'next' : 'prev']();           
               e.preventDefault();
            }
         };
      }
      
      if (conf.keyboard)  {
         
         $(document).bind("keydown.scrollable", function(evt) {

            // skip certain conditions
            if (!conf.keyboard || evt.altKey || evt.ctrlKey || $(evt.target).is(":input")) { return; }
            
            // does this instance have focus?
            if (conf.keyboard != 'static' && current != self) { return; }
               
            var key = evt.keyCode;
         
            if (vertical && (key == 38 || key == 40)) {
               self.move(key == 38 ? -1 : 1);
               return evt.preventDefault();
            }
            
            if (!vertical && (key == 37 || key == 39)) {             
               self.move(key == 37 ? -1 : 1);
               return evt.preventDefault();
            }    
            
         });  
      }
      
      // initial index
      if (conf.initialIndex) {
         self.seekTo(conf.initialIndex, 0, function() {});
      }
   } 

      
   // jQuery plugin implementation
   $.fn.scrollable = function(conf) { 
         
      // already constructed --> return API
      var el = this.data("scrollable");
      if (el) { return el; }      

      conf = $.extend({}, $.tools.scrollable.conf, conf); 
      
      this.each(function() {        
         el = new Scrollable($(this), conf);
         $(this).data("scrollable", el);  
      });
      
      return conf.api ? el: this; 
      
   };
         
   
})(jQuery);
