(function($) {
  $.fn.contentSlide = function(options) {
    options = $.extend(true, {}, $.fn.contentSlide.defaults, options || {});

    this
      .setupExtras(options.setup || $.fn.contentSlide.base, options)
      .trigger('content-slide.initialize');

    return this;
  };

  $.fn.contentSlide.base = {
    initialize: [function(options) {
      this.bind('content-slide.initialize', function() {
        $(this)
          .trigger('content-slide.build')
          .trigger('content-slide.bindEvents')
          .trigger('content-slide.updateContent');
      });
    }],

    bindEvents: [function(options) {
      this.bind('content-slide.bindEvents', function() {
        var self = $(this);

        var last = { x : 0 };
        var start = { x : 0 };
        var current = { x : 0 };
        var touchLast = { x : 0 };
        var touchStart = { x : 0 };
        var touchCurrent = { x : 0 };

        var timeStamp = undefined;
        var startX = undefined,
            startY = undefined;
        var pageX = undefined,
            pageY = undefined;
        var elementX = undefined;

        function ignoreEvent(event) {
          if (event.target.nodeName == 'A' || event.target.parentNode.nodeName == 'A') {
            return true;
          }

          event.preventDefault();
        }

        if (document.addEventListener) {
          document.addEventListener('touchmove', ignoreEvent, false);
        }

        function touchdown(event) {
          if (Preloader.finished) {
            $('#loading').remove();
          }

          if (event.target.nodeName == 'A' || event.target.parentNode.nodeName == 'A') {
            return true;
          }

          if (event.touches && event.touches.length) {
            event.preventDefault();
            event = event.touches[0];

            document.addEventListener('touchstart', ignoreEvent, false);
            document.addEventListener('touchend', ignoreEvent, false);
          }

          touchStart   = { x : event.clientX };
          touchCurrent = touchStart;
          start        = { x : touchStart.x };
          if ('Touch' in window) {
            if (self[0].style.webkitTransform) {
              elementX = parseInt(self[0].style.webkitTransform.match(/translate3d\(([\-0-9]+)px/)[1], 10);
            } else {
              elementX = 0;
            }
          } else {
            elementX = parseInt(self.css('left'), 10);
          }
          startX = event.clientX;
          startY = event.clientY;

          return false;
        }

        function touchmove(event) {
          if (!startX || !startY) {
            return;
          }

          if (event.touches && event.touches.length) {
            event.preventDefault();
            event = event.touches[0];
          }

          timeStamp = event.timeStamp;
          touchLast    = touchCurrent;
          touchCurrent = { x : event.clientX };
          last = current;
          current = { x : touchCurrent.x - start.x };

          pageX = event.clientX;
          pageY = event.clientY;

          var width = -1 * options.data[options.position.y].images.length;

          if ($.trim($(this).css('width')) != '999999px') {
            width = parseInt($(this).css('width'), 10);
          } else {
            self.find('.row:eq(' + options.position.y + ') .item').each(function() {
              width += $(this).width();
            });
          }

          var innerWidth = window.innerWidth || $(window).width();
          var x = Math.floor(Math.max(Math.min(0, current.x + elementX), -1 * (width - innerWidth)));
          var y = Math.floor(options.position.y * self.find(options.className).height() * -1);

          if (innerWidth > width) {
            x = 0;
          }

          if ('Touch' in window) {
            self[0].style.webkitTransitionDuration = '0.5s';
            self[0].style.webkitTransform = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
          } else {
            self.css({
              'left': x + 'px'
            });
          };

          return false;
        }

        function touchstop(event) {
          if (event.target.nodeName == 'A' || event.target.parentNode.nodeName == 'A') {
            return true;
          }

          if (event.touches) {
            event.preventDefault();
            document.removeEventListener('touchstart', ignoreEvent, false);
            document.removeEventListener('touchend', ignoreEvent, false);
          }

          var stop         = undefined;
          var start        = undefined;
          var xDistance    = Math.abs(startX - pageX);
          var yDistance    = Math.abs(startY - pageY);
          var xDistanceMet = xDistance >= parseInt(options.sensitivity, 10);
          var yDistanceMet = yDistance >= parseInt(options.sensitivity, 10);

          if (xDistanceMet && xDistance > yDistance) {
            start = startX;
            stop  = pageX;
          } else if (yDistanceMet && yDistance > xDistance) {
            start = startY;
            stop  = pageY;
          }

          var time      = Math.abs(timeStamp - event.timeStamp);
          var speed     = (Math.abs(start - stop) /
                           Math.abs(time)).toPrecision(5);
          var duration  = Math.max(1 / speed * Math.abs(options.extraShift),
                          Math.abs(options.extraShift) * 0.5);
          var direction = Math.min(stop - start, 1) > 0 ? -1 : 1;
          var yChanged  = false;

          if (time > 300) {
            startX = startY = undefined;

            return;
          }

          if (xDistanceMet && xDistance > yDistance) {
          } else if (yDistanceMet && yDistance > xDistance) {
            if (direction == -1 && options.position.y > 0) {
              options.position.x  = x = 0;
              options.position.y -= 1;
              yChanged = true;
            } else if (direction == 1 && options.position.y < (options.rows || options.data.length - 1)) {
              options.position.x  = x = 0;
              options.position.y += 1;
              yChanged = true;
            }
          }

          var pointDiff = current.x - last.x,
              timeDiff = event.timeStamp - timeStamp,
              touchDistance = Math.abs(pointDiff),
              speedX = Math.abs(pointDiff) / timeDiff;

          current.x = current.x + Math.max(-800, Math.min(800, pointDiff * timeDiff * 3));

          var width = -1 * options.data[options.position.y].images.length;

          if ($.trim($(this).css('width')) != '999999px') {
            width = parseInt($(this).css('width'), 10);
          } else {
            self.find('.row:eq(' + options.position.y + ') .item').each(function() {
              width += $(this).width();
            });
          }

          var innerWidth = window.innerWidth || $(window).width();
          var x = Math.floor(Math.max(Math.min(0, current.x + elementX), -1 * (width - innerWidth)));
          var y = Math.floor(options.position.y * self.find(options.className).height() * -1);

          if (innerWidth > width || yChanged) {
            last.x = start.x = current.x = x = 0;
          }

          self.trigger('content-slide.updateContent')

          duration = Math.min(duration, options.maxDuration);

          if ('Touch' in window) {
            self[0].style.webkitTransitionDuration = Math.round(duration / 1000) + 's';
            self[0].style.webkitTransitionTimingFunction = 'ease-out';
            self[0].style.webkitTransform = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
          } else {
            self
              .stop()
              .animate({
                'top' : y + 'px',
                'left': x + 'px'
              }, {
                'easing'   : 'easeOutQuint',
                'duration' : duration
              });
          }

          startX = startY = undefined;

          return false;
        }

        if ('Touch' in window) {
          this.addEventListener('touchstart', touchdown, false);
          this.addEventListener('touchmove', touchmove, false);
          this.addEventListener('touchend', touchstop, false);

          window.onorientationchange = function() {
            self.trigger('content-slide.jumpTo');
          };
        } else {
          $(this)
            .bind('mousedown.content-slide', touchdown)
            .bind('mousemove.content-slide', touchmove)
            .bind('mouseup.content-slide, mouseleave.content-slide', touchstop);

          $(window).resize(function() {
            self.trigger('content-slide.jumpTo');
          });
        }
      });
    }],

    build: [function(options) {
      this.bind('content-slide.build', function() {
        var self = $(this);

        $(options.data).each(function(index, item) {
          self
            .append('<div class="row">')
            .find('.row:last')
              .append(item.images);

          if (item.current) {
            options.position.y = index;

            setTimeout(function() {
              self.trigger('content-slide.jumpTo');
            }, 50);
          }
        });

        self.width(999999);
      });
    }],

    jumpTo: [function(options) {
      this.bind('content-slide.jumpTo', function(event, animate, delay) {
        var x = Math.floor(options.position.x * $(this).find(options.className).width()  * -1);
        var y = Math.floor(options.position.y * $(this).find(options.className).height() * -1);

        if (('Touch' in window)) {
          this.style.webkitTransitionDuration = (animate ? Math.round((delay || 2000) / 1000) + 's' : '0s');
          this.style.webkitTransitionTimingFunction = 'ease-out';
          this.style.webkitTransform = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
        } else {
          $(this).stop()[animate ? 'animate' : 'css']({
            'top'  : y + 'px',
            'left' : x + 'px'
          }, delay || 2000);
        }

        $(this).trigger('content-slide.updateContent');

        var
        self = $('#bottom-nav > li, #info-nav > li')
        self.find('span span').stop().animate({ height: 0 }, 200, function() {
          self.removeClass('hover');
        });
      });
    }],

    watch: [function(options) {
      var
      self = this;
      self.data('last-hash', window.location.hash.replace(/^#/, ''));

      setInterval(function() {
        var lastHash    = self.data('last-hash');
        var currentHash = window.location.hash.replace(/^#/, '');

        if (currentHash !== lastHash) {
          self.data('last-hash', currentHash);

          $(options.data).each(function(index, item) {
            var hash = item.name.toLowerCase().replace(/[^a-z0-9]+/g, '-');

            if (currentHash == hash) {
              var previousY = options.position.y;
              var distance  = Math.abs(previousY - index);
              var delay     = distance * 500;

              options.position.x = 0;
              options.position.y = index;

              self.trigger('content-slide.jumpTo', [true, delay]);

              return false;
            }
          });
        }
      }, 300);
    }],

    updateContent: [function(options) {
      this.bind('content-slide.updateContent', function() {
        var project = options.data[options.position.y];
        var hash    = project.name.toLowerCase().replace(/[^a-z0-9]+/g, '-');

        window.location.hash = hash;

        $(this).data('last-hash', hash);

        $('#info-nav')
          .find('li:first > div')
            .text(project.name)
          .end()
          .find('li li')
          .empty()
            .append('<h2>' + project.name + '</h2>')
            .append(project.html);

        if ('Touch' in window) {
          setTimeout(function() {
            $('#info-nav').css({
              'left'  : Math.floor(window.innerWidth - $('#info-nav').width()) + 'px',
              'right' : 'auto'
            });
          }, 50);
        }
      });
    }]
  };

  $.fn.contentSlide.defaults = {
    friction    : 0.325,
    className   : '.item',
    extraShift  : 800,
    sensitivity : 20,
    maxDuration : 1750,
    position    : {
      x : 0,
      y : 0
    }
  };

  // From:
  // http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/
  $.fn.setupExtras = $.fn.setupExtras || function(setup, options) {
    for (property in setup) {
      if (setup[property] instanceof Array) {
        var length = setup[property].length;

        for (var i = 0; i < length; i++) {
          setup[property][i].call(this, options);
        }
      } else {
        setup[property].call(this, options);
      }
    }

    return this;
  };
})(jQuery);

