#ifdef 0
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
#endif

/**
 * This singleton provides a custom drop target detection. We need this because
 * the default DnD target detection relies on the cursor's position. We want
 * to pick a drop target based on the dragged site's position.
 */
var gDropTargetShim = {
  /**
   * Cache for the position of all cells, cleaned after drag finished.
   */
  _cellPositions: null,

  /**
   * The last drop target that was hovered.
   */
  _lastDropTarget: null,

  /**
   * Initializes the drop target shim.
   */
  init: function () {
    gGrid.node.addEventListener("dragstart", this, true);
  },

  /**
   * Add all event listeners needed during a drag operation.
   */
  _addEventListeners: function () {
    gGrid.node.addEventListener("dragend", this);

    let docElement = document.documentElement;
    docElement.addEventListener("dragover", this);
    docElement.addEventListener("dragenter", this);
    docElement.addEventListener("drop", this);
  },

  /**
   * Remove all event listeners that were needed during a drag operation.
   */
  _removeEventListeners: function () {
    gGrid.node.removeEventListener("dragend", this);

    let docElement = document.documentElement;
    docElement.removeEventListener("dragover", this);
    docElement.removeEventListener("dragenter", this);
    docElement.removeEventListener("drop", this);
  },

  /**
   * Handles all shim events.
   */
  handleEvent: function (aEvent) {
    switch (aEvent.type) {
      case "dragstart":
        this._dragstart(aEvent);
        break;
      case "dragenter":
        aEvent.preventDefault();
        break;
      case "dragover":
        this._dragover(aEvent);
        break;
      case "drop":
        this._drop(aEvent);
        break;
      case "dragend":
        this._dragend(aEvent);
        break;
    }
  },

  /**
   * Handles the 'dragstart' event.
   * @param aEvent The 'dragstart' event.
   */
  _dragstart: function (aEvent) {
    if (aEvent.target.classList.contains("newtab-link")) {
      gGrid.lock();
      this._addEventListeners();
    }
  },

  /**
   * Handles the 'dragover' event.
   * @param aEvent The 'dragover' event.
   */
  _dragover: function (aEvent) {
    // XXX bug 505521 - Use the dragover event to retrieve the
    //                  current mouse coordinates while dragging.
    let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
    gDrag.drag(sourceNode._newtabSite, aEvent);

    // Find the current drop target, if there's one.
    this._updateDropTarget(aEvent);

    // If we have a valid drop target,
    // let the drag-and-drop service know.
    if (this._lastDropTarget) {
      aEvent.preventDefault();
    }
  },

  /**
   * Handles the 'drop' event.
   * @param aEvent The 'drop' event.
   */
  _drop: function (aEvent) {
    // We're accepting all drops.
    aEvent.preventDefault();

    // remember that drop event was seen, this explicitly
    // assumes that drop event preceeds dragend event
    this._dropSeen = true;

    // Make sure to determine the current drop target
    // in case the dragover event hasn't been fired.
    this._updateDropTarget(aEvent);

    // A site was successfully dropped.
    this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
  },

  /**
   * Handles the 'dragend' event.
   * @param aEvent The 'dragend' event.
   */
  _dragend: function (aEvent) {
    if (this._lastDropTarget) {
      if (aEvent.dataTransfer.mozUserCancelled || !this._dropSeen) {
        // The drag operation was cancelled or no drop event was generated
        this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
        this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
      }

      // Clean up.
      this._lastDropTarget = null;
      this._cellPositions = null;
    }

    this._dropSeen = false;
    gGrid.unlock();
    this._removeEventListeners();
  },

  /**
   * Tries to find the current drop target and will fire
   * appropriate dragenter, dragexit, and dragleave events.
   * @param aEvent The current drag event.
   */
  _updateDropTarget: function (aEvent) {
    // Let's see if we find a drop target.
    let target = this._findDropTarget(aEvent);

    if (target != this._lastDropTarget) {
      if (this._lastDropTarget)
        // We left the last drop target.
        this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);

      if (target)
        // We're now hovering a (new) drop target.
        this._dispatchEvent(aEvent, "dragenter", target);

      if (this._lastDropTarget)
        // We left the last drop target.
        this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);

      this._lastDropTarget = target;
    }
  },

  /**
   * Determines the current drop target by matching the dragged site's position
   * against all cells in the grid.
   * @return The currently hovered drop target or null.
   */
  _findDropTarget: function () {
    // These are the minimum intersection values - we want to use the cell if
    // the site is >= 50% hovering its position.
    let minWidth = gDrag.cellWidth / 2;
    let minHeight = gDrag.cellHeight / 2;

    let cellPositions = this._getCellPositions();
    let rect = gTransformation.getNodePosition(gDrag.draggedSite.node);

    // Compare each cell's position to the dragged site's position.
    for (let i = 0; i < cellPositions.length; i++) {
      let inter = rect.intersect(cellPositions[i].rect);

      // If the intersection is big enough we found a drop target.
      if (inter.width >= minWidth && inter.height >= minHeight)
        return cellPositions[i].cell;
    }

    // No drop target found.
    return null;
  },

  /**
   * Gets the positions of all cell nodes.
   * @return The (cached) cell positions.
   */
  _getCellPositions: function DropTargetShim_getCellPositions() {
    if (this._cellPositions)
      return this._cellPositions;

    return this._cellPositions = gGrid.cells.map(function (cell) {
      return {cell: cell, rect: gTransformation.getNodePosition(cell.node)};
    });
  },

  /**
   * Dispatches a custom DragEvent on the given target node.
   * @param aEvent The source event.
   * @param aType The event type.
   * @param aTarget The target node that receives the event.
   */
  _dispatchEvent: function (aEvent, aType, aTarget) {
    let node = aTarget.node;
    let event = document.createEvent("DragEvent");

    // The event should not bubble to prevent recursion.
    event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false,
                        false, false, 0, node, aEvent.dataTransfer);

    node.dispatchEvent(event);
  }
};