var dragWrapperHandler = {
  // @inheritdoc
  shouldAcceptDrag: function(e) {
    // We can't access filenames during the 'dragenter' event, so we have to
    // wait until 'drop' to decide whether to do something with the file or
    // not.
    // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
    return e.dataTransfer.types.indexOf('Files') > -1;
  },
  // @inheritdoc
  doDragEnter: function() {
    chrome.send('startDrag');
    ExtensionSettings.getInstance().showDragDropOverlay(true);
  },
  // @inheritdoc
  doDragLeave: function() {
    ExtensionSettings.getInstance().showDragDropOverlay(false);
    chrome.send('stopDrag');
  },
  // @inheritdoc
  doDragOver: function(e) {
    e.preventDefault();
  },
  // @inheritdoc
  doDrop: function(e) {
    // Only process files that look like extensions. Other files should
    // navigate the browser normally.
    if (!e.dataTransfer.files.length ||
        !/\.(crx|user\.js)$/.test(e.dataTransfer.files[0].name)) {
      ExtensionSettings.getInstance().showDragDropOverlay(false);
      return;
    }
    chrome.send('installDroppedFile');
    ExtensionSettings.getInstance().showDragDropOverlay(false);
    e.preventDefault();
  }
};
    
function DragWrapper(target, handler) {
  this.initialize(target, handler);
}

DragWrapper.prototype = {
  initialize: function(target, handler) {
    target.addEventListener('dragenter',
                            this.onDragEnter_.bind(this));
    target.addEventListener('dragover', this.onDragOver_.bind(this));
    target.addEventListener('drop', this.onDrop_.bind(this));
    target.addEventListener('dragleave', this.onDragLeave_.bind(this));

    this.target_ = target;
    this.handler_ = handler;
  },

  /**
   * The number of un-paired dragenter events that have fired on |this|. This
   * is incremented by |onDragEnter_| and decremented by |onDragLeave_|. This
   * is necessary because dragging over child widgets will fire additional
   * enter and leave events on |this|. A non-zero value does not necessarily
   * indicate that |isCurrentDragTarget()| is true.
   * @type {number}
   * @private
   */
  dragEnters_: 0,

  /**
   * Whether the tile page is currently being dragged over with data it can
   * accept.
   * @type {boolean}
   */
  get isCurrentDragTarget() {
    return this.target_.classList.contains('drag-target');
  },

  /**
   * Handler for dragenter events fired on |target_|.
   * @param {Event} e A MouseEvent for the drag.
   * @private
   */
  onDragEnter_: function(e) {
    if (++this.dragEnters_ == 1) {
      if (this.handler_.shouldAcceptDrag(e)) {
        this.target_.classList.add('drag-target');
        this.handler_.doDragEnter(e);
      }
    } else {
      // Sometimes we'll get an enter event over a child element without an
      // over event following it. In this case we have to still call the
      // drag over handler so that we make the necessary updates (one visible
      // symptom of not doing this is that the cursor's drag state will
      // flicker during drags).
      this.onDragOver_(e);
    }
  },

  /**
   * Thunk for dragover events fired on |target_|.
   * @param {Event} e A MouseEvent for the drag.
   * @private
   */
  onDragOver_: function(e) {
    if (!this.target_.classList.contains('drag-target'))
      return;
    this.handler_.doDragOver(e);
  },

  /**
   * Thunk for drop events fired on |target_|.
   * @param {Event} e A MouseEvent for the drag.
   * @private
   */
  onDrop_: function(e) {
    this.dragEnters_ = 0;
    if (!this.target_.classList.contains('drag-target'))
      return;
    this.target_.classList.remove('drag-target');
    this.handler_.doDrop(e);
  },

  /**
   * Thunk for dragleave events fired on |target_|.
   * @param {Event} e A MouseEvent for the drag.
   * @private
   */
  onDragLeave_: function(e) {
    if (--this.dragEnters_ > 0)
      return;

    this.target_.classList.remove('drag-target');
    this.handler_.doDragLeave(e);
  },
};
