(function ($) {

var methods = {

  init : function(options) {
    var defaults = {
      time_constant: 200, // ms
      dist: -100, // zoom scale TODO: make this more intuitive as an option
      shift: 0, // spacing for center image
      padding: 0, // Padding between non center items
      full_width: false, // Change to full width styles
      indicators: false, // Toggle indicators
      no_wrap: false // Don't wrap around and cycle through items.
    };
    options = $.extend(defaults, options);

    return this.each(function() {

      var images, offset, center, pressed, dim, count,
          reference, referenceY, amplitude, target, velocity,
          xform, frame, timestamp, ticker, dragged, vertical_dragged;
      var $indicators = $('<ul class="indicators"></ul>');

      // Initialize
      var view = $(this);
      var showIndicators = view.attr('data-indicators') || options.indicators;

      // Don't double initialize.
      if (view.hasClass('initialized')) {
        // Redraw carousel.
        $(this).trigger('carouselNext', [0.000001]);
        return true;
      }

      // Options
      if (options.full_width) {
        options.dist = 0;
        var firstImage = view.find('.carousel-item img').first();
        if (firstImage.length) {
          imageHeight = firstImage.on('load', function(){
            view.css('height', $(this).height());
          });
        } else {
          imageHeight = view.find('.carousel-item').first().height();
          view.css('height', imageHeight);
        }

        // Offset fixed items when indicators.
        if (showIndicators) {
          view.find('.carousel-fixed-item').addClass('with-indicators');
        }
      }

      view.addClass('initialized');
      pressed = false;
      offset = target = 0;
      images = [];
      item_width = view.find('.carousel-item').first().innerWidth();
      dim = item_width * 2 + options.padding;

      view.find('.carousel-item').each(function (i) {
        images.push($(this)[0]);
        if (showIndicators) {
          var $indicator = $('<li class="indicator-item"></li>');

          // Add active to first by default.
          if (i === 0) {
            $indicator.addClass('active');
          }

          // Handle clicks on indicators.
          $indicator.click(function () {
            var index = $(this).index();
            cycleTo(index);
          });
          $indicators.append($indicator);
        }
      });

      if (showIndicators) {
        view.append($indicators);
      }
      count = images.length;

      function setupEvents() {
        if (typeof window.ontouchstart !== 'undefined') {
          view[0].addEventListener('touchstart', tap);
          view[0].addEventListener('touchmove', drag);
          view[0].addEventListener('touchend', release);
        }
        view[0].addEventListener('mousedown', tap);
        view[0].addEventListener('mousemove', drag);
        view[0].addEventListener('mouseup', release);
        view[0].addEventListener('mouseleave', release);
        view[0].addEventListener('click', click);
      }

      function xpos(e) {
        // touch event
        if (e.targetTouches && (e.targetTouches.length >= 1)) {
          return e.targetTouches[0].clientX;
        }

        // mouse event
        return e.clientX;
      }

      function ypos(e) {
        // touch event
        if (e.targetTouches && (e.targetTouches.length >= 1)) {
          return e.targetTouches[0].clientY;
        }

        // mouse event
        return e.clientY;
      }

      function wrap(x) {
        return (x >= count) ? (x % count) : (x < 0) ? wrap(count + (x % count)) : x;
      }

      function scroll(x) {
        var i, half, delta, dir, tween, el, alignment, xTranslation;

        offset = (typeof x === 'number') ? x : offset;
        center = Math.floor((offset + dim / 2) / dim);
        delta = offset - center * dim;
        dir = (delta < 0) ? 1 : -1;
        tween = -dir * delta * 2 / dim;
        half = count >> 1;

        if (!options.full_width) {
          alignment = 'translateX(' + (view[0].clientWidth - item_width) / 2 + 'px) ';
          alignment += 'translateY(' + (view[0].clientHeight - item_width) / 2 + 'px)';
        } else {
          alignment = 'translateX(0)';
        }

        // Set indicator active
        if (showIndicators) {
          var diff = (center % count);
          var activeIndicator = $indicators.find('.indicator-item.active');
          if (activeIndicator.index() !== diff) {
            activeIndicator.removeClass('active');
            $indicators.find('.indicator-item').eq(diff).addClass('active');
          }
        }

        // center
        // Don't show wrapped items.
        if (!options.no_wrap || (center >= 0 && center < count)) {
          el = images[wrap(center)];
          el.style[xform] = alignment +
            ' translateX(' + (-delta / 2) + 'px)' +
            ' translateX(' + (dir * options.shift * tween * i) + 'px)' +
            ' translateZ(' + (options.dist * tween) + 'px)';
          el.style.zIndex = 0;
          if (options.full_width) { tweenedOpacity = 1; }
          else { tweenedOpacity = 1 - 0.2 * tween; }
          el.style.opacity = tweenedOpacity;
          el.style.display = 'block';
        }

        for (i = 1; i <= half; ++i) {
          // right side
          if (options.full_width) {
            zTranslation = options.dist;
            tweenedOpacity = (i === half && delta < 0) ? 1 - tween : 1;
          } else {
            zTranslation = options.dist * (i * 2 + tween * dir);
            tweenedOpacity = 1 - 0.2 * (i * 2 + tween * dir);
          }
          // Don't show wrapped items.
          if (!options.no_wrap || center + i < count) {
            el = images[wrap(center + i)];
            el.style[xform] = alignment +
              ' translateX(' + (options.shift + (dim * i - delta) / 2) + 'px)' +
              ' translateZ(' + zTranslation + 'px)';
            el.style.zIndex = -i;
            el.style.opacity = tweenedOpacity;
            el.style.display = 'block';
          }

          // left side
          if (options.full_width) {
            zTranslation = options.dist;
            tweenedOpacity = (i === half && delta > 0) ? 1 - tween : 1;
          } else {
            zTranslation = options.dist * (i * 2 - tween * dir);
            tweenedOpacity = 1 - 0.2 * (i * 2 - tween * dir);
          }
          // Don't show wrapped items.
          if (!options.no_wrap || center - i >= 0) {
            el = images[wrap(center - i)];
            el.style[xform] = alignment +
              ' translateX(' + (-options.shift + (-dim * i - delta) / 2) + 'px)' +
              ' translateZ(' + zTranslation + 'px)';
            el.style.zIndex = -i;
            el.style.opacity = tweenedOpacity;
            el.style.display = 'block';
          }
        }

        // center
        // Don't show wrapped items.
        if (!options.no_wrap || (center >= 0 && center < count)) {
          el = images[wrap(center)];
          el.style[xform] = alignment +
            ' translateX(' + (-delta / 2) + 'px)' +
            ' translateX(' + (dir * options.shift * tween) + 'px)' +
            ' translateZ(' + (options.dist * tween) + 'px)';
          el.style.zIndex = 0;
          if (options.full_width) { tweenedOpacity = 1; }
          else { tweenedOpacity = 1 - 0.2 * tween; }
          el.style.opacity = tweenedOpacity;
          el.style.display = 'block';
        }
      }

      function track() {
        var now, elapsed, delta, v;

        now = Date.now();
        elapsed = now - timestamp;
        timestamp = now;
        delta = offset - frame;
        frame = offset;

        v = 1000 * delta / (1 + elapsed);
        velocity = 0.8 * v + 0.2 * velocity;
      }

      function autoScroll() {
        var elapsed, delta;

        if (amplitude) {
          elapsed = Date.now() - timestamp;
          delta = amplitude * Math.exp(-elapsed / options.time_constant);
          if (delta > 2 || delta < -2) {
              scroll(target - delta);
              requestAnimationFrame(autoScroll);
          } else {
              scroll(target);
          }
        }
      }

      function click(e) {
        // Disable clicks if carousel was dragged.
        if (dragged) {
          e.preventDefault();
          e.stopPropagation();
          return false;

        } else if (!options.full_width) {
          var clickedIndex = $(e.target).closest('.carousel-item').index();
          var diff = (center % count) - clickedIndex;

          // Disable clicks if carousel was shifted by click
          if (diff !== 0) {
            e.preventDefault();
            e.stopPropagation();
          }
          cycleTo(clickedIndex);
        }
      }

      function cycleTo(n) {
        var diff = (center % count) - n;

        // Account for wraparound.
        if (!options.no_wrap) {
          if (diff < 0) {
            if (Math.abs(diff + count) < Math.abs(diff)) { diff += count; }

          } else if (diff > 0) {
            if (Math.abs(diff - count) < diff) { diff -= count; }
          }
        }

        // Call prev or next accordingly.
        if (diff < 0) {
          view.trigger('carouselNext', [Math.abs(diff)]);

        } else if (diff > 0) {
          view.trigger('carouselPrev', [diff]);
        }
      }

      function tap(e) {
        pressed = true;
        dragged = false;
        vertical_dragged = false;
        reference = xpos(e);
        referenceY = ypos(e);

        velocity = amplitude = 0;
        frame = offset;
        timestamp = Date.now();
        clearInterval(ticker);
        ticker = setInterval(track, 100);

      }

      function drag(e) {
        var x, delta, deltaY;
        if (pressed) {
          x = xpos(e);
          y = ypos(e);
          delta = reference - x;
          deltaY = Math.abs(referenceY - y);
          if (deltaY < 30 && !vertical_dragged) {
            // If vertical scrolling don't allow dragging.
            if (delta > 2 || delta < -2) {
              dragged = true;
              reference = x;
              scroll(offset + delta);
            }

          } else if (dragged) {
            // If dragging don't allow vertical scroll.
            e.preventDefault();
            e.stopPropagation();
            return false;

          } else {
            // Vertical scrolling.
            vertical_dragged = true;
          }
        }

        if (dragged) {
          // If dragging don't allow vertical scroll.
          e.preventDefault();
          e.stopPropagation();
          return false;
        }
      }

      function release(e) {
        if (pressed) {
          pressed = false;
        } else {
          return;
        }

        clearInterval(ticker);
        target = offset;
        if (velocity > 10 || velocity < -10) {
          amplitude = 0.9 * velocity;
          target = offset + amplitude;
        }
        target = Math.round(target / dim) * dim;

        // No wrap of items.
        if (options.no_wrap) {
          if (target >= dim * (count - 1)) {
            target = dim * (count - 1);
          } else if (target < 0) {
            target = 0;
          }
        }
        amplitude = target - offset;
        timestamp = Date.now();
        requestAnimationFrame(autoScroll);

        if (dragged) {
          e.preventDefault();
          e.stopPropagation();
        }
        return false;
      }

      xform = 'transform';
      ['webkit', 'Moz', 'O', 'ms'].every(function (prefix) {
        var e = prefix + 'Transform';
        if (typeof document.body.style[e] !== 'undefined') {
          xform = e;
          return false;
        }
        return true;
      });

      window.onresize = scroll;

      setupEvents();
      scroll(offset);

      $(this).on('carouselNext', function(e, n) {
        if (n === undefined) {
          n = 1;
        }
        target = offset + dim * n;
        if (offset !== target) {
          amplitude = target - offset;
          timestamp = Date.now();
          requestAnimationFrame(autoScroll);
        }
      });

      $(this).on('carouselPrev', function(e, n) {
        if (n === undefined) {
          n = 1;
        }
        target = offset - dim * n;
        if (offset !== target) {
          amplitude = target - offset;
          timestamp = Date.now();
          requestAnimationFrame(autoScroll);
        }
      });

      $(this).on('carouselSet', function(e, n) {
        if (n === undefined) {
          n = 0;
        }
        cycleTo(n);
      });

    });

  },
  next : function(n) {
    $(this).trigger('carouselNext', [n]);
  },
  prev : function(n) {
    $(this).trigger('carouselPrev', [n]);
  },
  set : function(n) {
    $(this).trigger('carouselSet', [n]);
  }
};

  $.fn.carousel = function(methodOrOptions) {
    if ( methods[methodOrOptions] ) {
      return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
      // Default to "init"
      return methods.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.carousel' );
    }
  }; // Plugin end

}( jQuery ));