/*******************************************************************************
 * Copyright (c) MOBAC developers
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package mobac.gui.atlastree;

import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetContext;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;

import javax.swing.JOptionPane;
import javax.swing.JTree;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import mobac.exceptions.InvalidNameException;
import mobac.program.interfaces.LayerInterface;
import mobac.program.interfaces.MapInterface;
import mobac.program.model.AtlasTreeModel;
import mobac.utilities.I18nUtils;

import org.apache.log4j.Logger;


public class DragDropController {

	static Logger log = Logger.getLogger(DragDropController.class);

	public DragDropController(JAtlasTree atlasTree) {
		super();
		this.atlasTree = atlasTree;
		new AtlasDragSource();
		new AtlasDropTarget();
	}

	JAtlasTree atlasTree;

	protected class AtlasDragSource implements DragSourceListener, DragGestureListener {

		final DragGestureRecognizer recognizer;
		final DragSource source;

		public AtlasDragSource() {
			source = new DragSource();
			recognizer = source.createDefaultDragGestureRecognizer(atlasTree,
					DnDConstants.ACTION_MOVE, this);
		}

		public void dragGestureRecognized(DragGestureEvent dge) {
			TreePath path = atlasTree.getSelectionPath();
			if ((path == null) || (path.getPathCount() <= 1))
				// We can't move the root node or an empty selection
				return;
			TreeNode oldNode = (TreeNode) path.getLastPathComponent();
			if (!(oldNode instanceof LayerInterface || oldNode instanceof MapInterface))
				return;
			Transferable transferable = new NodeTransferWrapper(oldNode);
			source.startDrag(dge, DragSource.DefaultMoveNoDrop, transferable, this);
		}

		/**
		 * Called whenever the drop target changes and it has bee accepted (
		 */
		public void dragEnter(DragSourceDragEvent dsde) {
			dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
		}

		public void dragOver(DragSourceDragEvent dsde) {
		}

		public void dragDropEnd(DragSourceDropEvent dsde) {
		}

		public void dragExit(DragSourceEvent dse) {
			dse.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
		}

		public void dropActionChanged(DragSourceDragEvent dsde) {
		}

	}

	protected class AtlasDropTarget implements DropTargetListener {

		final DropTarget target;

		public AtlasDropTarget() throws HeadlessException {
			super();
			target = new DropTarget(atlasTree, this);
		}

		public synchronized void dragEnter(DropTargetDragEvent dtde) {
		}

		public synchronized void dragExit(DropTargetEvent dte) {
		}

		public synchronized void dragOver(DropTargetDragEvent dtde) {
			try {
				Transferable t = dtde.getTransferable();
				Object o = t.getTransferData(NodeTransferWrapper.ATLAS_OBJECT_FLAVOR);
				TreeNode node = getNodeForEvent(dtde);
				if (o instanceof LayerInterface && node instanceof LayerInterface) {
					dtde.acceptDrag(dtde.getDropAction());
					return;
				}
				if (o instanceof MapInterface && node instanceof LayerInterface
						|| node instanceof MapInterface) {
					dtde.acceptDrag(dtde.getDropAction());
					return;
				}
				dtde.rejectDrag();
			} catch (Exception e) {
				log.error("", e);
			}
		}

		public void dropActionChanged(DropTargetDragEvent dtde) {
		}

		public synchronized void drop(DropTargetDropEvent dtde) {
			try {
				TreeNode sourceNode = (TreeNode) dtde.getTransferable().getTransferData(
						NodeTransferWrapper.ATLAS_OBJECT_FLAVOR);

				Point pt = dtde.getLocation();
				DropTargetContext dtc = dtde.getDropTargetContext();
				JTree tree = (JTree) dtc.getComponent();
				TreePath parentpath = tree.getClosestPathForLocation(pt.x, pt.y);
				TreeNode targetNode = (TreeNode) parentpath.getLastPathComponent();

				if (targetNode.equals(sourceNode) || targetNode.getParent().equals(sourceNode)) {
					dtde.rejectDrop();
					return;
				}
				AtlasTreeModel atlasTreeModel = (AtlasTreeModel) atlasTree.getModel();
				if (sourceNode instanceof LayerInterface && targetNode instanceof LayerInterface)
					mergeLayers(atlasTreeModel, (LayerInterface) sourceNode,
							(LayerInterface) targetNode);

				if (targetNode instanceof MapInterface)
					// We can not make a map child of another map
					// -> use it's layer instead
					targetNode = targetNode.getParent();

				if (sourceNode instanceof MapInterface && targetNode instanceof LayerInterface)
					moveMap(atlasTreeModel, (MapInterface) sourceNode, (LayerInterface) targetNode);

			} catch (Exception e) {
				log.error("", e);
				atlasTree.getTreeModel().notifyStructureChanged();
				dtde.rejectDrop();
			}
		}

		protected void mergeLayers(AtlasTreeModel atlasTreeModel, LayerInterface sourceLayer,
				LayerInterface targetLayer) throws InvalidNameException {
			int answer = JOptionPane.showConfirmDialog(null,
					String.format(I18nUtils.localizedStringForKey("msg_confirm_merge_layer"),
							sourceLayer.getName(), targetLayer.getName()), 
					I18nUtils.localizedStringForKey("msg_confirm_merge_layer_title"),
					JOptionPane.YES_NO_OPTION);
			if (answer != JOptionPane.YES_OPTION)
				return;
			try {
				atlasTreeModel.mergeLayers(sourceLayer, targetLayer);
			} catch (InvalidNameException e) {
				JOptionPane.showMessageDialog(null, e.getMessage(), 
						I18nUtils.localizedStringForKey("msg_merge_layer_failed"),
						JOptionPane.ERROR_MESSAGE);
				throw e;
			}
		}

		protected void moveMap(AtlasTreeModel atlasTreeModel, MapInterface map,
				LayerInterface targetLayer) throws InvalidNameException {
			atlasTreeModel.moveMap(map, targetLayer);
		}

		private TreeNode getNodeForEvent(DropTargetDragEvent dtde) {
			Point p = dtde.getLocation();
			DropTargetContext dtc = dtde.getDropTargetContext();
			JTree tree = (JTree) dtc.getComponent();
			TreePath path = tree.getClosestPathForLocation(p.x, p.y);
			return (TreeNode) path.getLastPathComponent();
		}

	}

}
