;(function ( $, window, undefined ) {

  "use strict";

  var pluginName = 'pep';
  var defaults   = {

    initiate:                       function(){},
    start:                          function(){},
    drag:                           function(){},
    stop:                           function(){},
    easing:                         null,
    rest:                           function(){},
    moveTo:                         false,
    callIfNotStarted:               ['stop', 'rest'],
    startThreshold:                 [0,0],
    grid:                           [1,1],
    debug:                          false,
    activeClass:                    'pep-active',
    multiplier:                     1,
    velocityMultiplier:             2.5,
    shouldPreventDefault:           true,
    allowDragEventPropagation:      true,
    stopEvents:                     '',
    hardwareAccelerate:             true,
    useCSSTranslation:              true,
    disableSelect:                  true,
    cssEaseString:                  "cubic-bezier(0.190, 1.000, 0.220, 1.000)",
    cssEaseDuration:                1000,
    shouldEase:                     true,
    droppable:                      false,
    droppableActiveClass:           'pep-dpa',
    overlapFunction:                false,
    constrainTo:                    false,
    removeMargins:                  true,
    place:                          true,
    deferPlacement:                 false,
    axis:                           null,
    forceNonCSS3Movement:           false,
    elementsWithInteraction:        'input',
    revert:                         false,
    revertAfter:                    'stop',
    revertIf:                       function(){ return true; },
    ignoreRightClick:               true,
    startPos:                       {
        left:                           null,
        top:                            null
    },
    useBoundingClientRect:          false
  };

  function Pep( el, options ) {

    this.name = pluginName;

    this.el  = el;
    this.$el = $(el);

    this.options    = $.extend( {}, defaults, options) ;

    this.$document  = $(this.$el[0].ownerDocument);
    this.$body      = this.$document.find('body');

    this.moveTrigger        = "MSPointerMove pointermove touchmove mousemove";
    this.startTrigger       = "MSPointerDown pointerdown touchstart mousedown";
    this.stopTrigger        = "MSPointerUp pointerup touchend mouseup";
    this.startTriggerArray  = this.startTrigger.split(' ');
    this.moveTriggerArray   = this.moveTrigger.split(' ');
    this.stopTriggerArray   = this.stopTrigger.split(' ');
    this.stopEvents         = [ this.stopTrigger, this.options.stopEvents ].join(' ');

    if ( this.options.constrainTo === 'window' )
      this.$container = this.$document;
    else if ( this.options.constrainTo && (this.options.constrainTo !== 'parent') )
      this.$container = $(this.options.constrainTo);
    else
      this.$container = this.$el.parent();

    if ( this.isPointerEventCompatible() )
      this.applyMSDefaults();

    this.CSSEaseHash    = this.getCSSEaseHash();
    this.scale          = 1;
    this.started        = false;
    this.disabled       = false;
    this.activeDropRegions = [];
    this.resetVelocityQueue();

    this.init();
    return this;
  }

  Pep.prototype.init = function () {

    if ( this.options.debug )
      this.buildDebugDiv();

    if ( this.options.disableSelect )
      this.disableSelect();

    if ( this.options.place && !this.options.deferPlacement ) {
      this.positionParent();
      this.placeObject();
    }

    this.ev = {};
    this.pos = {};
    this.subscribe();
  };

  Pep.prototype.subscribe = function () {
    var self = this;

    this.onStartEvent = function(ev){ self.handleStart(ev); };
    this.$el.on(this.startTrigger, this.onStartEvent);

    this.onStartEventOnElementsWithInteraction = function(ev){ ev.ignorePropagation = true; };
    this.$el.on(
      this.startTrigger,
      this.options.elementsWithInteraction,
      this.onStartEventOnElementsWithInteraction
    );

    this.onStopEvents = function(ev) { self.handleStop(ev); };
    this.$document.on(this.stopEvents, this.onStopEvents);

    this.onMoveEvents = function(ev){ self.moveEvent = ev; };
    this.$document.on(this.moveTrigger, this.onMoveEvents);
  };

  Pep.prototype.unsubscribe = function() {
    this.$el.off(this.startTrigger, this.onStartEvent);
    this.$el.off(
      this.startTrigger,
      this.options.elementsWithInteraction,
      this.onStartEventOnElementsWithInteraction
    );
    this.$document.off(this.stopEvents, this.onStopEvents);
    this.$document.off(this.moveTrigger, this.onMoveEvents);
  };

  Pep.prototype.handleStart = function(ev) {

    if (ev.ignorePropagation) return;

    var self = this;

            if ( this.isValidMoveEvent(ev) && !this.disabled ){

              if( !(this.options.ignoreRightClick && ev.which === 3) ) {

                    if ( this.isPointerEventCompatible() && ev.preventManipulation )
                      ev.preventManipulation();

                    ev = this.normalizeEvent(ev);

                    if ( this.options.place && this.options.deferPlacement ) {
                      this.positionParent();
                      this.placeObject();
                    }

                    this.log({ type: 'event', event: ev.type });

                    if ( this.options.hardwareAccelerate && !this.hardwareAccelerated ) {
                      this.hardwareAccelerate();
                      this.hardwareAccelerated = true;
                    }

                    var shouldContinue = this.options.initiate.call(this, ev, this);
                    if ( shouldContinue === false )
                      return;

                    clearTimeout( this.restTimeout );

                    this.$el.addClass( this.options.activeClass );
                    this.removeCSSEasing();

                    this.startX = this.ev.x = ev.pep.x;
                    this.startY = this.ev.y = ev.pep.y;

                    this.initialPosition = this.initialPosition || this.$el.position();

                    this.startEvent = this.moveEvent = ev;

                    this.active     = true;

                    if ( this.options.shouldPreventDefault )
                      ev.preventDefault();

                    if ( !this.options.allowDragEventPropagation )
                      ev.stopPropagation();

                    (function watchMoveLoop(){
                        if ( !self.active ) return;
                        self.handleMove();
                        self.requestAnimationFrame( watchMoveLoop );
                    })();

                    (function watchEasingLoop(){
                        if ( !self.options.easing ) return;
                        if ( self.easing ) self.options.easing.call(self, null, self);
                        self.requestAnimationFrame( watchEasingLoop );
                    })();
              }
            }
  };

  Pep.prototype.handleMove = function() {

            if ( typeof(this.moveEvent) === 'undefined' )
              return;

            var ev      = this.normalizeEvent( this.moveEvent );
            var curX    = window.parseInt(ev.pep.x / this.options.grid[0]) * this.options.grid[0];
            var curY    = window.parseInt(ev.pep.y / this.options.grid[1]) * this.options.grid[1];

            this.addToLIFO( { time: ev.timeStamp, x: curX, y: curY } );

            var dx, dy;

            if ( $.inArray( ev.type, this.startTriggerArray ) > -1  ){
              dx = 0;
              dy = 0;
            } else{
              dx = curX - this.ev.x;
              dy = curY - this.ev.y;
            }

            this.dx   = dx;
            this.dy   = dy;
            this.ev.x = curX;
            this.ev.y = curY;

            if (dx === 0 && dy === 0){
              this.log({ type: 'event', event: '** stopped **' });
              return;
            }

            var initialDx  = Math.abs(this.startX - curX);
            var initialDy  = Math.abs(this.startY - curY);
            if ( !this.started && ( initialDx > this.options.startThreshold[0] || initialDy > this.options.startThreshold[1] ) ){
              this.started = true;
              this.$el.addClass('pep-start');
              this.options.start.call(this, this.startEvent, this);
            }

            this.doMoveTo(dx, dy);

            if ( this.options.droppable ) {
              this.calculateActiveDropRegions();
            }

            var continueDrag = this.options.drag.call(this, ev, this);

            if ( continueDrag === false ) {
              this.resetVelocityQueue();
              return;
            }

            this.log({ type: 'event', event: ev.type });
            this.log({ type: 'event-coords', x: this.ev.x, y: this.ev.y });
            this.log({ type: 'velocity' });
  };

  Pep.prototype.doMoveTo = function(dx, dy) {
            var hash = this.handleConstraint(dx, dy);
            var xOp, yOp;

            if ( typeof this.options.moveTo === 'function') {
              xOp     = ( dx >= 0 ) ? "+=" + Math.abs(dx/this.scale)*this.options.multiplier : "-=" + Math.abs(dx/this.scale)*this.options.multiplier;
              yOp     = ( dy >= 0 ) ? "+=" + Math.abs(dy/this.scale)*this.options.multiplier : "-=" + Math.abs(dy/this.scale)*this.options.multiplier;

              if ( this.options.constrainTo ) {
                xOp = (hash.x !== false) ? hash.x : xOp;
                yOp = (hash.y !== false) ? hash.y : yOp;
              }

              if ( this.options.axis  === 'x' ) yOp = hash.y;
              if ( this.options.axis  === 'y' ) xOp = hash.x;

              this.options.moveTo.call(this, xOp, yOp);
            } else if ( !this.shouldUseCSSTranslation() ){
              xOp     = ( dx >= 0 ) ? "+=" + Math.abs(dx/this.scale)*this.options.multiplier : "-=" + Math.abs(dx/this.scale)*this.options.multiplier;
              yOp     = ( dy >= 0 ) ? "+=" + Math.abs(dy/this.scale)*this.options.multiplier : "-=" + Math.abs(dy/this.scale)*this.options.multiplier;

              if ( this.options.constrainTo ) {
                xOp = (hash.x !== false) ? hash.x : xOp;
                yOp = (hash.y !== false) ? hash.y : yOp;
              }

              if ( this.options.axis  === 'x' ) yOp = hash.y;
              if ( this.options.axis  === 'y' ) xOp = hash.x;

              this.moveTo(xOp, yOp);
            }
            else {

              dx = (dx/this.scale)*this.options.multiplier;
              dy = (dy/this.scale)*this.options.multiplier;

              if ( this.options.constrainTo ) {
                dx = (hash.x === false) ? dx : 0 ;
                dy = (hash.y === false) ? dy : 0 ;
              }

              if ( this.options.axis  === 'x' ) dy = 0;
              if ( this.options.axis  === 'y' ) dx = 0;

              this.moveToUsingTransforms( dx, dy );
            }
  };

  Pep.prototype.handleStop = function(ev) {

            if (!this.active)
              return;

            this.log({ type: 'event', event: ev.type });

            this.active = false;

            this.easing = true;

            this.$el.removeClass('pep-start')
                    .addClass('pep-ease');

            if ( this.options.droppable ) {
              this.calculateActiveDropRegions();
            }

            if ( this.started || (!this.started &&  $.inArray('stop', this.options.callIfNotStarted) > -1 ) ) {
              this.options.stop.call(this, ev, this);
            }

            if (this.options.shouldEase) {
              this.ease(ev, this.started);
            } else {
              this.removeActiveClass();
            }

            if ( this.options.revert && (this.options.revertAfter === 'stop' || !this.options.shouldEase) && ( this.options.revertIf && this.options.revertIf.call(this) ) ) {
              this.revert();
            }

            this.started = false;

            this.resetVelocityQueue();

  };

  Pep.prototype.ease = function(ev, started){

            var pos       = this.$el.position();
            var vel       = this.velocity();
            var dt        = this.dt;
            var x         = (vel.x/this.scale) * this.options.multiplier;
            var y         = (vel.y/this.scale) * this.options.multiplier;

            var hash      = this.handleConstraint(x, y, true);

            if ( this.cssAnimationsSupported() )
              this.$el.css( this.getCSSEaseHash() );

            var xOp = ( vel.x > 0 ) ? "+=" + x : "-=" + Math.abs(x);
            var yOp = ( vel.y > 0 ) ? "+=" + y : "-=" + Math.abs(y);

            if ( this.options.constrainTo ) {
              xOp = (hash.x !== false) ? hash.x : xOp;
              yOp = (hash.y !== false) ? hash.y : yOp;
            }

            if ( this.options.axis  === 'x' ) yOp = "+=0";
            if ( this.options.axis  === 'y' ) xOp = "+=0";

            var jsAnimateFallback = !this.cssAnimationsSupported() || this.options.forceNonCSS3Movement;
            if (typeof this.options.moveTo === 'function') {
              this.options.moveTo.call(this, xOp, yOp);
            } else {
              this.moveTo(xOp, yOp, jsAnimateFallback);
            }

            var self = this;
            this.restTimeout = setTimeout( function(){

              if ( self.options.droppable ) {
                self.calculateActiveDropRegions();
              }

              self.easing = false;

              if ( started || ( !started && $.inArray('rest', self.options.callIfNotStarted) > -1 ) ) {
                self.options.rest.call(self, ev, self);
              }

              if ( self.options.revert && (self.options.revertAfter === 'ease' && self.options.shouldEase) && ( self.options.revertIf && self.options.revertIf.call(self) ) ) {
                self.revert();
              }

              self.removeActiveClass();

            }, this.options.cssEaseDuration );

  };

  Pep.prototype.normalizeEvent = function(ev) {
      ev.pep        = {};

      if ( this.isTouch(ev) ) {

        ev.pep.x      = ev.originalEvent.touches[0].pageX;
        ev.pep.y      = ev.originalEvent.touches[0].pageY;
        ev.pep.type   = ev.type;

      }
      else if ( this.isPointerEventCompatible() || !this.isTouch(ev) ) {

        if ( ev.pageX  ) {
          ev.pep.x      = ev.pageX;
          ev.pep.y      = ev.pageY;
        } else {
          ev.pep.x      = ev.originalEvent.pageX;
          ev.pep.y      = ev.originalEvent.pageY;
        }

        ev.pep.type   = ev.type;

      }

      return ev;
   };

  Pep.prototype.resetVelocityQueue = function() {
    this.velocityQueue = new Array(5);
  };

  Pep.prototype.moveTo = function(x,y, animate) {

    this.log({ type: 'delta', x: x, y: y });
    if ( animate ) {
      this.$el.animate({ top: y, left: x }, 0, 'easeOutQuad', {queue: false});
    } else{
      this.$el.stop(true, false).css({ top: y , left: x });
    }

  };

  Pep.prototype.moveToUsingTransforms = function(x,y) {

    var matrixArray  = this.matrixToArray( this.matrixString() );
    if ( !this.cssX )
      this.cssX = this.xTranslation( matrixArray );

    if ( !this.cssY )
      this.cssY = this.yTranslation( matrixArray );

    this.cssX = this.cssX + x;
    this.cssY = this.cssY + y;

    this.log({ type: 'delta', x: x, y: y });

    matrixArray[4]    = this.cssX;
    matrixArray[5]    = this.cssY;

    this.translation  = this.arrayToMatrix( matrixArray );
    this.transform( this.translation );
  };

  Pep.prototype.transform = function(value) {
    this.$el.css({
        '-webkit-transform': value,
           '-moz-transform': value,
            '-ms-transform': value,
             '-o-transform': value,
                'transform': value  });
  };

  Pep.prototype.xTranslation = function(matrixArray) {
    matrixArray  = matrixArray || this.matrixToArray( this.matrixString() );
    return parseInt(matrixArray[4], 10);
  };

  Pep.prototype.yTranslation = function(matrixArray) {
    matrixArray  = matrixArray || this.matrixToArray( this.matrixString() );
    return parseInt(matrixArray[5], 10);
  };

  Pep.prototype.matrixString = function() {

    var validMatrix = function(o){
      return !( !o || o === 'none' || o.indexOf('matrix') < 0  );
    };

    var matrix = "matrix(1, 0, 0, 1, 0, 0)";

    if ( validMatrix( this.$el.css('-webkit-transform') ) )
      matrix = this.$el.css('-webkit-transform');

    if ( validMatrix( this.$el.css('-moz-transform') ) )
      matrix = this.$el.css('-moz-transform');

    if ( validMatrix( this.$el.css('-ms-transform') ) )
      matrix = this.$el.css('-ms-transform');

    if ( validMatrix( this.$el.css('-o-transform') ) )
      matrix = this.$el.css('-o-transform');

    if ( validMatrix( this.$el.css('transform') ) )
      matrix = this.$el.css('transform');

    return matrix;
  };

  Pep.prototype.matrixToArray = function(str) {
      return str.split('(')[1].split(')')[0].split(',');
  };

  Pep.prototype.arrayToMatrix = function(array) {
      return "matrix(" +  array.join(',')  + ")";
  };

  Pep.prototype.addToLIFO = function(val){
    var arr = this.velocityQueue;
    arr = arr.slice(1, arr.length);
    arr.push(val);
    this.velocityQueue = arr;
  };

  Pep.prototype.velocity = function(){
    var sumX = 0;
    var sumY = 0;

    for ( var i = 0; i < this.velocityQueue.length -1; i++  ){
      if ( this.velocityQueue[i] ){
        sumX        += (this.velocityQueue[i+1].x - this.velocityQueue[i].x);
        sumY        += (this.velocityQueue[i+1].y - this.velocityQueue[i].y);
        this.dt     = ( this.velocityQueue[i+1].time - this.velocityQueue[i].time );
      }
    }

    return { x: sumX*this.options.velocityMultiplier, y: sumY*this.options.velocityMultiplier};
  };

  Pep.prototype.revert = function() {
    if ( this.shouldUseCSSTranslation() ){
      this.moveToUsingTransforms(-this.xTranslation(),-this.yTranslation());
    }

    if (this.options.place) {
      this.moveTo(this.initialPosition.left, this.initialPosition.top);
    }
  };

  Pep.prototype.requestAnimationFrame = function(callback) {
    return  window.requestAnimationFrame        && window.requestAnimationFrame(callback)         ||
            window.webkitRequestAnimationFrame  && window.webkitRequestAnimationFrame(callback)   ||
            window.mozRequestAnimationFrame     && window.mozRequestAnimationFrame(callback)      ||
            window.oRequestAnimationFrame       && window.mozRequestAnimationFrame(callback)      ||
            window.msRequestAnimationFrame      && window.msRequestAnimationFrame(callback)       ||
            window.setTimeout(callback, 1000 / 60);
  };

  Pep.prototype.positionParent = function() {

    if ( !this.options.constrainTo || this.parentPositioned )
      return;

    this.parentPositioned = true;

    if ( this.options.constrainTo === 'parent' ) {
      this.$container.css({ position: 'relative' });
    } else if ( this.options.constrainTo === 'window'             &&
                this.$container.get(0).nodeName !== "#document"   &&
                this.$container.css('position') !== 'static' )
    {
      this.$container.css({ position: 'static' });
    }

  };

  Pep.prototype.placeObject = function() {

    if ( this.objectPlaced )
      return;

    this.objectPlaced = true;

    this.offset = (this.options.constrainTo === 'parent' || this.hasNonBodyRelative() ) ?
                    this.$el.position() : this.$el.offset();

    if ( parseInt( this.$el.css('left'), 10 ) )
      this.offset.left = this.$el.css('left');

    if (typeof this.options.startPos.left === "number")
        this.offset.left = this.options.startPos.left;

    if ( parseInt( this.$el.css('top'), 10 ) )
      this.offset.top = this.$el.css('top');

    if (typeof this.options.startPos.top === "number")
        this.offset.top = this.options.startPos.top;

    if ( this.options.removeMargins )
      this.$el.css({margin: 0});

    this.$el.css({
      position:   'absolute',
      top:        this.offset.top,
      left:       this.offset.left
    });

  };

  Pep.prototype.hasNonBodyRelative = function() {
    return this.$el.parents().filter(function() {
        var $this = $(this);
        return $this.is('body') || $this.css('position') === 'relative';
    }).length > 1;
  };

  Pep.prototype.setScale = function(val) {
    this.scale = val;
  };

  Pep.prototype.setMultiplier = function(val) {
    this.options.multiplier = val;
  };

  Pep.prototype.removeCSSEasing = function() {
    if ( this.cssAnimationsSupported() )
      this.$el.css( this.getCSSEaseHash(true) );
  };

  Pep.prototype.disableSelect = function() {

    this.$el.css({
      '-webkit-touch-callout' : 'none',
        '-webkit-user-select' : 'none',
         '-khtml-user-select' : 'none',
           '-moz-user-select' : 'none',
            '-ms-user-select' : 'none',
                'user-select' : 'none'
    });

  };

  Pep.prototype.removeActiveClass = function() {
    this.$el.removeClass( [this.options.activeClass, 'pep-ease'].join(' ') );
  };

  Pep.prototype.handleConstraint = function(dx, dy, accountForTranslation) {
    var pos               = this.$el.position();
    this.pos.x            = pos.left;
    this.pos.y            = pos.top;

    var hash              = { x: false, y: false };

    var upperYLimit, upperXLimit, lowerXLimit, lowerYLimit;

    this.log({ type: "pos-coords", x: this.pos.x, y: this.pos.y});

    if ( $.isArray( this.options.constrainTo ) ) {

      if ( this.options.constrainTo[3] !== undefined && this.options.constrainTo[1] !== undefined ) {
        upperXLimit     = this.options.constrainTo[1] === false ?  Infinity : this.options.constrainTo[1];
        lowerXLimit     = this.options.constrainTo[3] === false ? -Infinity : this.options.constrainTo[3];
      }
      if ( this.options.constrainTo[0] !== false && this.options.constrainTo[2] !== false ) {
        upperYLimit       = this.options.constrainTo[2] === false ?  Infinity : this.options.constrainTo[2];
        lowerYLimit       = this.options.constrainTo[0] === false ? -Infinity : this.options.constrainTo[0];
      }

      if ( this.pos.x + dx < lowerXLimit)     hash.x = lowerXLimit;
      if ( this.pos.y + dy < lowerYLimit)     hash.y = lowerYLimit;

    } else if ( typeof this.options.constrainTo === 'string' ) {
      lowerXLimit       = 0;
      lowerYLimit       = 0;
      upperXLimit       = this.$container.width()  - (this.options.useBoundingClientRect ? this.$el[0].getBoundingClientRect().width : this.$el.outerWidth());
      upperYLimit       = this.$container.height() - (this.options.useBoundingClientRect ? this.$el[0].getBoundingClientRect().height : this.$el.outerHeight());

      if ( this.pos.x + dx < 0 )              hash.x = 0;
      if ( this.pos.y + dy < 0 )              hash.y = 0;
    }

    if ( this.pos.x + dx > upperXLimit )    hash.x = upperXLimit;
    if ( this.pos.y + dy > upperYLimit )    hash.y = upperYLimit;

    if ( this.shouldUseCSSTranslation() && accountForTranslation ){
      if (hash.x === lowerXLimit && this.xTranslation() ) hash.x = lowerXLimit - this.xTranslation();
      if (hash.x === upperXLimit && this.xTranslation() ) hash.x = upperXLimit - this.xTranslation();

      if (hash.y === lowerYLimit && this.yTranslation() ) hash.y = lowerYLimit - this.yTranslation();
      if (hash.y === upperYLimit && this.yTranslation() ) hash.y = upperYLimit - this.yTranslation();
    }

    return hash;
  };

  Pep.prototype.getCSSEaseHash = function(reset){
    if ( typeof(reset) === 'undefined' ) reset = false;

    var cssEaseString;
    if (reset){
      cssEaseString = '';
    } else if ( this.CSSEaseHash ) {
      return this.CSSEaseHash;
    } else {
      cssEaseString = ['all', this.options.cssEaseDuration + 'ms', this.options.cssEaseString].join(' ');
    }

    return {
                  '-webkit-transition'   : cssEaseString,
                     '-moz-transition'   : cssEaseString,
                      '-ms-transition'   : cssEaseString,
                       '-o-transition'   : cssEaseString,
                          'transition'   : cssEaseString
          };
  };

  Pep.prototype.calculateActiveDropRegions = function() {
    var self = this;
    this.activeDropRegions.length = 0;

    $.each( $(this.options.droppable), function(idx, el){
      var $el = $(el);
      if ( self.isOverlapping($el, self.$el) ){
        $el.addClass(self.options.droppableActiveClass);
        self.activeDropRegions.push($el);
      } else {
        $el.removeClass(self.options.droppableActiveClass);
      }
    });

  };

  Pep.prototype.isOverlapping = function($a,$b) {

    if ( this.options.overlapFunction ) {
      return this.options.overlapFunction($a,$b);
    }

    var rect1 = $a[0].getBoundingClientRect();
    var rect2 = $b[0].getBoundingClientRect();

    return !( rect1.right   < rect2.left  ||
              rect1.left    > rect2.right ||
              rect1.bottom  < rect2.top   ||
              rect1.top     > rect2.bottom  );
  };

  Pep.prototype.isTouch = function(ev){
    return ev.type.search('touch') > -1;
  };

  Pep.prototype.isPointerEventCompatible = function() {
    return ("MSPointerEvent" in window);
  };

  Pep.prototype.applyMSDefaults = function(first_argument) {
    this.$el.css({
        '-ms-touch-action' :    'none',
        'touch-action' :        'none',
        '-ms-scroll-chaining':  'none',
        '-ms-scroll-limit':     '0 0 0 0'
    });
  };

  Pep.prototype.isValidMoveEvent = function(ev){
    return ( !this.isTouch(ev) || ( this.isTouch(ev) && ev.originalEvent && ev.originalEvent.touches && ev.originalEvent.touches.length === 1 ) );
  };

  Pep.prototype.shouldUseCSSTranslation = function() {

    if ( this.options.forceNonCSS3Movement )
      return false;

    if ( typeof(this.useCSSTranslation) !== "undefined" )
      return this.useCSSTranslation;

    var useCSSTranslation = false;

    if ( !this.options.useCSSTranslation || ( typeof(Modernizr) !== "undefined" && !Modernizr.csstransforms)){
      useCSSTranslation = false;
    }
    else{
      useCSSTranslation = true;
    }

    this.useCSSTranslation =  useCSSTranslation;
    return useCSSTranslation;
  };

  Pep.prototype.cssAnimationsSupported = function() {

    if ( typeof(this.cssAnimationsSupport) !== "undefined" ){
      return this.cssAnimationsSupport;
    }

    if ( ( typeof(Modernizr) !== "undefined" && Modernizr.cssanimations) ){
      this.cssAnimationsSupport = true;
      return true;
    }

    var animation = false,
        elm = document.createElement('div'),
        animationstring = 'animation',
        keyframeprefix = '',
        domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
        pfx  = '';

    if( elm.style.animationName ) { animation = true; }

    if( animation === false ) {
      for( var i = 0; i < domPrefixes.length; i++ ) {
        if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) {
          pfx = domPrefixes[ i ];
          animationstring = pfx + 'Animation';
          keyframeprefix = '-' + pfx.toLowerCase() + '-';
          animation = true;
          break;
        }
      }
    }

    this.cssAnimationsSupport = animation;
    return animation;
  };

  Pep.prototype.hardwareAccelerate = function() {
    this.$el.css({
      '-webkit-perspective':          1000,
      'perspective':                  1000,
      '-webkit-backface-visibility':  'hidden',
      'backface-visibility':          'hidden'
    });
   };

  Pep.prototype.getMovementValues = function() {
    return { ev: this.ev, pos: this.pos, velocity: this.velocity() };
   };

  Pep.prototype.buildDebugDiv = function() {

    var $debugDiv;
    if ( $('#pep-debug').length === 0 ){
      $debugDiv = $('<div></div>');
      $debugDiv
        .attr('id', 'pep-debug')
        .append("<div style='font-weight:bold; background: red; color: white;'>DEBUG MODE</div>")
        .append("<div id='pep-debug-event'>no event</div>")
        .append("<div id='pep-debug-ev-coords'>event coords: <span class='pep-x'>-</span>, <span class='pep-y'>-</span></div>")
        .append("<div id='pep-debug-pos-coords'>position coords: <span class='pep-x'>-</span>, <span class='pep-y'>-</span></div>")
        .append("<div id='pep-debug-velocity'>velocity: <span class='pep-x'>-</span>, <span class='pep-y'>-</span></div>")
        .append("<div id='pep-debug-delta'>&Delta; movement: <span class='pep-x'>-</span>, <span class='pep-y'>-</span></div>")
        .css({
          position:   'fixed',
          bottom:     5,
          right:      5,
          zIndex:     99999,
          textAlign:  'right',
          fontFamily: 'Arial, sans',
          fontSize:   10,
          border:     '1px solid #DDD',
          padding:    '3px',
          background: 'white',
          color:      '#333'
        });
    }

    var self = this;
    setTimeout(function(){
      self.debugElements = {
        $event:      $("#pep-debug-event"),
        $velocityX:  $("#pep-debug-velocity .pep-x"),
        $velocityY:  $("#pep-debug-velocity .pep-y"),
        $dX:         $("#pep-debug-delta .pep-x"),
        $dY:         $("#pep-debug-delta .pep-y"),
        $evCoordsX:  $("#pep-debug-ev-coords .pep-x"),
        $evCoordsY:  $("#pep-debug-ev-coords .pep-y"),
        $posCoordsX: $("#pep-debug-pos-coords .pep-x"),
        $posCoordsY: $("#pep-debug-pos-coords .pep-y")
      };
    }, 0);

    $('body').append( $debugDiv );
  };

  Pep.prototype.log = function(opts) {
    if ( !this.options.debug ) return;

    switch (opts.type){
    case "event":
      this.debugElements.$event.text(opts.event);
      break;
    case "pos-coords":
      this.debugElements.$posCoordsX.text(opts.x);
      this.debugElements.$posCoordsY.text(opts.y);
      break;
    case "event-coords":
      this.debugElements.$evCoordsX.text(opts.x);
      this.debugElements.$evCoordsY.text(opts.y);
      break;
    case "delta":
      this.debugElements.$dX.text(opts.x);
      this.debugElements.$dY.text(opts.y);
      break;
    case "velocity":
      var vel = this.velocity();
      this.debugElements.$velocityX.text( Math.round(vel.x) );
      this.debugElements.$velocityY.text( Math.round(vel.y) );
      break;
    }
  };

  Pep.prototype.toggle = function(on) {
    if ( typeof(on) === "undefined"){
      this.disabled = !this.disabled;
    }
    else {
      this.disabled = !on;
    }
  };

  $.extend($.easing,
  {
    easeOutQuad: function (x, t, b, c, d) {
      return -c *(t/=d)*(t-2) + b;
    },
    easeOutCirc: function (x, t, b, c, d) {
      return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
    },
    easeOutExpo: function (x, t, b, c, d) {
      return (t===d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
    }
  });

  $.fn[pluginName] = function ( options ) {
    return this.each(function () {
      if (!$.data(this, 'plugin_' + pluginName)) {
        var pepObj = new Pep( this, options );
        $.data(this, 'plugin_' + pluginName, pepObj);
        $.pep.peps.push(pepObj);
      }
    });
  };

  $.pep = {};
  $.pep.peps = [];
  $.pep.toggleAll = function(on){
    $.each(this.peps, function(index, pepObj){
      pepObj.toggle(on);
    });
  };

  $.pep.unbind = function($obj){
    var pep = $obj.data('plugin_' + pluginName);

    if ( typeof pep === 'undefined' )
      return;

    pep.toggle(false);
    pep.unsubscribe();
    $obj.removeData('plugin_' + pluginName);

  };

}(jQuery, window));
