/*
 * jsPlumb
 * 
 * Title:jsPlumb 1.5.4
 * 
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
 * elements, or VML.  
 * 
 * This file contains the MooTools adapter.
 *
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
 * 
 * http://jsplumb.org
 * http://github.com/sporritt/jsplumb
 * http://code.google.com/p/jsplumb
 * 
 * Dual licensed under the MIT and GPL2 licenses.
 */

;(function() {
	
	/*
	 * overrides the FX class to inject 'step' functionality, which MooTools does not
	 * offer, and which makes me sad.  they don't seem keen to add it, either, despite
	 * the fact that it could be useful:
	 * 
	 * https://mootools.lighthouseapp.com/projects/2706/tickets/668
	 * 
	 */
	var jsPlumbMorph = new Class({
		Extends:Fx.Morph,
		onStep : null,
		initialize : function(el, options) {
			this.parent(el, options);
			if (options.onStep) {
				this.onStep = options.onStep;
			}
		},
		step : function(now) {
			this.parent(now);
			if (this.onStep) { 
				try { this.onStep(); } 
				catch(e) { } 
			}
		}
	});
	
	var _droppables = {},
	_droppableOptions = {},
	_draggablesByScope = {},
	_draggablesById = {},
	_droppableScopesById = {};
	/*
	 * 
	 */
	var _executeDroppableOption = function(el, dr, event, originalEvent) {
		if (dr) {
			var id = dr.get("id");
			if (id) {
				var options = _droppableOptions[id];
				if (options) {
					if (options[event]) {
						options[event](el, dr, originalEvent);
					}
				}
			}
		}
	};	
	
	var _checkHover = function(el, entering) {
		if (el) {
			var id = el.get("id");
			if (id) {
				var options = _droppableOptions[id];
				if (options) {
					if (options.hoverClass) {
						if (entering) el.addClass(options.hoverClass);
						else el.removeClass(options.hoverClass);
					}
				}
			}
		}
	};

	/**
	 * adds the given value to the given list, with the given scope. creates the scoped list
	 * if necessary.
	 * used by initDraggable and initDroppable.
	 */
	var _add = function(list, _scope, value) {
		var l = list[_scope];
		if (!l) {
			l = [];
			list[_scope] = l;
		}
		l.push(value);
	};
	
	/*
	 * gets an "element object" from the given input.  this means an object that is used by the
	 * underlying library on which jsPlumb is running.  'el' may already be one of these objects,
	 * in which case it is returned as-is.  otherwise, 'el' is a String, the library's lookup 
	 * function is used to find the element, using the given String as the element's id.
	 */
	var _getElementObject = function(el) {
	  return $(el);
	};
		
	jsPlumb.CurrentLibrary = {				
		
		/**
		 * adds the given class to the element object.
		 */
		addClass : function(el, clazz) {
			el = jsPlumb.CurrentLibrary.getElementObject(el);
			try {
				if (el.className.constructor == SVGAnimatedString) {
					jsPlumbUtil.svg.addClass(el, clazz);
				}
				else el.addClass(clazz);
			}
			catch (e) {				
				// SVGAnimatedString not supported; no problem.
				el.addClass(clazz);
			}						
		},	
			
		animate : function(el, properties, options) {			
			var m = new jsPlumbMorph(el, options);
			m.start(properties);
		},
		
		appendElement : function(child, parent) {
			_getElementObject(parent).grab(child);			
		},
		
		bind : function(el, event, callback) {
			var els = jsPlumbUtil.isString(el) || typeof el.length == "undefined" ? [ _getElementObject(el) ] : $$(el);
			//el = _getElementObject(el);
			for (var i = 0; i < els.length; i++)
				els[i].addEvent(event, callback);
		},

		destroyDraggable : function(el) {
			// TODO
			var id = jsPlumb.getId(el), d = _draggablesById[id];
			if (d) {
				for (var i = 0; i < d.length; i++)
					d[i].detach();

				delete _draggablesById[id];
			}
		},

		destroyDroppable : function(el) {
			// TODO
		},
		
		dragEvents : {
			'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep',
			'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete'
		},

		/*
		 * wrapper around the library's 'extend' functionality (which it hopefully has.
		 * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
		 * instead.  it's not like its hard.
		 */
		extend : function(o1, o2) {
			return $extend(o1, o2);
		},
		
		getClientXY : function(eventObject) {
			return [eventObject.event.clientX, eventObject.event.clientY];
		},
		
		getDragObject : function(eventArgs) {
			return eventArgs[0];
		},
		
		getDragScope : function(el) {
			var id = jsPlumb.getId(el),
			    drags = _draggablesById[id];
			return drags[0].scope;
		},
	
		getDropEvent : function(args) {			
			return args[2];
		},
		
		getDropScope : function(el) {
			var id = jsPlumb.getId(el);
			return _droppableScopesById[id];
		},
		
		getDOMElement : function(el) { 
			if (el == null) return null;
			// MooTools just decorates the DOM elements. so we have either an ID or an Element here.
			return typeof(el) == "string" ? document.getElementById(el) : el; 
		},
							
		getElementObject : _getElementObject,
		
		/*
		  gets the offset for the element object.  this should return a js object like this:
		  
		  { left:xxx, top: xxx}
		 */
		getOffset : function(el) {
			var p = el.getPosition();
			return { left:p.x, top:p.y };
		},	
		
		getOriginalEvent : function(e) {
			return e.event;
		},			
		
		getPageXY : function(eventObject) {
			return [eventObject.event.pageX, eventObject.event.pageY];
		},
		
		getParent : function(el) {
			return jsPlumb.CurrentLibrary.getElementObject(el).getParent();
		},
		
		getScrollLeft : function(el) {
			return null;
		},
		
		getScrollTop : function(el) {
			return null;
		},
		
		getSelector : function(context, spec) {
            if (arguments.length == 2) {
                return jsPlumb.CurrentLibrary.getElementObject(context).getElements(spec);
            }
            else
			     return $$(context);
		},
		
		getSize : function(el) {
			var s = el.getSize();
			return [s.x, s.y];
		},

        getTagName : function(el) {
            var e = jsPlumb.CurrentLibrary.getElementObject(el);
            return e != null ? e.tagName : null;
        },
		
		/*
		 * takes the args passed to an event function and returns you an object that gives the
		 * position of the object being moved, as a js object with the same params as the result of
		 * getOffset, ie: { left: xxx, top: xxx }.
		 */
		getUIPosition : function(eventArgs, zoom) {
		  var ui = eventArgs[0],
			  el = jsPlumb.CurrentLibrary.getElementObject(ui),
			  p = el.getPosition();
			
		  zoom = zoom || 1;		  
			
		  return { left:p.x / zoom, top:p.y / zoom};
		},		
		
		hasClass : function(el, clazz) {
			return el.hasClass(clazz);
		},
		
		initDraggable : function(el, options, isPlumbedComponent, _jsPlumb) {
			var id = jsPlumb.getId(el);
			var drag = _draggablesById[id];
			if (!drag) {
				var originalZIndex = 0,
                    originalCursor = null,
				    dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000;
                
				options.onStart = jsPlumbUtil.wrap(options.onStart, function() {
                    originalZIndex = this.element.getStyle('z-index');
					this.element.setStyle('z-index', dragZIndex);
                    drag.originalZIndex = originalZIndex;
					if (jsPlumb.Defaults.DragOptions.cursor) {
						originalCursor = this.element.getStyle('cursor');
						this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor);
					}
					$(document.body).addClass(_jsPlumb.dragSelectClass);
				});
				
				options.onComplete = jsPlumbUtil.wrap(options.onComplete, function() {
					this.element.setStyle('z-index', originalZIndex);
					if (originalCursor) {
						this.element.setStyle('cursor', originalCursor);
					}                    
					$(document.body).removeClass(_jsPlumb.dragSelectClass);
				});
				
				// DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element
                // draggable.  this is the only library adapter that has to care about this parameter.
				var scope = "" + (options.scope || jsPlumb.Defaults.Scope),
				    filterFunc = function(entry) {
					    return entry.get("id") != el.get("id");
				    },
				    droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : [];

                if (isPlumbedComponent) {

				    options.droppables = droppables;
				    options.onLeave = jsPlumbUtil.wrap(options.onLeave, function(el, dr) {
		    			if (dr) {
			    			_checkHover(dr, false);
				    		_executeDroppableOption(el, dr, 'onLeave');
					    }
				    });
				    options.onEnter = jsPlumbUtil.wrap(options.onEnter, function(el, dr) {
					    if (dr) {
						    _checkHover(dr, true);
						    _executeDroppableOption(el, dr, 'onEnter');
					    }
				    });
				    options.onDrop = function(el, dr, event) {
					    if (dr) {
						    _checkHover(dr, false);
						    _executeDroppableOption(el, dr, 'onDrop', event);
					    }
				    };
                }
                else
                    options.droppables = [];
				
				drag = new Drag.Move(el, options);
				drag.scope = scope;
                drag.originalZIndex = originalZIndex;
                _add(_draggablesById, el.get("id"), drag);
				// again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint)
                if (isPlumbedComponent) {
				    _add(_draggablesByScope, scope, drag);
                }
				// test for disabled.
				if (options.disabled) drag.detach();
			}
			return drag;
		},
		
		initDroppable : function(el, options, isPlumbedComponent, isPermanent) {
			var scope = options.scope;
            _add(_droppables, scope, el);
			var id = jsPlumb.getId(el);

            el.setAttribute("_isPermanentDroppable", isPermanent);  // we use this to clear out droppables on drag complete.
			_droppableOptions[id] = options;
			_droppableScopesById[id] = scope;
			var filterFunc = function(entry) { return entry.element != el; },
			    draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : [];
			for (var i = 0; i < draggables.length; i++) {
				draggables[i].droppables.push(el);
			}
		},
		
		isAlreadyDraggable : function(el) {
			return _draggablesById[jsPlumb.getId(el)] != null;
		},										
		
		isDragSupported : function(el, options) {
			return typeof Drag != 'undefined' ;
		},	
		
		/*
		 * you need Drag.Move imported to make drop work.
		 */
		isDropSupported : function(el, options) {
			return (typeof Drag !== undefined && typeof Drag.Move !== undefined);
		},
		
		/**
		 * removes the given class from the element object.
		 */
		removeClass : function(el, clazz) {
			el = jsPlumb.CurrentLibrary.getElementObject(el);
			try {
				if (el.className.constructor == SVGAnimatedString) {
					jsPlumbUtil.svg.removeClass(el, clazz);
				}
				else el.removeClass(clazz);
			}
			catch (e) {				
				// SVGAnimatedString not supported; no problem.
				el.removeClass(clazz);
			}
		},
		
		removeElement : function(element, parent) {
            var el = _getElementObject(element);
			if (el) el.dispose();  // ??
		},

		setDragFilter : function(el, filter) {
			jsPlumbUtil.log("NOT IMPLEMENTED: setDragFilter");
		},
		
		setDraggable : function(el, draggable) {
			var draggables = _draggablesById[el.get("id")];
			if (draggables) {
				draggables.each(function(d) {
					if (draggable) d.attach(); else d.detach();
				});
			}
		},
		
		setDragScope : function(el, scope) {
			var drag = _draggablesById[el.get("id")];
			var filterFunc = function(entry) {
				return entry.get("id") != el.get("id");
			};
			var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : [];
			drag[0].droppables = droppables;
		},
		
		setOffset : function(el, o) {
			_getElementObject(el).setPosition({x:o.left, y:o.top});
		},

        stopDrag : function() {
            for (var i in _draggablesById) {
                for (var j = 0; j < _draggablesById[i].length; j++) {
                    var d = _draggablesById[i][j];
                    d.stop();
                    if (d.originalZIndex !== 0)
                        d.element.setStyle("z-index", d.originalZIndex);
                }
            }
        },
		
		/**
		 * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
		 * the other libraries do not.  yui, in fact, cannot even pass an original event.  we have to pull out stuff
		 * from the originalEvent to put in an options object for YUI. 
		 * @param el
		 * @param event
		 * @param originalEvent
		 */
		trigger : function(el, event, originalEvent) {
			originalEvent.stopPropagation();
			_getElementObject(el).fireEvent(event, originalEvent);
		},
		
		unbind : function(el, event, callback) {
			el = _getElementObject(el);
			el.removeEvent(event, callback);
		}
	};
	
	window.addEvent('domready', jsPlumb.init);
})();
