if (typeof(tm) == "undefined") {
  throw new Error("tm.dnd Require tm.core");
}

tm.dnd = {};

tm.dnd.Draggables = {
  //当前拖拽的对象
  draggable: null,

  winMoveDragStart: function(e, draggable) {
    e = e ? e : window.event;
    if (!tm.Event.isLeftClick(e)) {
      return;
    }
    this.removeDragEvents();
    if (this.draggable && !this.draggable.hasDrop) {
      tm.dnd.Droppables.hideDropTmpDiv();
      this.draggable.endDrag(e);
      this.removeDragEvents();
      return;
    }
    this.draggable = draggable;
    //call droppables callback
    if (draggable.canDrop) {
      tm.dnd.Droppables.detectDragStart(e);
    }
    //开始拖拽
    draggable.startDrag(e);
    this.addDragEvents();
  },
  
  winMoveDragGo: function(e) {
    e = e ? e : window.event;
    var draggable = tm.dnd.Draggables.draggable;
    if (!draggable.isDrag) {
      return;
    }
    //call droppable callback
    if (draggable.canDrop) {
      tm.dnd.Droppables.detectHover(e);
    }
    //call onDrag callback
    draggable.onDrag(e);
  },
  
  winMoveDragStop: function(e) {
  	e = e ? e : window.event;
    if (!tm.Event.isLeftClick(e)) {
      return;
    }
  	var draggable = tm.dnd.Draggables.draggable;
  	if (draggable.canDrop) {
      //drop callback
      tm.dnd.Droppables.detectDrop(e);
  	}
    //drag callback
    draggable.endDrag(e);
    //
    tm.dnd.Draggables.removeDragEvents();
  },
  escKeyPress: function(e) {
    e = e ? e : window.event;
    if (e.keyCode == 27) {
      tm.dnd.Droppables.hideDropTmpDiv();
      tm.dnd.Draggables.draggable.endDrag(e);
      tm.dnd.Draggables.removeDragEvents();
    }
  },
  addDragEvents: function() {
    tm.Event.addEvent(document, "mousemove", this.winMoveDragGo, true);
    tm.Event.addEvent(document, "mouseup", this.winMoveDragStop, false);
    tm.Event.addEvent(document, "keypress", this.escKeyPress, false);
  },
  removeDragEvents: function() {
    tm.Event.removeEvent(document, "mousemove", tm.dnd.Draggables.winMoveDragGo, false);
    tm.Event.removeEvent(document, "mouseup", tm.dnd.Draggables.winMoveDragStop, false);
    tm.Event.removeEvent(document, "keypress", tm.dnd.Draggables.escKeyPress, false);
  }
};

/**
 * class Draggable
 */
tm.dnd.Draggable = Class.create();
tm.extend(tm.dnd.Draggable.prototype, {
  element: null,
  droppable: null,
  revert: false,
  offset: { left: 0, top: 0 },
  size: { width: 0, height: 0 },
  bound: {x: 0, y: 0, w: 0, h: 0},
  moveToEnd: false,
  hasDrop: true,
  resetBound: function() {
  	this.bound = tm.Position.bound(this.element);
  },
  isMoveToEnd: function() {
  	return this.moveToEnd;
  },
  endPoint: {
    parent: null,
    sibling: null
  },
  isDrag: false,
  hasMoveElem: false,
  
  initialize: function(elem, args) {
  	var options = {
      handle: elem,
      canDrop: false,
      opacity: true
  	};
    tm.extend(this, options, args);
    this.element = typeof(elem) == "string" ? $(elem) : elem;
    this.handle = typeof(this.handle) == "string" ? $(this.handle) : this.handle;
    this.handle.style.cursor = "move";
    var draggable = this;
    tm.Event.addEvent(this.handle, "mousedown", 
      function(e) {
        e = e ? e : window.event;
        tm.dnd.Draggables.winMoveDragStart(e, draggable);
      }, 
      false);
  },
  startDrag: function(e) {
  	this.hasDrop = false;
    var elem = this.element;
    //透明
    if (this.isOpacity()) {
      tm.Effect.setOpacity(elem, 0.8);
    }
    //记录ffset
    var p = tm.Event.pointer(e);
    var pos = tm.Position.cumulativeOffset(elem);
    this.offset = { left: p.x - pos.left, top: p.y - pos.top };
    this.size = { width: elem.offsetWidth, height: elem.offsetHeight };
    //创建记录点，用于revert的时候，插入element
    var sibling = null;
    if (sibling = this._findAvailNextSibling(elem)) {
      this.endPoint = {sibling: sibling, parent: elem.parentNode};
    } else if (elem.parentNode != document.body) {
      this.endPoint = {sibling: null, parent: elem.parentNode};
    }
    //从当前节点parentNode删除，并且添加到body上
    elem.parentNode.removeChild(elem);
    elem.style.position = "absolute";
    elem.style.left = pos.left + "px";
    elem.style.top = pos.top + "px";
    
    elem.style.width = this.size.width + "px";
    document.body.appendChild(elem);
    
    
    this.isDrag = true;
    this.hasMoveElem = true;
    //callback
    this.onDragStart(e);
  },
  onDrag: function(e) {
    var elem = this.element;
    if (this.hasMoveElem == false) {
      this.hasMoveElem = true;
    }
    elem.style.left = tm.Event.pointerX(e) - this.offset.left + "px";
    elem.style.top = tm.Event.pointerY(e) - this.offset.top + "px";
    //callback
    this.onDragging(e);
  }, 
  endDrag: function(e) {
  	this.hasDrop = true;
    this.isDrag = false;
    if (!this.hasMoveElem) {
      return;
    }
    if (this.isOpacity()) {
      tm.Effect.setOpacity(this.element, 1);
    }
    if (this.isRevert() || this.isMoveToEnd()) {
      this.moveToEndPoint();
    }
    //callback
    this.onDragEnd(e);
  },
  moveToEndPoint: function(e) {
    document.body.removeChild(this.element);
    if (this.endPoint.sibling) {
      this.endPoint.parent.insertBefore(this.element, this.endPoint.sibling);
    } else {
      this.endPoint.parent.appendChild(this.element);    
    }
    if (this.isRevert()) {
      this.element.style.position = "relative";
      this.element.style.left = "0";
      this.element.style.top = "0";
    }
  },
  onDragStart: tm.fnEmpty,
  onDragging: tm.fnEmpty,
  onDragEnd: tm.fnEmpty,
  isRevert: function() {
    return this.revert;
  },
  isOpacity: function() {
  	return this.opacity;
  },
  _findAvailNextSibling: function(elem) {
    var sibling = elem;
    while (sibling = sibling.nextSibling) {
      if (sibling.nodeType != 3) {
        return sibling;
      }
    }
    return null;
  }
});


tm.dnd.Droppable = Class.create();
tm.extend(tm.dnd.Droppable.prototype, {
  initialize: function(elem, args) {
  	args = args ? args : {};
  	var fields = {
      draggables: new tm.util.Set(),
      currentMoveIndex: 65535
  	}
  	tm.extend(this, fields, args);
  	this.element = typeof(elem) == "string" ? $(elem) : elem;
  },
  addDraggable: function(d) {
    if (!this.draggables.contains(d)) {
      d.droppable = this;
      this.draggables.add(d);
      this.element.appendChild(d.element);
    }
  },
  removeDraggable: function(d) {
    if (this.draggables.contains(d)) {
      d.droppable = null;
      this.draggables.removeObject(d);
      this.element.removeChild(d.element);
    }
  },
  hasDraggable: function(d) {
    return this.draggables.contains(d);
  },
  onDragStart: tm.fnEmpty,
  onDrag: tm.fnEmpty,
  onDrop: tm.fnEmpty
});


tm.dnd.Droppables = {
  drops: [],
  dropTmpDiv: false,
  getDropTmpDiv: function() {
  	if (!this.dropTmpDiv) {
  	  var elem = document.createElement("DIV");
  	  elem.style.margin = "6px 3px 0 3px";
  	  elem.style.border = "1px dashed red";
  	  elem.style.display = "none";
  	  document.body.appendChild(elem);
  	  
      this.dropTmpDiv = elem;
  	}
  	return this.dropTmpDiv;
  },
  hideDropTmpDiv: function() {
  	var dtd = this.getDropTmpDiv();
  	dtd.style.display = "none";
  	if (dtd.parentNode) {
  	  dtd.parentNode.removeChild(dtd);
  	}
  	document.body.appendChild(dtd);
  },
  add: function(dp) {
    this.drops.push(dp);
  },
  addDraggable: function(dg, dp) {
  	dp.addDraggable(dg);
  },
  removeDraggable: function(dg) {
  	for (var i = 0; i < this.drops.length; i++) {
	  this.drops[i].removeDraggable(dg);
  	}
  },
  detectDragStart: function(e) {
    tm.Position.prepare();
    var p = tm.Event.pointer(e);
    var droppable;
    var dgs;
    for (var i = 0; i < this.drops.length; i++) {
      droppable = this.drops[i];
      if (tm.Position.within(droppable.element, p.x, p.y)) {
        droppable.onDragStart(e);
//        droppable.draggables.removeObject(tm.dnd.Draggables.draggable);
        break;
      }
    }
    for (var i = 0; i < this.drops.length; i++) {
      droppable = this.drops[i];
//      droppable.currentMoveIndex = 65535;
      dgs = droppable.draggables;
      for (var j = 0; j < dgs.size(); j++) {
      	dgs.get(j).resetBound();
      }
      dgs.sort(function(a, b) { return a.bound.y - b.bound.y; });
    }
  },
  detectHover: function(e) {
    tm.Position.prepare();
    var p = tm.Event.pointer(e);
    var droppable;
    for (var i = 0; i < this.drops.length; i++) {
      droppable = this.drops[i];
      //检查指针在哪个droppable内
      if (tm.Position.within(droppable.element, p.x, p.y)) {
      	for (var j = 0; j < this.drops.length; j++) {
      	  if (this.drops[j] != droppable) {
      	  	this.drops[j].currentMoveIndex = 65535;
      	  }
      	}
      	var dgs = droppable.draggables;
      	var draggable = tm.dnd.Draggables.draggable;
      	//如果droppable内无draggable或者只有正在拖动的draggable
      	var index = -1;
		
		var dg = null;
		var bd = {x: 0, y: 0, w: 0, h: 0};
		var isSelf = false;
		if (dgs.size() == 0) {
		  index = -2048;
		} else if (dgs.size() == 1) {
		  dg = dgs.get(0);
		  if (dg == draggable) {
		  	index = 1024;
		  } else {
		  	if (droppable.currentMoveIndex == 1023) {
		  	  tm.extend(bd, dg.bound, {y: dg.bound.y + this.getDropTmpDiv().offsetHeight + 8});
		  	} else if (droppable.currentMoveIndex == 1025) {
		  	  tm.extend(bd, dg.bound);
		  	} else {
		  	  tm.extend(bd, dg.bound);
		  	}
		  	if (this.withinUpH(bd, p.y) || bd.y >= p.y) {
		  	  index = 1023;
		  	} else if (this.withinDownH(bd, p.y) || bd.y + bd.h <= p.y) {
		  	  index = 1025;
		  	}
		  }
		} else {
		  var prevDg, nextDg;
		  var offsetH = this.getDropTmpDiv().offsetHeight + 8;
          var selfIndex = dgs.indexOf(draggable);
		  for (var j = 0; j < dgs.size(); j++) {
		  	dg = dgs.get(j);
		  	
		  	prevDg = j == 0 ? null : dgs.get(j - 1);
		  	nextDg = j == dgs.size() - 1 ? null : dgs.get(j + 1);
		  	nextNextDg = j == dgs.size() - 2 ? null : dgs.get(j + 2);
		    if (j >= droppable.currentMoveIndex) {
		      if (selfIndex != -1 && j >= selfIndex) {
		      	tm.extend(bd, dg.bound, {y: dg.bound.y + offsetH - draggable.bound.h});
		      } else {
		      	tm.extend(bd, dg.bound, {y: dg.bound.y + offsetH});
		      }
		    } else {
		      if (selfIndex != -1 && j >= selfIndex) {
		      	tm.extend(bd, dg.bound - draggable.bound.h);
		      } else {
		        tm.extend(bd, dg.bound);
		      }
		    }
		    if (this.withinUpH(bd, p.y)) {
		      if (dg == draggable) {
		      	if (nextDg) {
		      	  index = j;
		      	  isSelf = true;
		      	} else {
		      	  index = dgs.size();
		      	}
		      } else {
		  	    index = j;
		      }
		      break;
		  	} else if (this.withinDownH(bd, p.y)) {
              if (nextDg && nextDg == draggable) {
              	if (nextNextDg) {
              	  index = j + 2;
              	} else {
              	  index = dgs.size();
              	}
              } else {
                index = j + 1;
              }
		  	  break;
		  	} else if (p.y <= bd.y) {
		  	  if (prevDg) {
		  	  	 if (p.y >= prevDg.bound.y) {
			      if (dg == draggable) {
			      	index = j;
			      	isSelf = true;
			      } else {
			  	    index = j;
			      }
		  	  	 }
		  	  } else {
			      if (dg == draggable) {
			      	index = j;
			      	isSelf = true;
			      } else {
			  	    index = j;
			      }
		  	  }
		      break;
		  	} else if (p.y >= bd.y + bd.h) {
		  	  if (nextDg) {
		  	    if (p.y <= nextDg.bound.y) {
			      if (dg == draggable) {
			      	index = j + 1;
			      } else {
			        if (nextDg == draggable) {
			          if (nextNextDg) {
			          	index = j + 2;
			          } else {
			          	index = dgs.size();
			          }
			        } else {
			          index = j + 1;
			        }
			      }
			      break;
		  	    }
		  	  } else {
		  	    index = j + 1;
		  	    break;
		  	  }
		  	}
		  }
		}
		
//		window.status = dgs.size() + "_" + index + "_" + droppable.currentMoveIndex;
		if (index != droppable.currentMoveIndex) {
		  droppable.currentMoveIndex = index;
		  this.getDropTmpDiv().style.display = "block";
		  this.getDropTmpDiv().style.height = draggable.bound.h + "px";
		  this.getDropTmpDiv().parentNode.removeChild(this.getDropTmpDiv());
		  if (index == -2048) {
		  	droppable.element.appendChild(this.getDropTmpDiv());
		  } else if (index == 1024) {
		  	droppable.element.appendChild(this.getDropTmpDiv());
		  } else if (index == 1023) {
		  	droppable.element.insertBefore(this.getDropTmpDiv(), droppable.draggables.get(0).element);
		  } else if (index == 1025) {
		  	droppable.element.appendChild(this.getDropTmpDiv());
		  } else if (index == dgs.size()) {
		  	droppable.element.appendChild(this.getDropTmpDiv());
		  } else {
		  	if (isSelf) {
		  	  droppable.element.insertBefore(this.getDropTmpDiv(), droppable.draggables.get(index + 1).element);  
		  	} else {
		  	  droppable.element.insertBefore(this.getDropTmpDiv(), droppable.draggables.get(index).element);
		  	}
		  }
//		  document.title = new Date().getTime();
		}
		
        //all back
        droppable.onDrag(e);
        break;
      }
    }
  },
  detectDrop: function(e) {
    var draggable = tm.dnd.Draggables.draggable;
    
    if (draggable.hasMoveElem) {
      tm.Position.prepare();
      var p = tm.Event.pointer(e);
      var droppable;
    
	  draggable.element.style.position = "relative";
	  draggable.element.style.left = "0";
	  draggable.element.style.top = "0";
	    
	  for (var i = 0; i < this.drops.length; i++) {
	    droppable = this.drops[i];
	    if (tm.Position.within(droppable.element, p.x, p.y)) {
	      droppable.onDrop(e);
	      
	      var index = droppable.currentMoveIndex;
	      if (index == 65535) {
	      	;
	      } else if (index == -2048) {
		  	draggable.endPoint = { sibling: null, parent: droppable.element };
		  } else if (index == 1024) {
		  	draggable.endPoint = { sibling: null, parent: droppable.element };
		  } else if (index == 1023) {
		  	draggable.endPoint = { sibling: droppable.draggables.get(0).element, parent: droppable.element };
		  } else if (index == 1025) {
		  	draggable.endPoint = { sibling: null, parent: droppable.element };
		  } else if (index == droppable.draggables.size()) {
		  	draggable.endPoint = { sibling: null, parent: droppable.element };
		  } else {
			draggable.endPoint = { sibling: droppable.draggables.get(index).element, parent: droppable.element };
		  }          
          draggable.element.style.width = "";
          
          for (var i = 0; i < this.drops.length; i++) {
	        this.drops[i].draggables.removeObject(draggable);
	        this.drops[i].currentMoveIndex = 65535;
	      }
          droppable.draggables.add(draggable);
          draggable.droppable = droppable;
	      break;
	    }
	  }
	  this.hideDropTmpDiv();
    }
  }, 
  within: function(bound, x, y) {
  	return x > bound.x
  	    && x < bound.x + bound.w
  	    && y > bound.y
  	    && y < bound.y + bound.h;
  },
  withinUpH: function(bound, y) {
  	return y > bound.y && y <= bound.y + bound.h / 2;
  },
  withinDownH: function(bound, y) {
  	return y > bound.y + bound.h / 2 && y < bound.y + bound.h;
  }
};
