/**
 * ---------------------------- DRAGEND JS -------------------------------------
 *
 * Version: 0.2.0_rc1
 * https://github.com/Stereobit/dragend
 * Copyright (c) 2012 Tobias Otte, t@stereob.it
 *
 * Licensed under MIT-style license:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

 ;(function( window ) {
  "use strict";

  function init( $, Hammer ) {

    // Welcome To dragend JS
    // =====================
    //
    // dragend JS is a swipe plugin for jQuery (http://jquery.com/). It's open
    // source (https://github.com/Stereobit/dragend) and uses hammer.js
    // (http://eightmedia.github.com/hammer.js/) for observing multi-touch
    // gestures. You can use dragend JS in fullscreen or boxed mode.
    //
    // The current version is 0.2.0_rc2
    //
    // Usage
    // =====================
    //
    // To activate dragend JS just call the function on a jQuery element
    //
    // $("#swipe-container").dragend();
    //
    // You could rather pass in a options object or a string to bump on of the
    // following behaviors: "up", "down", "left", "right" for swiping in one of
    // these directions, "page" with the page number as second argument to go to a
    // explicit page and without any value to go to the first page
    //
    // Settings
    // =====================
    //
    // You can use the following options:
    //
    // * pageClass: classname selector for all elments that should provide a page
    // * direction: "horizontal" or "vertical"
    // * minDragDistance: minuimum distance (in pixel) the user has to drag
    //   to trigger swip
    // * scribe: pixel value for a possible scribe
    // * onSwipeStart: callback function before the animation
    // * onSwipeEnd: callback function after the animation
    // * onDrag: callback on drag
    // * onDragEnd: callback on dragend
    // * borderBetweenPages: if you need space between pages add a pixel value
    // * duration
    // * hammerSettings

    var

      noop = function() {},

      // Default setting
      defaultSettings = {
        pageClass          : "dragend-page",
        direction          : "horizontal",
        minDragDistance    : "40",
        onSwipeStart       : noop,
        onSwipeEnd         : noop,
        onDrag             : noop,
        onDragEnd          : noop,
        keyboardNavigation : false,
        itemsInPage        : 1,
        scribe             : 0,
        borderBetweenPages : 0,
        duration           : 300,
        hammerSettings     : {
          drag_min_distance: 0,
          css_hacks        : false,
          prevent_default  : true
        }
      },

      keycodes = {
        37: "left",
        38: "up",
        39: "right",
        40: "down"
      },

      errors = {
        handling: "Dragend JS detected some problems with the event handling. Maybe the user-drag CSS attribute on images can help",
        pages: "No pages found",
        hammer: "It seems like Hammer JS is not be included before dragend JS"
      },

      containerStyles = {
        overflow: "hidden",
        padding : 0
      },

      setStyles = function( element, styles ) {

        var property,
            value;

        for ( property in styles ) {

          if ( styles.hasOwnProperty(property) ) {
            value = styles[property];

            switch ( property ) {
              case "height":
              case "width":
              case "marginLeft":
              case "marginTop":
                value += "px";
            }

            element.style[property] = value;

          }

        }

      },

      extend = function( destination, source ) {

        var property;

        for ( property in source ) {
          destination[property] = source[property];
        }

        return destination;

      },

      proxy = function( fn, context ) {

        return function() {
          return fn.apply( context, Array.prototype.slice.call(arguments) );
        };

      },

      getElementsByClassName = function( className, root ) {
        var elements = [],
            allElements;

        if ( document.querySelector && document.querySelectorAll ) {
           elements = root.getElementsByClassName( className );
        } else {
          allElements = root.getElementsByTagName('*');

          for ( var i = 0; i < allElements.length; i++ ) {
            if ((' ' + allElements[i].className + ' ').indexOf(' ' + className +' ') > -1 ) {
              elements.push( allElements[i] );
            }
          }

        }

        return Array.prototype.slice.call( elements );
      },

      animate = function( element, propery, to, speed, callback ) {

        var start = + new Date(),
            from = parseInt(element.style[propery], 10),

            timer = setInterval(function() {

            var timeGone = + new Date() - start,
                value;

            if (timeGone >= speed) {

              value = to;
              callback();

              clearInterval( timer );

            } else {
              value = Math.round((( (to - from) * (Math.floor((timeGone / speed) * 100) / 100) ) + from));
            }

            element.style[propery] = value + "px";

          }, 5);

      },

      supports = (function() {
         var div = document.createElement('div'),
             vendors = 'Khtml Ms O Moz Webkit'.split(' '),
             len = vendors.length;

         return function( prop ) {
            if ( prop in div.style ) return true;

            prop = prop.replace(/^[a-z]/, function(val) {
               return val.toUpperCase();
            });

            while( len-- ) {
               if ( vendors[len] + prop in div.style ) {
                  // browser supports box-shadow. Do what you need.
                  // Or use a bang (!) to test if the browser doesn't.
                  return true;
               }
            }
            return false;
         };
      })(),

      Dragend = function( container, settings ) {
        var defaultSettingsCopy = extend( {}, defaultSettings );

        this.settings      = extend( defaultSettingsCopy, settings );
        this.container     = container;
        this.pageContainer = document.createElement( "div" );
        this.scrollBorder  = { x: 0, y: 0 };
        this.page          = 0;
        this.preventScroll = false;
        this.pageCssProperties = {
          margin: 0
        };

        this.pageContainer.innerHTML = this.container.innerHTML;
        this.container.innerHTML = null;
        this.container.appendChild( this.pageContainer );

        // Initialisation

        setStyles(this.container, containerStyles);
        this._observe();

        // Give the DOM some time to update ...
        window.setTimeout( proxy(function() {
            this.updateInstance( settings );
        }, this), 10 );

      },

      // ### Scroll translate
      //
      // Animation when translate is supported
      //
      // Takes:
      // x and y values to go with

      _scrollTransform = function( coordinates ) {
        if ( this.settings.direction === "horizontal" ) {
          setStyles( this.pageContainer, {
            "-webkit-transform": "translateX(" + coordinates.x + "px)",
            "-moz-transform": "translateX(" + coordinates.x + "px)",
            "-ms-transform": "translateX(" + coordinates.x + "px)",
            "-0-transform": "translateX(" + coordinates.x + "px)",
            "transform": "translateX(" + coordinates.x + "px)"
          });
        } else if (this.settings.direction === "vertical" ) {
          setStyles( this.pageContainer, {
            "-webkit-transform": "translateY(" + coordinates.y + "px)",
            "-moz-transform": "translateY(" + coordinates.y + "px)",
            "-ms-transform": "translateY(" + coordinates.y + "px)",
            "-o-transform": "translateY(" + coordinates.y + "px)",
            "transform": "translateY(" + coordinates.y + "px)"
          });
        }
      },

      // ### Animated scroll with translate support

      _animateScrollTransform = function() {
        setStyles( this.pageContainer, {
          "-webkit-transition": "-webkit-transform " + this.settings.duration + "ms ease-out",
          "-moz-transition": "-moz-transform " + this.settings.duration + "ms ease-out",
          "-ms-transition": "-ms-transform " + this.settings.duration + "ms ease-out",
          "-o-transition": "-o-transform " + this.settings.duration + "ms ease-out",
          "transition": "transform " + this.settings.duration + "ms ease-out"
        });

        this._scroll({
          x: - this.scrollBorder.x,
          y: - this.scrollBorder.y
        });

        window.setTimeout( proxy(afterScrollTranslate, this), this.settings.duration );
      },

      afterScrollTranslate = function() {
        this._onSwipeEnd();
        setStyles( this.pageContainer, {
          "-webkit-transition": "-webkit-transform 0"
        });
      },

      // ### Scroll fallback
      //
      // Animation lookup table  when translate isn't supported
      //
      // Takes:
      // x and y values to go with

      _scrollWithoutTranslate = function( coordinates ) {

        if ( this.settings.direction === "horizontal") {
          setStyles(this.pageContainer, {
            "marginLeft": coordinates.x
          });
        } else if ( this.settings.direction === "vertical" ) {
          setStyles(this.pageContainer, {
            "marginTop": coordinates.y
          });
        }
      },

      // ### Animated scroll without translate support

      _animateScrollWithoutTranslate = function() {
        var property,
            value;

        this.activeElement = this.pages[this.page * this.settings.itemsInPage];

        if ( this.settings.direction === "horizontal") {
            property = "marginLeft";
            value = - this.scrollBorder.x;
        } else if ( this.settings.direction === "vertical" ) {
            property = "marginTop";
            value = - this.scrollBorder.y;
        }

        animate( this.pageContainer, property, value, this.settings.duration, proxy( this._onSwipeEnd, this ));

      };

    // ### Check translate support
    ( function() {

      var scroll,
          animateScroll;

      if ( supports('transform') ) {
        scroll = _scrollTransform;
        animateScroll = _animateScrollTransform;
      } else {
        scroll = _scrollWithoutTransform;
        animateScroll = _animateScrollWithoutTranslate;
      }

      extend( Dragend.prototype, {
        "_scroll": scroll,
        "_animateScroll": animateScroll
      });

    })();


    extend(Dragend.prototype, {

      // Private functions
      // =================

      // ### Overscroll lookup table
      //
      // Checks if its the last or first page to slow down the scrolling if so
      //
      // Takes:
      // Drag event

      _checkOverscroll: function( direction, x, y ) {
        var coordinates = {
          x: x,
          y: y,
          overscroll: true
        };

        switch ( direction ) {

          case "right":
            if ( !this.scrollBorder.x && this._checkGestureDirection( direction ) ) {
              Math.round( coordinates.x = (x - this.scrollBorder.x) / 5 );
              return coordinates;
            }
            break;

          case "left":
            if ( (this.pagesCount - 1) * this.pageDimentions.width <= this.scrollBorder.x ) {
              coordinates.x = Math.round( - ((Math.ceil(this.pagesCount) - 1) * (this.pageDimentions.width + this.settings.borderBetweenPages)) + x / 5 );
              return coordinates;
            }
            break;

          case "down":
            if ( !this.scrollBorder.y && this._checkGestureDirection( direction )) {
              coordinates.y = Math.round( (y - this.scrollBorder.y) / 5 );
              return coordinates;
            }
            break;

          case "up":
            if ( (this.pagesCount - 1) * this.pageDimentions.height <= this.scrollBorder.y ) {
              coordinates.y = Math.round( - ((Math.ceil(this.pagesCount) - 1) * (this.pageDimentions.height + this.settings.borderBetweenPages)) + y / 5 );
              return coordinates;
            }
            break;
        }

        return {
          x: x - this.scrollBorder.x,
          y: y - this.scrollBorder.y,
          overscroll: false
        };
      },

      // Observe
      //
      // Sets the observers for drag, resize and key events

      _observe: function() {
        if (!Hammer) {
          throw new Error(errors.hammer);
        }

        var hammer = new Hammer(this.container, this.settings.hammerSettings);

        hammer.on("drag", proxy( this._onDrag, this ))
              .on( "dragend", proxy( this._onDragend, this ));

        Hammer.event.bindDom(window, "resize", proxy( this._sizePages, this ));

        if ( this.settings.keyboardNavigation ) {
          Hammer.event.bindDom(document.body, "keydown", proxy( this._onKeydown, this ));
        }

      },

      _onDrag: function( event ) {
        var gesture,
            coordinates;

        event.stopPropagation();

        if ( event.gesture ) {
          gesture = event.gesture;
          coordinates = this._checkOverscroll( gesture.direction, gesture.deltaX, gesture.deltaY );
          this.settings.onDrag.call( this, this.activeElement, gesture, coordinates.overscroll );
        } else {
          throw new Error(errors.handling);
        }

        if ( !this.preventScroll ) {
          this._scroll( coordinates );
        }

      },

      _onDragend: function( event ) {
        var gesture;

          if (event.preventDefault) {
            event.preventDefault();
          } else if (event.preventManipulation) {
            event.preventManipulation();
          }

        if (event.gesture) {
          gesture = event.gesture;
        } else {
          throw new Error(errors.handling);
        }

        if ( event.gesture.distance > this.settings.minDragDistance && this._checkGestureDirection( gesture.direction )) {
          this.swipe( gesture.direction );
        } else {
          this._scrollToPage();
        }

        this.settings.onDragEnd.call( this, this.container, this.activeElement, this.page );
      },

      _onKeydown: function( event ) {
        var direction = keycodes[event.keyCode];

        if ( direction ) {
          this._scrollToPage(direction);
        }
      },

      _checkGestureDirection: function( direction ) {
        if (((direction === "left" || direction === "right") && this.settings.direction === "horizontal") ||
            ((direction === "up" || direction === "down") && this.settings.direction === "vertical") ) {
          return true;
        }
      },

      setHorizontalContainerCssValues: function() {
        extend( this.pageCssProperties, {
          "cssFloat" : "left",
          "overflowY": "auto",
          "overflowX": "hidden",
          "padding"  : 0,
          "display"  : "block"
        });

        setStyles(this.pageContainer, {
          "overflow"                   : "hidden",
          "width"                      : (this.pageDimentions.width + this.settings.borderBetweenPages) * this.pagesCount,
          "boxSizing"                  : "content-box",
          "-webkit-backface-visibility": "hidden",
          "-webkit-perspective"        : 1000,
          "margin"                     : 0,
          "padding"                    : 0
        });
      },

      setVerticalContainerCssValues: function() {
        extend( this.pageCssProperties, {
          "overflow": "hidden",
          "padding" : 0,
          "display" : "block"
        });

        setStyles(this.pageContainer, {
          "padding-bottom"              : this.settings.scribe,
          "boxSizing"                   : "content-box",
          "-webkit-backface-visibility" : "hidden",
          "-webkit-perspective"         : 1000,
          "margin"                      : 0
        });
      },

      setContainerCssValues: function(){
        switch ( this.settings.direction ) {
          case "horizontal":
            this.setHorizontalContainerCssValues();
            break;

          case "vertical":
            this.setVerticalContainerCssValues();
            break;
        }
      },

      // ### Calculate page dimentions
      //
      // Updates the page dimentions values

      _setPageDimentions: function() {
        var width  = this.container.offsetWidth,
            height = this.container.offsetHeight;

        if ( this.settings.direction === "horizontal" ) {
          width = width - parseInt( this.settings.scribe, 10 );
        } else {
          height = height - parseInt( this.settings.scribe, 10 );
        }

        this.pageDimentions = {
          width : width,
          height: height
        };

      },

      // ### Size pages

      _sizePages: function() {
        var pagesCount = this.pages.length;

        this._setPageDimentions();

        this.setContainerCssValues();

        if ( this.settings.direction === "horizontal" ) {
          extend( this.pageCssProperties, {
            height: this.pageDimentions.height,
            width : this.pageDimentions.width / this.settings.itemsInPage
          });
        } else {
          extend( this.pageCssProperties, {
            height: this.pageDimentions.height / this.settings.itemsInPage,
            width : this.pageDimentions.width
          });
        }

        for (var i = 0; i < pagesCount; i++) {
          setStyles(this.pages[i], this.pageCssProperties);
        }

      },

      // ### Callculate new page
      //
      // Update global values for specific swipe action
      //
      // Takes direction and, if specific page is used the pagenumber

      _calcNewPage: function(direction, pageNumber) {
        switch ( direction ) {
          case "up":
            if ( this.page < this.pagesCount - 1 ) {
              this.scrollBorder.y = this.scrollBorder.y + this.pageDimentions.height + this.settings.borderBetweenPages;
              this.page++;
            }
            break;

          case "down":
            if ( this.page > 0 ) {
              this.scrollBorder.y = this.scrollBorder.y - this.pageDimentions.height - this.settings.borderBetweenPages;
              this.page--;
            }
            break;

          case "left":
            if ( this.page < this.pagesCount - 1 ) {
              this.scrollBorder.x = this.scrollBorder.x + this.pageDimentions.width + this.settings.borderBetweenPages;
              this.page++;
            }
            break;

          case "right":
            if ( this.page > 0 ) {
              this.scrollBorder.x = this.scrollBorder.x - this.pageDimentions.width - this.settings.borderBetweenPages;
              this.page--;
            }
            break;

          case "page":
            switch ( this.settings.direction ) {
              case "horizontal":
                this.scrollBorder.x = (this.pageDimentions.width + this.settings.borderBetweenPages) * pageNumber;
                break;

              case "vertical":
                this.scrollBorder.y = (this.pageDimentions.height + this.settings.borderBetweenPages) * pageNumber;
                break;
            }
            this.page = pageNumber;
            break;

          default:
            this.scrollBorder.y = 0;
            this.scrollBorder.x = 0;
            this.page           = 0;
            break;
        }
      },

      // ### On swipe end
      //
      // Function called after the scroll animation ended

      _onSwipeEnd: function() {
        this.preventScroll = false;

        this.activeElement = this.pages[this.page * this.settings.itemsInPage];

        // Call onSwipeEnd callback function
        this.settings.onSwipeEnd.call( this, this.container, this.activeElement, this.page);
      },

      // Jump to page
      //
      // Jumps without a animantion to specific page. The page number is only
      // necessary for the specific page direction
      //
      // Takes:
      // Direction and pagenumber

      _jumpToPage: function( options, pageNumber ) {

        if ( options ) {
          this._calcNewPage( options, pageNumber );
        }

        this._scroll({
          x: - this.scrollBorder.x,
          y: - this.scrollBorder.y
        });
      },

      // Scroll to page
      //
      // Scrolls with a animantion to specific page. The page number is only necessary
      // for the specific page direction
      //
      // Takes:
      // Direction and pagenumber

      _scrollToPage: function( options, pageNumber ) {
        this.preventScroll = true;

        if ( options ) this._calcNewPage( options, pageNumber );
        this._animateScroll();
      },

      // Public functions
      // ================

      swipe: function( direction ) {
        // Call onSwipeStart callback function
        this.settings.onSwipeStart.call( this, this.container, this.activeElement, this.page );
        this._scrollToPage( direction );
      },

      updateInstance: function( settings ) {

        settings = settings || {};

        if ( typeof settings === "object" ) extend( this.settings, settings );

        this.pages = getElementsByClassName(this.settings.pageClass, this.pageContainer);

        if (this.pages.length) {
          this.pagesCount = this.pages.length / this.settings.itemsInPage;
        } else {
          throw new Error(errors.pages);
        }

        this.activeElement = this.pages[this.page * this.settings.itemsInPage];
        this._sizePages();

        if ( settings.jumpToPage ) this.jumpToPage( settings.jumpToPage );
      },

      scrollToPage: function( page ) {
        this._scrollToPage( "page", page - 1);
      },

      jumpToPage: function( page ) {
        this._jumpToPage( "page", page - 1);
      }

    });

    if ( $ ) {

        // Register jQuery plugin
        $.fn.dragend = function( settings ) {

          settings = settings || {};

          this.each(function() {
            var instance = $(this).data( "dragend" );

            // check if instance already created
            if ( instance ) {
              instance.updateInstance( settings );
              if ( settings.jumpToPage ) instance.jumpToPage( settings.jumpToPage );
            } else {
              instance = new Dragend( this, settings );
              $(this).data( "dragend", instance );
            }

            if ( settings.scrollToPage ) instance.scrollToPage( settings.scrollToPage );

            // check if should trigger swipe
            if ( typeof settings === "string" ) instance.swipe( settings );

          });

          // jQuery functions should always return the intance
          return this;
        };

    }

    return Dragend;

  }

  if ( typeof define == 'function' && typeof define.amd == 'object' && define.amd ) {
      define( ["jquery", "hammer"], function( jquery, hammer ) {
        return init( jquery, hammer );
      } );
  } else {
      window.Dragend = init( window.jQuery || window.Zepto, window.Hammer );
  }

})( window );