/**
 * Copyright (C) 2009 eXo Platform SAS.
 * 
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

/**
 * This class manages the drag and drop of components on the page.
 * It uses a DradDrop object to manage the events, sets some callback functions
 * and some parameters to initialize the DragDrop object.
 */

(function($, base, common) {	
	eXo.portal.PortalDragDrop = { 
		
		/**
		 * This function inits the PortalDragDrop object
		 * It initializes a DragDrop object that will manage the drag events
		 */
		init : function(dragClasses) {
	      if (!dragClasses || !dragClasses.length) {
			return;
		  }
			
	      $(".UIPageBody").find(".DragControlArea, .uiIconDragDrop").off("mouseover").on("mouseover", function() {
	        if(eXo.portal.portalMode == 1 || eXo.portal.portalMode == 2) {
	          this.style.cursor ='move';
	        } else {
	          this.style.cursor ='default';    		  
	        }
	      });
	      
		  var browser = base.Browser;
		  var DragDrop = common.DragDrop;
		  var PortalDragDrop = eXo.portal.PortalDragDrop;
		  
		  var previewBlock = null;
			/**
			 * This function is called after the DragDrop object is initialized
			 */
		  var initCallback = function (x, y, mouseX, mouseY, e) {	  	
			if (eXo.portal.isInDragging) return;		  
			var dragObject = this, jDragObj = $(this);
			
			var origDragObjectStyle = {};
		    var properties = ["top", eXo.core.I18n.isLT() ? "left" : "right", "zIndex", "opacity", "filter", "position", "width"];
		    $.each(properties, function(idx, elem) {
		    	origDragObjectStyle[elem] = dragObject.style[elem];
		    });
		    jDragObj.data("origDragObjectStyle", origDragObjectStyle);
		    
		    var isAddingNewly = jDragObj.children(".UIComponentBlock").length == 0;
			var originalDragObjectTop = y;
			var originalDragObjectLeft = x;
			if (!isAddingNewly && browser.isIE7()) {
				originalDragObjectLeft = browser.findPosXInContainer(dragObject, $("#UIWorkingWorkspace")[0]);
			}
				
		    //use this when press ESC with firefox (cancel dragdrop in column container)
		    jDragObj.data("backupParentSibling", jDragObj.parent().next("td"));	  
		    var backupDragObjectWidth = dragObject.offsetWidth;
		        
		    var componentBlockWidth = 300;
		    if(isAddingNewly) {
		      var cloneObject = jDragObj.clone(true, true);
		      jDragObj.before(cloneObject);
		      DragDrop.init(cloneObject[0], cloneObject[0]);
		      cloneObject[0].onDragStart = initCallback;
		      cloneObject[0].onDrag = dragCallback;
		      cloneObject[0].onDragEnd = dropCallback;
		      
		      jDragObj.width(backupDragObjectWidth);
		      jDragObj.fadeTo(0, 0.5);
		    } else {
		        previewBlock = PortalDragDrop.createPreview();
		    	dragObject.parentNode.insertBefore(previewBlock, dragObject);
		    	jDragObj.width(componentBlockWidth).find(".EDITION-BLOCK .NewLayer").each(function() {
		    		$(this).width(componentBlockWidth);
		    	});	    	
		    }
		    
		    dragObject.isAddingNewly = isAddingNewly;	    
		    dragObject.style.position = "absolute";
		    dragObject.style.top = originalDragObjectTop + "px";
		    var dragObjectLeft = originalDragObjectLeft;
		    
		    var objAndMouse = browser.findMouseRelativeX(dragObject.offsetParent, e) - originalDragObjectLeft;
		    if (objAndMouse > componentBlockWidth/2) {
		    	var tmp = (objAndMouse*componentBlockWidth)/backupDragObjectWidth;
		    	dragObjectLeft = originalDragObjectLeft + objAndMouse - tmp;
		    }	       
		    
		    if (eXo.core.I18n.isRT() && !isAddingNewly && !jDragObj.hasClass("UIPageBody")) {
		    	dragObject.style.left = originalDragObjectLeft + backupDragObjectWidth - componentBlockWidth + "px";
		    } else {
		    	//rt and lt are the same for UIPageBody and newly added obj
		    	dragObject.style.left = dragObjectLeft + "px";
		    }
		    
		    jDragObj.data("dropableTargets", PortalDragDrop.findDropableTargets(dragObject));
		    eXo.portal.isInDragging = true;
		  };
		  
		   var dragCallback = function(nx, ny, ex, ey, e) {
		     var dragObject = this, jDragObj = $(this);
		     /* Control Scroll */
		     eXo.portal.PortalDragDrop.scrollOnDrag(dragObject, e);
		    
		     var foundTarget = PortalDragDrop.findTarget(dragObject, ex, ey) || jDragObj.data("lastFoundTargetObject");
		     if (!foundTarget) return;	     
		     jDragObj.data("lastFoundTargetObject", jDragObj.data("foundTargetObject"));
		     jDragObj.data("foundTargetObject", foundTarget);	     
		    
		     var jTarget = $(foundTarget);
		     var uiComponentLayout;
		     if(foundTarget.className == "UIPage") {
			   uiComponentLayout = jTarget.find(".VIEW-PAGE").first();
		     } else if(foundTarget.className == "UIPortal") {
		       if(eXo.portal.portalMode % 2) uiComponentLayout = jTarget.find(".LAYOUT-PORTAL").first();
		       else uiComponentLayout = jTarget.find(".VIEW-PORTAL").first();
		     } else {
		       var foundUIComponent = $(foundTarget);
		       if(eXo.portal.portalMode % 2) uiComponentLayout = foundUIComponent.find(".LAYOUT-BLOCK").first();
		       else uiComponentLayout = foundUIComponent.find(".VIEW-BLOCK").first();
		       uiComponentLayout.css("height", "auto");
		     }
		    
		     var componentIdElement = uiComponentLayout.children("div").first();
		     var layoutTypeElement = componentIdElement.children("div");
		     layoutTypeElement = layoutTypeElement.length == 0 ? null : layoutTypeElement[0];
		     PortalDragDrop.layoutTypeElementNode = layoutTypeElement;
		    
		     if(previewBlock == null) previewBlock = PortalDragDrop.createPreview();	    	     
		     if(layoutTypeElement != null && !$(layoutTypeElement).hasClass("UITableColumnContainer")) {
		      /* ===============================CASE ROW LAYOUT================================ */
		      var rowContainer = jTarget.find(".UIRowContainer").first();
		      var listComponent = rowContainer.children("div").filter(function() {
		    	  return this.className != "DragAndDropPreview" && this != dragObject;
		      });	      	      
		      
		      jDragObj.data("listComponentInTarget", listComponent);
		      var insertPosition = eXo.portal.PortalDragDrop.findInsertPosition(listComponent, "row", ey);
		      if (jDragObj.data("foundTargetObject") === jDragObj.data("lastFoundTargetObject") && 
		    		  insertPosition === jDragObj.data("foundIndex")) return;	      
		      jDragObj.data("foundIndex", insertPosition);
		      
		      /* Insert preview block */
		      if(insertPosition >= 0) {
		        rowContainer[0].insertBefore(previewBlock, listComponent[insertPosition]);
		      } else {
		        rowContainer[0].appendChild(previewBlock);
		      }
		    } else {
		      /* ===============================CASE COLUMN LAYOUT================================ */
		      var trContainer = jTarget.find(".TRContainer").first();
		      var listComponent = trContainer.children("td").filter(function() {
		    	 return this != previewBlock.parentNode && this != dragObject.parentNode; 
		      });
		      
		      var insertPosition = eXo.portal.PortalDragDrop.findInsertPosition(listComponent, "column", ex);
		      if (jDragObj.data("foundTargetObject") === jDragObj.data("lastFoundTargetObject") && 
		    		  insertPosition === jDragObj.data("foundIndex")) return;
		      jDragObj.data("foundIndex", insertPosition);
		      
		      /* Insert preview block */
		      if(insertPosition >= 0) {
		          trContainer[0].insertBefore(dragObject.parentNode, listComponent[insertPosition]);
		       } else {
		          trContainer[0].appendChild(dragObject.parentNode);
		       }
		    }
	
		    var dragParent = $(dragObject).parent();
		    if (dragParent.children("div").length == 1 && !dragParent.hasClass("EmptyContainer")) {
		    	dragParent.addClass("EmptyContainer");
		    }
		  };
	
		  var dropCallback = function(x, y, clientX, clientY, e) {
		    var hasChanged = true;
		    var dragObject = this, jDragObj = $(this);
			//When press esc key, we want to cancel the dragdrop
		  	if (e.which === 27) {
		  		hasChanged = false;
		  	}
		  	//When dragObject is outside 
		  	if (!jDragObj.data("foundTargetObject")) {
		  		jDragObj.data("foundTargetObject", jDragObj.data("lastFoundTargetObject"));
		  	}
		  	
		    var targetElement = jDragObj.data("foundTargetObject");
		    var foundIndex = jDragObj.data("foundIndex");
		    if(!targetElement || foundIndex == undefined) {
		       hasChanged = false;
		    }
	//			 Case RowContainer : When dragobject is next to preview object (position is not changed)
	//		    Case ColumnContainer : When dragObject.parent's lastSibling doesn't change
		  	if(!dragObject.isAddingNewly) {
		      if (dragObject.parentNode.tagName.toLowerCase() == "td") {
		        //Column Container
		        var backupParentSibling = jDragObj.data("backupParentSibling"); 
		        var currSibling = jDragObj.parent().next("td");
		        if ((backupParentSibling.length == 0 && currSibling.length == 0) || 
		        		currSibling[0] == backupParentSibling[0]) {
		          hasChanged = false;          
		        }
		      } else {
		        //RowContainer
		    	var next = jDragObj.next("div.DragAndDropPreview");
		    	var prev = jDragObj.prev("div.DragAndDropPreview");
		        if (next.length || prev.length) {
		          hasChanged = false;
		        }
		      }
		  	}
            
		    if(!dragObject.isAddingNewly) {
		    	jDragObj.find(".EDITION-BLOCK").last().hide();
		    	$.extend(dragObject.style, jDragObj.data("origDragObjectStyle"));
		    }
		  	
		    if(e.which !== 27) {
		    	eXo.portal.PortalDragDrop.doDropCallback(dragObject);
		    } else {
		      //When click ESC, restore dragObject's last position
		      if (dragObject.parentNode && dragObject.parentNode.tagName.toLowerCase() == "td") {
		        var tdNode = dragObject.parentNode;
		        var lastSibling = jDragObj.data("backupParentSibling");
		        if (lastSibling.length == 0) {
		          tdNode.parentNode.appendChild(tdNode);
		        } else {
		          tdNode.parentNode.insertBefore(tdNode, lastSibling[0]);
		        }
		      }
		      
		      if(dragObject.isAddingNewly) {
				dragObject.parentNode.removeChild(dragObject);
			  }
		    }		   		    
		    
		    if(previewBlock) previewBlock.parentNode.removeChild(previewBlock);
		    previewBlock = null;
		    
		    eXo.portal.isInDragging = false;
		    if (hasChanged) {
		   	 window.require(["SHARED/portalComposer"], function(composer) {
		   		 composer.toggleSaveButton();	   		 
		   	 });
		    }
		    
		    jDragObj.removeData();
		  };
		  
		  dragClasses = dragClasses.join(",.");
		  $("." + dragClasses).each(function() {
			  var dragBlock = this;	  
			  var clickObject = $(this).find(".DragControlArea, .uiIconDragDrop").last();
	
			  if(clickObject.length != 0) {
				  //object existing in the current layout
				  clickObject = clickObject[0];
			  } else {
				  //from the popup composer to add newly
				  clickObject = dragBlock;
			  }
			  DragDrop.init(clickObject, dragBlock);
			  
			  dragBlock.onDragStart = initCallback;
			  dragBlock.onDrag = dragCallback;
			  dragBlock.onDragEnd = dropCallback;
		  });	 	 
		},
	
		/**
		 * Perform following works after dropping :
		 * 
		 * 1. Remove the dragging object if any
		 * 2. Send an request to server side to update the changes
		 */
		doDropCallback : function(dragObject) {
		  var srcElement = dragObject, jDragObj = $(dragObject);
		  var PortalDragDrop = eXo.portal.PortalDragDrop;
		  var targetElement = jDragObj.data("foundTargetObject");
		  var foundIndex = jDragObj.data("foundIndex")
		  
		  if(!targetElement || foundIndex == undefined) {
		  	if(dragObject.isAddingNewly) {
			    dragObject.parentNode.removeChild(dragObject);
		  	} else {
		  		//Make IE7 rerender dom correctlly, this is a workaround for EXOGTN-1092
		  		dragObject.parentNode.replaceChild(dragObject, dragObject);
		  	}
		  	return;
		  }
		  
		  if(!srcElement.isAddingNewly && (foundIndex != undefined)) {
		    if(PortalDragDrop.layoutTypeElementNode != null) {
		      eXo.portal.PortalDragDrop.divRowContainerAddChild(srcElement, targetElement, foundIndex);
		    }
		  }
	
		  if(srcElement.isAddingNewly) {
		    jDragObj.remove();
		  }
		  
		  var params = [
		    {name: "srcID", value: (srcElement.id.replace(/^UIPortlet-/, ""))},
		    {name: "targetID", value: targetElement.id.replace(/^.*-/, "")},
		    {name: "insertPosition", value: foundIndex},
		    {name: "isAddingNewly", value: srcElement.isAddingNewly}
		  ] ;
	
		  // Modified by Philippe : added callback function
		  ajaxGet(eXo.env.server.createPortalURL("UIPortal", "MoveChild", true, params));
		},
	
		/**
		 * Return an array of droppable target objects
		 * 
		 * @param the dragging object
		 */
		findDropableTargets : function(dragBlock) {
		  var dropableTargets = new Array();
		  var jDragObj = $(dragBlock);
	
	      if (jDragObj.hasClass("UIColumnContainer")) {
		    var uiTableContainer = jDragObj.closest(".UITableColumnContainer");
		    dropableTargets.push(uiTableContainer[0]);
		    return dropableTargets;
		  }
	
	      var toolPanel = $("#UIPortalToolPanel");
	      
	      var uiRoot = eXo.portal.portalEditLevel == "EDIT_PAGE" ? toolPanel
					.find(".UIPage") : toolPanel.find(".UIPortal");
		  if (uiRoot.length && eXo.portal.UIPortal.canMoveComponent(dragBlock, uiRoot[0])) {
		    dropableTargets.push(uiRoot[0]);
		  }

		  var uiContainers = uiRoot.find(".UIContainer");
		  uiContainers.each(function() {
			var jCont = $(this);
			if (!jCont.closest(jDragObj).length
						&& !jCont.hasClass("ProtectedContainer")
						&& !jCont.hasClass("UITableColumnContainer")
						&& eXo.portal.UIPortal.canMoveComponent(dragBlock, this)) {
			  dropableTargets.push(this);
			}
		  });
		  return dropableTargets;
		},
	
		scrollOnDrag : function(dragObject, e) {
		  var jWin = $(window);
		  var browserHeight = jWin.height();
		  var browserWidth = jWin.width();
		  var mouseY = e.clientY || e.originalEvent.touches[0].clientY;
		  var mouseX = e.clientX || e.originalEvent.touches[0].clientX;
		  var deltaTop = mouseY - (Math.round(browserHeight * 5 / 6));
		  var deltaBottom = mouseY - (Math.round(browserHeight / 6));
		  var deltaLeft = mouseX - (Math.round(browserWidth * 5 / 6));
		  var deltaRight = mouseX - (Math.round(browserWidth / 6));
		  var scrollTop = jWin.scrollTop();
		  var scrollLeft = jWin.scrollLeft();
		  if (deltaTop > 0) {
			jWin.scrollTop(scrollTop + deltaTop - 5);
		  }

		  if (deltaBottom < 0 && scrollTop > 0) {
			jWin.scrollTop(scrollTop + deltaBottom);
		  }

		  if (deltaLeft > 0) {
			jWin.scrollLeft(scrollLeft + deltaLeft - 5);
		  }

		  if (deltaRight < 0 && scrollLeft > 0) {
			jWin.scrollLeft(scrollLeft + deltaRight);
		  }	  
		},
	
		/**
		 * Return a most suiable position among the <code>components</code> objects
		 * that the dragging object should be at
		 * 
		 * @param layout {string} the layout type which is "row" or "column"
		 */
		findInsertPosition : function(components, layout, mousePos) {
		   var browser = base.Browser;
		   if (layout == "row") {
		      for (var i = 0; i < components.length; i++) {
		    	  var componentTop = $(components[i]).offset().top;
		         var mouseYInPage = mousePos
		         var componentMIddle = componentTop + Math.round(components[i].offsetHeight / 2);
		         if (mouseYInPage > componentMIddle) continue;
		         else return i;
		      }
		      
		      return -1;
		   } else {
		      for (var i = 0; i < components.length; i++) {
		         var mouseXInPage = mousePos
		         var componentX = browser.findPosX(components[i], eXo.core.I18n.isRT());
		         if (eXo.core.I18n.isRT()) {
		        	 if (mouseXInPage < componentX) continue;        	 
		         } else if (mouseXInPage > componentX ) continue;
		                  
		         return i; 
		      }
		      
		      return -1;
		   }
		},
	
		/**
		 * Create a div block which show the preview block
		 */
		createPreview : function(layoutType) {
			var previewBlock = document.createElement("div");
			previewBlock.className = "DragAndDropPreview";
			previewBlock.id = "DragAndDropPreview";
			return previewBlock;
		},
	
		findTarget : function(dragObject, mousexInPage, mouseyInPage) {
		  var dropableTargets = $(dragObject).data("dropableTargets");
		  if(!dropableTargets) return null;
		  
		  var foundTarget = null;
		  var len = dropableTargets.length;
		  for(var i = 0 ; i < len ; i++) {
		    var ele =  dropableTargets[i];
	
		    if(dragObject != ele && this.isIn(mousexInPage, mouseyInPage, ele)) {
		      if(foundTarget == null) {
		        foundTarget = ele;
		      } else {
		        if($(ele).closest(foundTarget).length > 0) {
		          foundTarget = ele;
		        }
		      } 
		    }
		  }
		 	
		  return foundTarget;
		},
		  
		isIn : function(x, y, component) {
		  var browser = base.Browser;
		  var offset = $(component).offset();
		  var componentLeft = offset.left;
		  var componentRight = componentLeft + component.offsetWidth ;
		  var componentTop = offset.top;
		  var componentBottom = componentTop + component.offsetHeight ;
		  var isOver = false ;
	
		  if((componentLeft < x) && (x < componentRight)) {
		    if((componentTop < y) && (y < componentBottom)) {
		      isOver = true ;
		    }
		  }
		  return isOver ;
		},
		
		/**
		 * Add the <code>srcElement</code> dragging object to a container.
		 * If the dragging object is a column then let remove it from the table column container
		 */
		divRowContainerAddChild : function(srcElement, targetElement, insertPosition) {	  
		  var listComponent = $(srcElement).data("listComponentInTarget");
		  var uiRowContainer = $(targetElement).find(".UIRowContainer").first();
		  
		  var parentNode = srcElement.parentNode;
		  if(insertPosition >= 0) {
		    uiRowContainer[0].insertBefore(srcElement, listComponent[insertPosition]);
		  } else {
		    uiRowContainer[0].appendChild(srcElement);
		  }
			
		  uiRowContainer.removeClass("EmptyContainer");
		  
		  if(parentNode.nodeName.toLowerCase() == "td") {
		  	$(parentNode).remove();
		  }
		}
	} ;
	
	return eXo.portal.PortalDragDrop;
})($, base, common);
