/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.model.ui.dnd;

import java.util.*;

import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.*;
import org.jboss.tools.common.model.ui.navigator.TreeViewerDragDropProvider;

import org.jboss.tools.common.meta.XAdoptManager;
import org.jboss.tools.common.model.event.*;
import org.jboss.tools.common.model.util.ModelFeatureFactory;
import org.jboss.tools.common.model.XModelException;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.XModelTransferBuffer;
import org.jboss.tools.common.model.ui.ModelUIPlugin;

public class ControlDragDrop {
	static XAdoptManager paletteAdopt;
	
	static {
		try {
			if(Platform.getBundle("org.jboss.tools.jst.web") != null) {
				paletteAdopt = (XAdoptManager)ModelFeatureFactory.getInstance().createFeatureInstance("org.jboss.tools.jst.web.tld.model.handlers.JSPAdopt");
			}
		} catch (ClassCastException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
	}
	
	protected IControlDragDropProvider provider;
	
	public ControlDragDrop() {}
	
	public Control getControl() {
		return provider.getControl();
	}
	public void setProvider(IControlDragDropProvider provider) {
		this.provider = provider;
	}

	protected XModelObject getModelObjectForWidget(Widget widget) {
		return provider.getModelObjectForWidget(widget);
	}
	
	protected XModelObject getLeadSelection() {
		Widget[] w = provider.getSelection();
		return (w == null || w.length == 0) ? null : getModelObjectForWidget(w[0]);
	}
	
	protected XModelObject[] getMultiSelection() {
		Widget[] w = provider.getSelection();
		if(w == null || w.length < 2) return null;
		XModelObject[] os = new XModelObject[w.length];
		for (int i = 0; i < os.length; i++) os[i] = getModelObjectForWidget(w[i]); 
		return os;
	}
	
	public void enable() {
		enableDrag();
		enableDrop();
	}
	
	public void enableDrag() {
		DragSource ds = new DragSource(getControl(), DND.DROP_MOVE | DND.DROP_COPY);
		Transfer[] ts = new Transfer[]{ModelTransfer.getInstance(), TextTransfer.getInstance()};
		ds.setTransfer(ts);
		ds.addDragListener(new DSL());		 
	}
	
	public void enableDrop() {
		DropTarget old = (DropTarget)getControl().getData("DropTarget");
		if(old != null) {
			old.setTransfer(new Transfer[]{ModelTransfer.getInstance(), TextTransfer.getInstance()});
			old.addDropListener(new DTL());
		} else {
			DropTarget target = new DropTarget(getControl(), DND.DROP_MOVE | DND.DROP_COPY);
			Transfer[] types = new Transfer[] {ModelTransfer.getInstance()};
			target.setTransfer(types);
			target.addDropListener(new DTL());
		}
	}
	
	XModelObject draggedObject = null;
	
	class DSL implements DragSourceListener {
		public void dragStart(DragSourceEvent e) {
			XModelObject o = getLeadSelection();
			XModelObject[] targets = (o == null) ? null : getMultiSelection();
			XModelTransferBuffer.getInstance().enable();
			e.doit = (DnDUtil.isCopyEnabled(o, targets) && DnDUtil.copy(o, targets));
			if(e.doit) {
				draggedObject = o;
			} else {
				XModelTransferBuffer.getInstance().disable();
			}
		}
		public void dragSetData(DragSourceEvent event) {
			if (ModelTransfer.getInstance().isSupportedType(event.dataType)) {
				XModelObject o = getLeadSelection();
///				event.data = (o == null) ? new String[0] : new String[]{o.getPresentationString()};
				event.data = (o == null) ? "" : o.getPresentationString();
			} else if(TextTransfer.getInstance().isSupportedType(event.dataType)) {
				XModelObject o = getLeadSelection();
				event.data = (o == null) ? "" : o.getPresentationString();
			}
		}
		public void dragFinished(DragSourceEvent event) {
			XModelTransferBuffer.getInstance().disable();
			if (event.detail == DND.DROP_MOVE) {}
		}
		
	}

	class Scroller implements Runnable {
		private long border_time = -1;
		private int border = -1;
		private long item_time = -1;
		private Widget item = null;
		int status = 0;
		boolean scrollPending = false;
		boolean expandPending = false;
		
		public void run() {
			while(status == 1) {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					ignore();
				}
				long t = System.currentTimeMillis();
				if(item != null && t > item_time) {
					expandPending = true;
					item_time = -1;
				}
				if(border != -1 && t > border_time) {
					border_time += 500;
					scrollPending = true;
				}
			}
			status = 0;
		}
		
		public void start() {
			if(status == 0) {
				status = 1;
				new Thread(this).start();
			}
		}
		
		public void stop() {
			status = 2;
		}
		
		Widget lastItem = null;
		public void update(Widget w, int x, int y) {
			if (w != null && lastItem != w) lastItem = w;
			if(scrollPending) fireScroll();
			if(expandPending) fireExpand();

			if(w != item) {
				item = w;
				if(item != null) item_time = System.currentTimeMillis() + 1500; 
			}
			Scrollable sc = (provider.getControl() instanceof Scrollable) ? (Scrollable)provider.getControl() : null;
			if(sc == null) return; 

			Rectangle c = sc.getClientArea();
			int yp_t = sc.toDisplay(new Point(c.x, c.y)).y;
			int yp_b = yp_t + c.height;
			if(yp_t < y && yp_t + 20 > y) {
				if(border != 1) {
					border = 1;
					border_time = System.currentTimeMillis() + 500;
				}
			} else if(y < yp_b && y + 20 > yp_b) {
				if(border != 2) {
					border = 2;
					border_time = System.currentTimeMillis() + 500;
				}
			} else {
				if(border != -1) {
					border = -1;
					border_time = -1;
				}
			}
		}
		
		protected void fireExpand() {
			expandPending = false;
			if(!(item instanceof TreeItem)) return;
			if(provider instanceof TreeViewerDragDropProvider) {
				TreeViewerDragDropProvider p = (TreeViewerDragDropProvider)provider;
				p.expand(item);
			}
		}
		
		protected void fireScroll() {
			scrollPending = false;
			if(!(provider.getControl() instanceof Tree) || lastItem == null) return;
				 
			Tree tree = (Tree)provider.getControl();
			TreeItem i = (TreeItem)lastItem;
//			Rectangle r = i.getBounds();
			
			Scrollable sc = (provider.getControl() instanceof Scrollable) ? (Scrollable)provider.getControl() : null;
			if(sc == null || sc.getVerticalBar() == null) return;

			if(border == 1 && sc != null) {
				TreeItem i2 = getPrevTreeItem(tree, i);
				if(i2 != null) tree.showItem(i2);
			} else if(border == 2 && sc != null) {
				TreeItem i2 = getNextTreeItem(tree, i);
				if(i2 != null) tree.showItem(i2);
			}
		}
		
		private TreeItem getNextTreeItem (Tree tree, TreeItem item) {
			TreeItem result = null;
			Vector<TreeItem> rows = getTreeExpandedItems(tree);
			int index = rows.indexOf(item);
			if (index > -1) {
				result = (index + 1 < rows.size() ? rows.get(index + 1) : item);
			} else {
				result = item;
			}
			return result;
		}

		private TreeItem getPrevTreeItem (Tree tree, TreeItem item) {
			TreeItem result = null;
			Vector<TreeItem> rows = getTreeExpandedItems(tree);
			int index = rows.indexOf(item);			
			if (index > 0) {
				result = (TreeItem)rows.get(index - 1);
			} else {
				result = item;
			}
			return result;
		}
	}

	class DTL implements DropTargetListener {
		private boolean isTextDrop = false;
		private boolean isPaletteDrop = false;
		private Scroller scroller = new Scroller();
		private Widget lastItem = null;
		
		public void dragEnter(DropTargetEvent event) {
			scroller.start();
		}
		public void dragOver(DropTargetEvent event) {
			if (event.item != null && lastItem != event.item)
				lastItem = event.item;
			scroller.update(event.item, event.x, event.y);
			
			{
				XModelObject o = getModelObjectForWidget(event.item);
				if(o == null) {
					event.detail = DND.DROP_NONE;
					return;
				}
				boolean enabled = DnDUtil.isPasteEnabled(o);
				isTextDrop = false;
				isPaletteDrop = false;
				Properties p = provider.getDropProperties(event.x, event.y);
				if(p != null && ("none".equals(p.getProperty("text-context")))) {
					enabled = false;
				} else if(!enabled && o.isObjectEditable()) {
					if(p != null && "true".equals(p.getProperty("accepsAsString"))) {
						if(isAdoptableMacro(o)) {
							isPaletteDrop = true;
						} else {
							isTextDrop = true;
						}
						enabled = true;
					}
				}
				event.detail = (enabled) ? DND.DROP_MOVE : DND.DROP_NONE;
			}
		}
		boolean isAdoptableMacro(XModelObject target) {
			if(paletteAdopt == null) return false;
			if(target.getFileType() != XModelObject.FILE) return false;
			XModelObject source = target.getModel().getModelBuffer().source();
			if(source == null) return false;
			return paletteAdopt.isAdoptable(target, source);
		}
		public void dragLeave(DropTargetEvent event) {
			scroller.stop();
		}
		public void dragOperationChanged(DropTargetEvent event) {};
		public void dropAccept(DropTargetEvent event) {}
		public void drop(DropTargetEvent event) {
			scroller.stop();
//			if(event.currentDataType.type != ModelTransfer.TYPE) {
//				event.detail = DND.DROP_NONE;
//			} else 
			{
				Widget w = event.item;
				if(provider instanceof TreeViewerDragDropProvider) {
					w = lastItem;
				}
				XModelObject o = getModelObjectForWidget(w);
				if(o == null) return;
				try {
					Properties p = provider.getDropProperties(event.x, event.y);
					if(p == null) p = new Properties();
					p.setProperty("isDrop", "true");
					if(isTextDrop) {
						XModelObject s = o.getModel().getModelBuffer().source();
						if(s == null) return;
						p.put("start text", "" + s.getPresentationString());
					} else if(isPaletteDrop) {
						paletteAdopt.adopt(o, o.getModel().getModelBuffer().source(), p);
					} else if(DnDUtil.isPasteEnabled(o)) {
						DnDUtil.paste(o, p);
					}
					if(provider instanceof IControlDropListener) {
						p.put("drop.x", Integer.valueOf(event.x));
						p.put("drop.y", Integer.valueOf(event.y));
						if(draggedObject != null) p.put("draggedObject", draggedObject);
						((IControlDropListener)provider).drop(p);
					}
				} catch (ActionDeclinedException ade) {
					ignore();
				} catch (XModelException e) {
					ModelUIPlugin.getPluginLog().logError(e);
				}
			}
		}
	}
	
	void ignore() {
		//do nothing
	}
	
	private static Vector<TreeItem> getTreeExpandedItems(Tree tree) {
		TreeItem[] items = tree.getItems();
		Vector<TreeItem> result = new Vector<TreeItem>();
			
		for (int i = 0; items != null && i < items.length; i++) {
			result.add(result.size(), items[i]);	
			if (items[i].getExpanded()) fillTreeItemRows(items[i].getItems(), result);
		}
		return result;
	}
		
	private static Vector<TreeItem> fillTreeItemRows(TreeItem[] items, Vector<TreeItem> result){ 
		if (result == null) result = new Vector<TreeItem>();
		for (int i = 0; items != null && i < items.length; i++) {
			result.add(result.size(), items[i]);	
			if (items[i].getExpanded()) fillTreeItemRows(items[i].getItems(), result);
		}
		return result;
	}
	
	public Widget findTreeItem (int x, int y) {
		Tree tree = (provider.getControl() instanceof Tree) ? (Tree)provider.getControl() : null;
		Vector<TreeItem> items = getTreeExpandedItems(tree);
		for (int i = 0; i < items.size(); i++) {
			TreeItem item = items.get(i);
			Rectangle bounds = item.getBounds();
			Point p = tree.toDisplay(bounds.x, bounds.y);
			
			if (/*x >= p.x && x <= p.x + bounds.width &&*/
				y >= p.y && y <= p.y + bounds.height) {
					return item;						
				}
		}
		return null;
	}
}
