/*
Ascensor.js 
version: 1.8.21 (2015-03-28)
description: Ascensor is a jquery plugin which aims to train and adapt content according to an elevator system
repository: https://github.com/kirkas/Ascensor.js
license: BSD
author: Léo Galley <contact@kirkas.ch>
*/
(function($, window, document, undefined) {
  var pluginName = 'ascensor';


  /* Default settings */
  var defaults = {
    ascensorFloorName: false,
    childType: 'div',
    windowsOn: 0,
    direction: 'y',
    loop: false,
    width: '100%',
    height: '100%',
    time: 250,
    easing: 'linear',
    keyNavigation: true,
    queued: false,
    jump: false,
    ready: false,
    swipeNavigation: 'mobile-only',
    swipeVelocity: 0.7,
    wheelNavigation: false,
    wheelNavigationDelay: 40,
  };

  /* Plugin instance */
  function Plugin(element, options) {
    this.element = element;
    this.options = $.extend({}, defaults, options);
    this._defaults = defaults;
    this._name = pluginName;
    this.init();
  }

  // From https://gist.github.com/lorenzopolidori/3794226
  function has3d() {
    var el = document.createElement('p'),
      support3D,
      transforms = {
        'webkitTransform': '-webkit-transform',
        'OTransform': '-o-transform',
        'msTransform': '-ms-transform',
        'MozTransform': '-moz-transform',
        'transform': 'transform'
      };
    document.body.insertBefore(el, null);
    for (var t in transforms) {
      if (el.style[t] !== undefined) {
        el.style[t] = 'translate3d(1px,1px,1px)';
        support3D = window.getComputedStyle(el).getPropertyValue(transforms[t]);
      }
    }
    document.body.removeChild(el);
    return (support3D !== undefined && support3D.length > 0 && support3D !== 'none');
  }


  /* Add 'indexOf' helper in case missing (IE8) */
  if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(elt /*, from*/ ) {
      var len = this.length >>> 0;
      var from = Number(arguments[1]) || 0;
      from = (from < 0) ? Math.ceil(from) : Math.floor(from);
      if (from < 0) from += len;
      for (; from < len; from++) {
        if (from in this && this[from] === elt) return from;
      }
      return -1;
    };
  }

  /* Array helper */
  function existInArray(array, item) {
    var index = array.indexOf(item);
    if (index !== -1) return true;
    return false;
  }

  /* Value helper */
  function isFalse(value) {
    return value === false;
  }

  function isTrue(value) {
    return value === true;
  }

  function isNumber(value) {
    return typeof(value) === 'number';
  }

  function isString(value) {
    return typeof(value) === 'string';
  }

  function isFunction(value) {
    return typeof(value) === 'function';
  }

  function isObject(value) {
    return typeof(value) === 'object';
  }


  /* Plugin start */
  Plugin.prototype = {

    /* Initialization of plugin */
    init: function() {

      var self = this;

      // Constant helper
      this.AXIS_X = 1;
      this.AXIS_Y = 0;

      this.dataAttributeMap = {
        "next": "ascensor-next",
        "prev": "ascensor-prev",
        "down": "ascensor-down",
        "up": "ascensor-up",
        "left": "ascensor-left",
        "right": "ascensor-right"
      };

      // Setup global variable - selector 
      this.node = $(this.element);
      this.nodeChildren = this.node.children(this.options.childType);
      this.floorActive = (isNumber(this._getFloorFromHash())) ? this._getFloorFromHash() : this.options.windowsOn;

      this.NH = this.node.height();
      this.NW = this.node.width();

      // Setup global variable - helper
      var androidCompatible = true;
      var version = navigator.userAgent.match(/Android\s+([\d\.]+)/);
      if (version) androidCompatible = parseFloat(version[1]) > 3;


      this.directionIsArray = isObject(this.options.direction);
      this.supportTransform = has3d() && androidCompatible;



      // Check if floor name array & node children length match
      if (isObject(this.options.ascensorFloorName) && this.options.ascensorFloorName.length < this.nodeChildren.length) {
        return this._emitConsoleMessage("error", "floors total (" + this.nodeChildren.length + ") & floor name array length (" + this.options.ascensorFloorName.length + ") don't match");
      }

      // Check if direction array & node children length match
      if (this.directionIsArray && this.options.direction.length < this.nodeChildren.length) {
        return this._emitConsoleMessage("error", "floors total (" + this.nodeChildren.length + ") & direction array lenght (" + this.options.direction.length + ") don't match");
      }

      // Start the magic
      this.setup();

    },


    /* Magic */
    setup: function() {
      this._positionElement();
      this._bindEvents();
      this.scrollToFloor(this.floorActive);

      if (isObject(this.options.ascensorFloorName)) {
        this._updateHash(this.floorActive);
      }
      if (isFunction(this.options.ready)) this.options.ready();
    },


    /* Setup User listener */
    _bindEvents: function() {
      var self = this;

      this.node.on('scrollToDirection', function(event, direction) {
        self.scrollToDirection(direction);
      });

      this.node.on('scrollToStage', function(event, floor) {
        if (typeof floor == 'string') {
          var floorId = $.inArray(floor, self.options.ascensorFloorName);
          if (floorId !== -1) self.scrollToFloor(floorId);
        } else if (typeof floor == 'number') {
          if (floor > self.nodeChildren.length) return;
          self.scrollToFloor(floor);
        }
      });

      this.node.on('next', function(event, floor) {
        var dataAttributeDirection = self.nodeChildren.eq(self.floorActive).data(self.dataAttributeMap.next);
        if (isNumber(dataAttributeDirection)) return self.scrollToFloor(dataAttributeDirection);
        self.next();
      });

      this.node.on('prev', function(event, floor) {
        var dataAttributeDirection = self.nodeChildren.eq(self.floorActive).data(self.dataAttributeMap.prev);
        if (isNumber(dataAttributeDirection)) return self.scrollToFloor(dataAttributeDirection);
        self.prev();
      });

      this.node.on('refresh', function() {
        self.refresh();
      });

      this.node.on('remove', function() {
        self.destroy();
      });

      // setup resize & key listener
      $(window).on('resize.ascensor', function(event) {
        self.scrollToFloor(self.floorActive, false);
      });

      // If floorName, add hashchange listener
      if (isObject(this.options.ascensorFloorName)) {
        $(window).on('hashchange.ascensor', function(event) {
          self._hashchangeHandler(event);
        });
      }

      // Detect orientation change, for device
      if (window.DeviceOrientationEvent) {
        $(window).on('orientationchange.ascensor', function(event) {
          self.scrollToFloor(self.floorActive);
        });
      }

      if (this.options.keyNavigation) {
        $(document).on('keydown.ascensor', function(event) {
          self._keypressHandler(event);
        });
      }

      if (this.options.wheelNavigation) {
        this.node.on('mousewheel.ascensor DOMMouseScroll.ascensor wheel.ascensor', function(e) {
          setTimeout(function() {
            if (!self.scrollInChildren) self._handleMouseWheelEvent(e);
          }, 10);
        });

        this.nodeChildren.on('scroll.ascensor', function(e) {
          self.scrollInChildren = true;
          if (self.scrollTimeOut) clearTimeout(self.scrollTimeOut);
          self.scrollTimeOut = setTimeout(function() {
            self.scrollInChildren = false;
          }, 300);
        });
      }

      // If swipe event option is true || string
      if (this.options.swipeNavigation) {

        var touchEvent = 'touchstart.ascensor touchend.ascensor touchcancel.ascensor';

        // If mobile-only, only use touchstart/end event				
        if (this.options.swipeNavigation !== 'mobile-only') touchEvent += ' mousedown.ascensor mouseup.ascensor';

        // Listen to touch event
        this.node.on(touchEvent, function(event) {
          self._handleTouchEvent(event);
        });
      }
    },

    refresh: function() {
      this.nodeChildren = this.node.children(this.options.childType);
      this._positionElement();
    },

    /* Remove method*/
    destroy: function() {

      // Unbind all binded event
      this.nodeChildren.off('scroll.ascensor');
      this.node.off('mousewheel.ascensor DOMMouseScroll.ascensor wheel.ascensor scrollToDirection scrollToStage next prev refresh remove touchstart.ascensor touchend.ascensor mousedown.ascensor mouseup.ascensor touchcancel.ascensor');
      $(window).off('resize.ascensor hashchange.ascensor orientationchange.ascensor');
      $(document).off('keydown.ascensor');

      // Remove css
      this.node.css({
        'position': '',
        'overflow': '',
        'top': '',
        'left': '',
        'width': '',
        'height': ''
      });

      this.nodeChildren.css({
        'position': '',
        'overflow': '',
        'top': '',
        'left': '',
        'width': '',
        'height': '',
        'transform': ''
      });

      // Remove plugin instance
      this.node.removeData();
    },

    _handleMouseWheelEvent: function(event) {
      if (this.node.is(':animated')) return;

      this.scrollTime = new Date().getTime();

      if (!this.lastScrollTime || this.scrollTime - this.lastScrollTime > this.options.wheelNavigationDelay) {
        this.lastScrollTime = this.scrollTime;
        return;
      }

      this.lastScrollTime = this.scrollTime;

      var deltaY, deltaX, delta;
      if ('detail' in event.originalEvent) {
        deltaY = event.originalEvent.detail * -1;
      }
      if ('wheelDelta' in event.originalEvent) {
        deltaY = event.originalEvent.wheelDelta;
      }
      if ('wheelDeltaY' in event.originalEvent) {
        deltaY = event.originalEvent.wheelDeltaY;
      }
      if ('wheelDeltaX' in event.originalEvent) {
        deltaX = event.originalEvent.wheelDeltaX * -1;
      }

      // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
      if ('axis' in event.originalEvent && event.originalEvent.axis === event.originalEvent.HORIZONTAL_AXIS) {
        deltaX = deltaY * -1;
        deltaY = 0;
      }

      // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
      delta = deltaY === 0 ? deltaX : deltaY;

      // New school wheel delta (wheel event)
      if ('deltaY' in event.originalEvent) {
        deltaY = event.originalEvent.deltaY * -1;
        delta = deltaY;
      }
      if ('deltaX' in event.originalEvent) {
        deltaX = event.originalEvent.deltaX;
        if (deltaY === 0) {
          delta = deltaX * -1;
        }
      }

      if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) this.scrollToDirection('left');
      if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) this.scrollToDirection('right');
      if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 0) this.scrollToDirection('up');
      if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < 0) this.scrollToDirection('down');

    },


    /* Touch event handler */
    _handleTouchEvent: function(event) {
      var self = this;
      switch (event.type) {

        // On touch/mouse down
        case 'touchstart':
        case 'mousedown':

          // save time & original position for X/Y
          this.touchStartTime = new Date().getTime();
          this.touchStartX = (event.type == 'touchstart') ? event.originalEvent.touches[0].pageX : event.pageX;
          this.touchStartY = (event.type == 'touchstart') ? event.originalEvent.touches[0].pageY : event.pageY;
          break;

          // On touch/mousedown
        case 'touchend':
        case 'touchcancel':
        case 'mouseup':

          // Save time & final position for X/Y
          this.touchEndTime = new Date().getTime();
          this.touchEndX = (event.type == 'touchend' || event.type == 'touchcancel') ? event.originalEvent.changedTouches[0].pageX : event.pageX;
          this.touchEndY = (event.type == 'touchend' || event.type == 'touchcancel') ? event.originalEvent.changedTouches[0].pageY : event.pageY;

          // calculate distance, duration & velocity.
          var distanceX = this.touchStartX - this.touchEndX;
          var distanceY = this.touchStartY - this.touchEndY;
          var duration = this.touchEndTime - this.touchStartTime;
          var velocityX = Math.abs(distanceX) / duration;
          var velocityY = Math.abs(distanceY) / duration;

          // If velocity, use absolute distance to determine axis
          // and compare distance to 0 determine direction
          if (velocityX > this.options.swipeVelocity && Math.abs(distanceX) > Math.abs(distanceY) && distanceX < 0) this.scrollToDirection('left');
          if (velocityX > this.options.swipeVelocity && Math.abs(distanceX) > Math.abs(distanceY) && distanceX > 0) this.scrollToDirection('right');
          if (velocityY > this.options.swipeVelocity && Math.abs(distanceX) < Math.abs(distanceY) && distanceY < 0) this.scrollToDirection('up');
          if (velocityY > this.options.swipeVelocity && Math.abs(distanceX) < Math.abs(distanceY) && distanceY > 0) this.scrollToDirection('down');

          break;
      }
    },



    /* Position floor on dom */
    _positionElement: function() {
      var self = this;
      if (this.directionIsArray) this._generateFloorMap();

      // Setup floor size & position
      this.node.css({
        'position': 'absolute',
        'overflow': 'hidden',
        'top': '0',
        'left': '0',
        'width': this.options.width,
        'height': this.options.height
      });

      this.nodeChildren.css({
        'position': 'absolute',
        'overflow': 'auto',
        'top': '0',
        'left': '0',
        'width': '100%',
        'height': '100%'
      });

      // place element correctly
      this.nodeChildren.each(function(index) {
        if (self.supportTransform) {
          $(this).css({
            'transform': function() {
              if (self.options.direction === 'y') return 'translateY(' + index * 100 + '%)';
              if (self.options.direction === 'x') return 'translateX(' + index * 100 + '%)';
              if (self.directionIsArray) return 'translateY(' + self.options.direction[index][self.AXIS_Y] * 100 + '%) translateX(' + self.options.direction[index][self.AXIS_X] * 100 + '%)';
            }
          });
        } else {
          $(this).css({
            'top': function() {
              if (self.options.direction === 'y') return index * 100 + '%';
              if (self.directionIsArray) return self.options.direction[index][self.AXIS_Y] * 100 + '%';
            },
            'left': function() {
              if (self.options.direction === 'x') return index * 100 + '%';
              if (self.directionIsArray) return self.options.direction[index][self.AXIS_X] * 100 + '%';
            },
          });
        }
      });
    },


    /* Helper : Return floor index from hash. */
    _getFloorFromHash: function() {
      if (this._getHash()) {
        if (this.options.ascensorFloorName && existInArray(this.options.ascensorFloorName, this._getHash())) {
          return this.options.ascensorFloorName.indexOf(this._getHash());
        }
      }
      return false;
    },


    /* Helper : Return floor index from hash. */
    _getHash: function() {
      if (window.location.hash) {
        var hash = window.location.hash.split('#').pop();
        return hash;
      }
      return false;
    },


    /* Hanlder: Handle window hashcange event */
    _hashchangeHandler: function(event) {
      if (isNumber(this._getFloorFromHash()) && this._getFloorFromHash() !== this.floorActive && !this.node.is(':animated')) {
        this.scrollToFloor(this._getFloorFromHash());
      }
    },


    /* Will update hash location if floor name are setup. */
    _updateHash: function(floorIndex) {
      if (isObject(this.options.ascensorFloorName) && this._getHash() !== this.options.ascensorFloorName[floorIndex]) {
        window.location.replace(('' + window.location).split('#')[0] + '#' + this.options.ascensorFloorName[floorIndex]);
      }
    },


    /* Event helper, let ascensor create own event, with floor information */
    _emitEvent: function(eventName, from, to) {
      this.node.trigger(eventName, floor = {
        from: from,
        to: to
      });
    },


    /* Warn handler */
    _emitConsoleMessage: function(type, warn) {
      if (type == "error") console.error("Ascensor.js: " + warn);
      if (type == "warn") console.warn("Ascensor.js: " + warn);
    },


    /* Keypress Handler */
    _keypressHandler: function(e) {
      var self = this;
      var key = e.keyCode || e.which;
      if (!$('input, textarea, button').is(':focus')) {
        switch (key) {
          case 40:
          case 83:
            self.scrollToDirection('down');
            break;
          case 38:
          case 87:
            self.scrollToDirection('up');
            break;

          case 37:
          case 65:
            self.scrollToDirection('left');
            break;

          case 39:
          case 68:
            self.scrollToDirection('right');
            break;
        }
      }
    },


    /* Resize handler. Update scrollTop & scrollLeft position */
    scrollToFloor: function(floor) {

      // If floor send is a tring, check if it match any of ascensorFloorName, then use poistion in array
      if (isString(floor) && existInArray(this.options.ascensorFloorName, floor)) floor = this.options.ascensorFloorName.indexOf(floor);

      var self = this;
      var animate = (floor === this.floorActive) ? false : true;

      if (this.NW !== this.node.width()) this.NW = this.node.width();
      if (this.NH !== this.node.height()) this.NH = this.node.height();

      // Make sure position is correct
      var animationObject = this._getAnimationSettings(floor);

      if (animate) {
        this._emitEvent('scrollStart', self.floorActive, floor);
        this.node.stop().animate(animationObject.property, self.options.time, self.options.easing, animationObject.callback);
      } else {
        this.node.stop().scrollTop(animationObject.defaults.scrollTop).scrollLeft(animationObject.defaults.scrollLeft);
      }

      this.floorActive = floor;
      this.node.data('current-floor', this.floorActive);

    },


    /* Prev function */
    prev: function() {
      var targetFloor = this.floorActive - 1;
      if (targetFloor < 0) {
        if (!this.options.loop) return;
        targetFloor = this.nodeChildren.length - 1;
      }
      this.scrollToFloor(targetFloor);
    },


    /* Prev function */
    next: function() {
      var targetFloor = this.floorActive + 1;
      if (targetFloor > this.nodeChildren.length - 1) {
        if (!this.options.loop) return;
        targetFloor = 0;
      }

      this.scrollToFloor(targetFloor);
    },


    /* Helper to generate animation settings */
    _getAnimationSettings: function(floor) {
      var self = this;
      var saveFloorActive = self.floorActive;
      // Create animation setting object
      var animationSettings = {
        property: {},
        callback: function() {
          self._emitEvent('scrollEnd', saveFloorActive, floor);
          self._updateHash(floor);
        },
        defaults: {}
      };


      // Create a second setting object
      // in case the queued option is set
      var secondAnimationSettings = {
        property: {},
        callback: function() {
          self._emitEvent('scrollEnd', saveFloorActive, floor);
          self._updateHash(floor);
        }
      };

      animationSettings.defaults.scrollTop = floor * self.NH;
      animationSettings.defaults.scrollLeft = floor * self.NW;

      // If direction is vertical
      // => set scrollTop property & return animationSettings
      if (self.options.direction === 'y') {
        animationSettings.property.scrollTop = floor * self.NH;
        return animationSettings;
      }


      // If direction is horizontal	
      // => set scrollleft property & return animationSettings
      else if (self.options.direction === 'x') {
        animationSettings.property.scrollLeft = floor * self.NW;
        return animationSettings;
      }

      // If direction is a map
      else if (self.directionIsArray) {

        // => Save value
        var scrollTopValue = self.options.direction[floor][self.AXIS_Y] * self.NH;
        var scrollLeftValue = self.options.direction[floor][self.AXIS_X] * self.NW;

        animationSettings.defaults.scrollTop = scrollTopValue;
        animationSettings.defaults.scrollLeft = scrollLeftValue;


        // If the queued option is set
        if (self.options.queued) {

          // => Check floor position, to avoid animation if already on same floor
          var sameXposition = this.node.scrollLeft() === scrollLeftValue;
          var sameYposition = this.node.scrollTop() === scrollTopValue;


          // If queued direction is horizontal & on the same floor
          // => Set scrollTop property & return animationSettings
          if (self.options.queued === 'x' && sameXposition) {
            animationSettings.property.scrollTop = scrollTopValue;
            return animationSettings;
          }


          // If queued direction is horizontal & NOT on the same floor
          // => Set scrollLeft property
          // => Set callback to a second animation (scrollTop)
          // => return animationSettings
          else {
            animationSettings.property.scrollLeft = scrollLeftValue;
            secondAnimationSettings.property.scrollTop = scrollTopValue;
            animationSettings.callback = function() {
              self.node.stop().animate(secondAnimationSettings.property, self.options.time, self.options.easing, secondAnimationSettings.callback);
            };
            return animationSettings;
          }


          // If queued direction is vertical & on the same floor
          // => Set scrollTop scrollLeft & return animationSettings
          if (self.options.queued === 'y' && sameYposition) {
            animationSettings.property.scrollLeft = scrollLeftValue;
            return animationSettings;
          }


          // If queued direction is vertical & NOT on the same floor
          // => Set scrollTop property
          // => Set callback to a second animation (scrollLeft)
          // => return animationSettings
          else {
            animationSettings.property.scrollTop = scrollTopValue;
            secondAnimationSettings.property.scrollLeft = scrollLeftValue;
            animationSettings.callback = function() {
              self.node.stop().animate(secondAnimationSettings.property, self.options.time, self.options.easing, secondAnimationSettings.callback);
            };
            return animationSettings;
          }

        }

        // If queud option is not set, 
        // => set scrollTop & ScrollLeft property 
        // => return animationSettings
        else {
          animationSettings.property.scrollTop = scrollTopValue;
          animationSettings.property.scrollLeft = scrollLeftValue;
          return animationSettings;
        }
      }

      return animationSettings;
    },


    /* Helper to handle direction correctly. */
    scrollToDirection: function(direction) {
      var self = this;

      // If a data attribute with current direction
      // is found, use it.
      var dataAttributeDirection = this.nodeChildren.eq(this.floorActive).data(this.dataAttributeMap[direction]);
      if (isNumber(dataAttributeDirection)) return self.scrollToFloor(dataAttributeDirection);

      var directionIsHorizontal = (direction == 'right' || direction == 'left');
      var directionIsVertical = (direction == 'down' || direction == 'up');

      // If direction is x or y and there is 
      // direction are opppsite, return here
      if ((self.options.direction == 'y' && directionIsHorizontal) || (self.options.direction == 'x' && directionIsVertical)) return;

      // If direction is x or x, and 
      // direction match, use prev/next
      if ((self.options.direction == 'y' && direction == 'down') || (self.options.direction == 'x' && direction == 'right')) return self.next();
      if ((self.options.direction == 'y' && direction == 'up') || (self.options.direction == 'x' && direction == 'left')) return self.prev();


      if (self.directionIsArray) {

        var floorObject = self.floorMap[self.floorActive];

        // If existing, return appending floor
        var directFloor = floorObject[direction];
        if (isNumber(directFloor)) return self.scrollToFloor(directFloor);

        // Jump is set to true, use the 
        // closest floor in that same direction
        var closestFloor = floorObject.closest[direction];
        if (isTrue(self.options.jump) && isNumber(closestFloor)) return self.scrollToFloor(closestFloor);

        // If loop is set to true, use
        //	the furthest floor
        var furthestFloor = floorObject.furthest[direction];
        if (isNumber(furthestFloor) && (isTrue(self.options.loop) || (directionIsHorizontal && self.options.loop == 'loop-x') || (directionIsVertical && self.options.loop == 'loop-y'))) {
          return self.scrollToFloor(furthestFloor);
        }

        // If Increment exist & option is set
        var incrementFloor = floorObject.increment[direction];
        if (isNumber(incrementFloor)) {

          if (self.options.loop == 'increment' || directionIsVertical && self.options.loop == 'increment-y' || directionIsHorizontal && self.options.loop == 'increment-x') {
            return self.scrollToFloor(incrementFloor);
          }
        }

        // Jump from last to first or first to last
        if ((self.options.loop == 'increment-x' && directionIsHorizontal) || self.options.loop == 'increment') {
          if (self.floorActive == self.floorMap.furthest_x) return self.scrollToFloor(self.floorMap.closest_x);
          if (self.floorActive == self.floorMap.closest_x) return self.scrollToFloor(self.floorMap.furthest_x);
        }

        if ((self.options.loop == 'increment-y' && directionIsVertical) || self.options.loop == 'increment') {
          if (self.floorActive == self.floorMap.furthest_y) return self.scrollToFloor(self.floorMap.closest_y);
          if (self.floorActive == self.floorMap.closest_y) return self.scrollToFloor(self.floorMap.furthest_y);
        }
      }
    },



    /* Helper to get the direct appending floor in one precise direction direction */
    _getDirectFloorIndex: function(DA, floorIndex, direction) {
      var self = this;

      // Create floor target array base on floorobject
      var floorTarget = [this.options.direction[floorIndex][this.AXIS_Y], this.options.direction[floorIndex][this.AXIS_X]];

      // Adjust map depending on direction
      if (direction == 'right') floorTarget[this.AXIS_X] += 1;
      if (direction == 'left') floorTarget[this.AXIS_X] -= 1;
      if (direction == 'up') floorTarget[this.AXIS_Y] -= 1;
      if (direction == 'down') floorTarget[this.AXIS_Y] += 1;

      // loopand compare direction map
      var floorTargetIndex = false;
      $.each(DA, function(index, map) {

        // If current object map value are equal to target one
        if (map[self.AXIS_Y] == floorTarget[self.AXIS_Y] && map[self.AXIS_X] == floorTarget[self.AXIS_X]) {

          // Get index & break loop
          floorTargetIndex = index;
          return false;
        }
      });

      return floorTargetIndex;
    },

    /* Return correct axis depending on position */
    _getAxisFromDirection: function(direction) {
      var self = this;
      var axis;
      switch (direction) {
        case 'up':
        case 'down':
          axis = self.AXIS_Y;
          break;
        case 'left':
        case 'right':
          axis = self.AXIS_X;
          break;
      }

      return axis;
    },

    /* Helper to get the closest floor in one precise direction direction */
    _getClosestFloorIndex: function(DA, floorIndex, direction, level) {
      var self = this;

      level = level || 0;

      // Get axis & compare-to floorIndex
      var axis = this._getAxisFromDirection(direction);
      var goal = DA[floorIndex][axis];
      var oppositeAxis = (axis == this.AXIS_Y) ? this.AXIS_X : this.AXIS_Y;

      // Setup loop variable
      var closestIndex = false;
      var closestMap = false;

      // Loop trough floor position array
      $.each(DA, function(index, map) {

        // If on same axis
        if (map[oppositeAxis] == (DA[floorIndex][oppositeAxis] + level)) {

          // If direction is foward (right or down) and the value is bigger than goal 
          // of if direction is backward (left or up) and the value is smaller than the goal
          if (((direction == 'right' || direction == 'down') && map[axis] > goal) || ((direction == 'left' || direction == 'up') && map[axis] < goal)) {


            // No previous value set or if the current
            // value is smaller than the previous one					 
            if (!closestMap || Math.abs(map[axis] - goal) < Math.abs(closestMap[axis])) {
              closestIndex = index;
              closestMap = map;
            }
          }
        }
      });

      // return index
      return closestIndex;
    },


    /* Helper to get the furthest floor in one precise direction direction */
    _getFurthestFloorIndex: function(DA, floorIndex, direction, level) {
      var self = this;

      level = level || 0;

      // Get axis & compare-to floorIndex
      var axis = this._getAxisFromDirection(direction);
      var goal = DA[floorIndex][axis];
      var oppositeAxis = (axis == this.AXIS_Y) ? this.AXIS_X : this.AXIS_Y;

      // Setup loop variable
      var furthestMap = false;
      var furthestIndex = false;

      // Loop trough floor position array
      $.each(DA, function(index, map) {

        // If on same axis
        if (map[oppositeAxis] == (DA[floorIndex][oppositeAxis] + level)) {

          // If on same axis
          if (!furthestMap || (Math.abs(map[axis] - goal) > Math.abs(furthestMap[axis] - goal))) {
            furthestMap = map;
            furthestIndex = index;
          }
        }
      });

      // return index
      return furthestIndex;

    },

    /* Use to access quickly later, avoiding looping through every direction every time */
    _generateFloorMap: function() {
      var self = this;

      this.floorMap = [];

      // Create map only for floor present in the dom
      var DA = jQuery.grep(self.options.direction, function(directionArray, index) {
        return self.nodeChildren.length > index;
      });

      // Loop on the diration array and get 
      // the floor ID for each direction
      $.each(DA, function(index, floorItem) {
        self.floorMap[index] = {
          'down': self._getDirectFloorIndex(DA, index, 'down'),
          'up': self._getDirectFloorIndex(DA, index, 'up'),
          'right': self._getDirectFloorIndex(DA, index, 'right'),
          'left': self._getDirectFloorIndex(DA, index, 'left'),
          'increment': {
            'down': self._getFurthestFloorIndex(DA, index, 'down', 1),
            'up': self._getFurthestFloorIndex(DA, index, 'up', -1),
            'right': self._getFurthestFloorIndex(DA, index, 'right', 1),
            'left': self._getFurthestFloorIndex(DA, index, 'left', -1)
          },
          'closest': {
            'down': self._getClosestFloorIndex(DA, index, 'down'),
            'up': self._getClosestFloorIndex(DA, index, 'up'),
            'right': self._getClosestFloorIndex(DA, index, 'right'),
            'left': self._getClosestFloorIndex(DA, index, 'left')
          },
          'furthest': {
            'down': self._getFurthestFloorIndex(DA, index, 'down'),
            'up': self._getFurthestFloorIndex(DA, index, 'up'),
            'right': self._getFurthestFloorIndex(DA, index, 'right'),
            'left': self._getFurthestFloorIndex(DA, index, 'left')
          }
        };
      });

      function getFurtherFloorArray(floorArray, axis) {
        var furtherFloor = false;
        jQuery.each(floorArray, function(index, DA) {
          if (furtherFloor === false || furtherFloor[axis] < DA[axis]) {
            furtherFloor = DA;
          }
        });
        return furtherFloor;
      }

      function getClosestFloorOnAxis(floorArray, axis) {
        var furtherFloor = false;
        jQuery.each(floorArray, function(index, DA) {
          if (furtherFloor === false || furtherFloor[axis] > DA[axis]) {
            furtherFloor = DA;
          }
        });
        return furtherFloor;
      }

      function getSameAxisFloor(floorItem, axis) {
        return jQuery.grep(DA, function(DA) {
          var isOnSameAxis = DA[axis] == floorItem[axis];
          return isOnSameAxis;
        });
      }

      var approximateFurtherX = getFurtherFloorArray(DA, self.AXIS_X);
      var sameAxisXFurthest = getSameAxisFloor(approximateFurtherX, self.AXIS_X);
      var furtherY = getFurtherFloorArray(sameAxisXFurthest, self.AXIS_Y);

      var approximateFurtherY = getFurtherFloorArray(DA, self.AXIS_Y);
      var sameAxisYFurthest = getSameAxisFloor(approximateFurtherY, self.AXIS_Y);
      var furtherX = getFurtherFloorArray(sameAxisYFurthest, self.AXIS_X);

      self.floorMap.furthest_x = DA.indexOf(furtherX);
      self.floorMap.furthest_y = DA.indexOf(furtherY);

      var approximateClosestX = getClosestFloorOnAxis(DA, self.AXIS_X);
      var sameAxisXClosest = getSameAxisFloor(approximateClosestX, self.AXIS_X);
      var closestY = getClosestFloorOnAxis(sameAxisXClosest, self.AXIS_Y);

      var approximateClosestY = getClosestFloorOnAxis(DA, self.AXIS_Y);
      var sameAxisYClosest = getSameAxisFloor(approximateClosestY, self.AXIS_Y);
      var closestX = getClosestFloorOnAxis(sameAxisYClosest, self.AXIS_X);

      self.floorMap.closest_x = DA.indexOf(closestX);
      self.floorMap.closest_y = DA.indexOf(closestY);


    },

  };

  $.fn[pluginName] = function(options) {
    this.each(function() {
      if (!$.data(this, pluginName)) {
        $.data(this, pluginName, new Plugin(this, options));
      }
    });

    return this;
  };
})(jQuery, window, document);