/*
   Better drag and drop with an outline to show where the item will be dropped to.
   
   Implemented by Michael Daines (http://mdaines.com) for PNN (http://pnn.com).
   
*/

MarkerSortable = Class.create();
MarkerSortable.prototype = {
  initialize: function(containerSelector, elementSelector) {
    var options = Object.extend({
      format:      /^[^_]*_(.*)$/,
      onUpdate:    Prototype.emptyFunction
    }, arguments[2] || {});
    
    this.options = options;
    
    this.containerSelector = new Selector(containerSelector);
    this.elementSelector = new Selector(elementSelector);
    
    this.reload(true);
    
    Draggables.addObserver(new MarkerObserver(this, this.options.onUpdate));
  },
  reload: function() {
    this.containers = this.containerSelector.findElements();
    this.elements = this.containers.map(function(c) { return this.elementSelector.findElements(c); }.bind(this)).flatten();
    if (arguments[0]) this.elements.each(function(e) { new Draggable(e, this.options); }.bind(this));
  },
  serialize: function() {
    var name = arguments[0] || 'columns';
    
    this.reload();
    
    return this.elements.map(function(e) {
      return name + '[' + e.parentNode.id.match(this.options.format)[1] + '][]=' + e.id.match(this.options.format)[1];
    }.bind(this)).join('&');
    
  }
}

MarkerSortable.markerNode = function(element) {
  var node = document.createElement('DIV');
  var dimensions = Element.getDimensions(element);
  node.style.height = dimensions.height + 'px';
  Element.addClassName(node, 'dropmarker');
  return node;
}

MarkerObserver = Class.create();
MarkerObserver.prototype = {
  initialize: function(ms, observer) {
    this.markerSortable = ms;
    this.observer  = observer;
    this.lastValue = this.markerSortable.serialize();
  },
  getElementsInContainer: function(container) {
    if (!this._elementsInContainerCache[container.id])
      this._elementsInContainerCache[container.id] = this.markerSortable.elementSelector.findElements(container);
    return this._elementsInContainerCache[container.id];
  },
  onStart: function(eventName, draggable, event) {
    if (this.markerSortable.elements.include(draggable.element)) {
      this.lastValue = this.markerSortable.serialize();
    
      this._elementsInContainerCache = {};
    
      Position.absolutize(draggable.element);
    
      // put in marker node clone
      this.marker = MarkerSortable.markerNode(draggable.element);
      draggable.element.parentNode.insertBefore(this.marker, draggable.element);
    }
  },
  onDrag: function(eventName, draggable, event) {
    if (this.markerSortable.elements.include(draggable.element)) {
      if (!event) return undefined;
      
      var x = Event.pointerX(event);
      var y = Event.pointerY(event);
    
      // find closest container
      var containerDistances = this.markerSortable.containers.map(function(c) {
        var position = Position.cumulativeOffset(c);
        return Math.abs((position[0] + (c.offsetWidth/2)) - x);
      });
    
      closestContainer = this.markerSortable.containers[containerDistances.indexOf(containerDistances.min())];
    
      var elementsInContainer = this.getElementsInContainer(closestContainer);
    
      // if container is empty, put the marker there
      if (elementsInContainer.length == 0) {
        Element.remove(this.marker);
        closestContainer.appendChild(this.marker);
      
      // if the container isn't empty, find the closest element within that container
      // and put the marker there
      } else {
    
        var elementDistances = elementsInContainer.map(function(e) {
          if (e == draggable.element) return undefined;
          var elementPosition = Position.cumulativeOffset(e);
          return Math.abs((elementPosition[1] + (e.offsetHeight/2)) - y);
        });
      
        closestElement = elementsInContainer[elementDistances.indexOf(elementDistances.min())];
      
        Position.within(closestElement, x, y);
        var overlap = Position.overlap('vertical', closestElement);
  
        closestElement.parentNode.insertBefore(this.marker, overlap > 0.5 ? closestElement : closestElement.nextSibling);
      }
    }
  },
  onEnd: function(eventName, draggable, event) {
    if (this.markerSortable.elements.include(draggable.element)) {
      Element.hide(draggable.element);
      Position.relativize(draggable.element);
      draggable.element.style.left = 0;
      draggable.element.style.top = 0;
      Element.show(draggable.element);
    
      // move element to where marker node clone is
      Element.remove(draggable.element);
      this.marker.parentNode.insertBefore(draggable.element, this.marker);
    
      // remove marker node clone
      Element.remove(this.marker);
    
      if(this.lastValue != this.markerSortable.serialize())
        this.observer(this.markerSortable)
    }
  }
}

