define([

'jquery',
'../utils'

], function ($, Utils) {

function AttachBody (decorated, $element, options) {
  this.$dropdownParent = options.get('dropdownParent') || $(document.body);

  decorated.call(this, $element, options);
}

AttachBody.prototype.bind = function (decorated, container, $container) {
  var self = this;

  var setupResultsEvents = false;

  decorated.call(this, container, $container);

  container.on('open', function () {
    self._showDropdown();
    self._attachPositioningHandler(container);

    if (!setupResultsEvents) {
      setupResultsEvents = true;

      container.on('results:all', function () {
        self._positionDropdown();
        self._resizeDropdown();
      });

      container.on('results:append', function () {
        self._positionDropdown();
        self._resizeDropdown();
      });
    }
  });

  container.on('close', function () {
    self._hideDropdown();
    self._detachPositioningHandler(container);
  });

  this.$dropdownContainer.on('mousedown', function (evt) {
    evt.stopPropagation();
  });
};

AttachBody.prototype.destroy = function (decorated) {
  decorated.call(this);

  this.$dropdownContainer.remove();
};

AttachBody.prototype.position = function (decorated, $dropdown, $container) {
  // Clone all of the container classes
  $dropdown.attr('class', $container.attr('class'));

  $dropdown.removeClass('select2');
  $dropdown.addClass('select2-container--open');

  $dropdown.css({
    position: 'absolute',
    top: -999999
  });

  this.$container = $container;
};

AttachBody.prototype.render = function (decorated) {
  var $container = $('<span></span>');

  var $dropdown = decorated.call(this);
  $container.append($dropdown);

  this.$dropdownContainer = $container;

  return $container;
};

AttachBody.prototype._hideDropdown = function (decorated) {
  this.$dropdownContainer.detach();
};

AttachBody.prototype._attachPositioningHandler =
    function (decorated, container) {
  var self = this;

  var scrollEvent = 'scroll.select2.' + container.id;
  var resizeEvent = 'resize.select2.' + container.id;
  var orientationEvent = 'orientationchange.select2.' + container.id;

  var $watchers = this.$container.parents().filter(Utils.hasScroll);
  $watchers.each(function () {
    $(this).data('select2-scroll-position', {
      x: $(this).scrollLeft(),
      y: $(this).scrollTop()
    });
  });

  $watchers.on(scrollEvent, function (ev) {
    var position = $(this).data('select2-scroll-position');
    $(this).scrollTop(position.y);
  });

  $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
    function (e) {
    self._positionDropdown();
    self._resizeDropdown();
  });
};

AttachBody.prototype._detachPositioningHandler =
    function (decorated, container) {
  var scrollEvent = 'scroll.select2.' + container.id;
  var resizeEvent = 'resize.select2.' + container.id;
  var orientationEvent = 'orientationchange.select2.' + container.id;

  var $watchers = this.$container.parents().filter(Utils.hasScroll);
  $watchers.off(scrollEvent);

  $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
};

AttachBody.prototype._positionDropdown = function () {
  var $window = $(window);

  var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
  var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');

  var newDirection = null;

  var offset = this.$container.offset();

  offset.bottom = offset.top + this.$container.outerHeight(false);

  var container = {
    height: this.$container.outerHeight(false)
  };

  container.top = offset.top;
  container.bottom = offset.top + container.height;

  var dropdown = {
    height: this.$dropdown.outerHeight(false)
  };

  var viewport = {
    top: $window.scrollTop(),
    bottom: $window.scrollTop() + $window.height()
  };

  var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
  var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);

  var css = {
    left: offset.left,
    top: container.bottom
  };

  // Determine what the parent element is to use for calciulating the offset
  var $offsetParent = this.$dropdownParent;

  // For statically positoned elements, we need to get the element
  // that is determining the offset
  if ($offsetParent.css('position') === 'static') {
    $offsetParent = $offsetParent.offsetParent();
  }

  var parentOffset = $offsetParent.offset();

  css.top -= parentOffset.top;
  css.left -= parentOffset.left;

  if (!isCurrentlyAbove && !isCurrentlyBelow) {
    newDirection = 'below';
  }

  if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
    newDirection = 'above';
  } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
    newDirection = 'below';
  }

  if (newDirection == 'above' ||
    (isCurrentlyAbove && newDirection !== 'below')) {
    css.top = container.top - parentOffset.top - dropdown.height;
  }

  if (newDirection != null) {
    this.$dropdown
      .removeClass('select2-dropdown--below select2-dropdown--above')
      .addClass('select2-dropdown--' + newDirection);
    this.$container
      .removeClass('select2-container--below select2-container--above')
      .addClass('select2-container--' + newDirection);
  }

  this.$dropdownContainer.css(css);
};

AttachBody.prototype._resizeDropdown = function () {
  var css = {
    width: this.$container.outerWidth(false) + 'px'
  };

  if (this.options.get('dropdownAutoWidth')) {
    css.minWidth = css.width;
    css.position = 'relative';
    css.width = 'auto';
  }

  this.$dropdown.css(css);
};

AttachBody.prototype._showDropdown = function (decorated) {
  this.$dropdownContainer.appendTo(this.$dropdownParent);

  this._positionDropdown();
  this._resizeDropdown();
};

return AttachBody;

});