/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are 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:
 *     IBM Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.server.ui.internal.view.servers;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.actions.TextActionHandler;
import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;

import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.IServerWorkingCopy;
import org.eclipse.wst.server.core.internal.ServerPlugin;
import org.eclipse.wst.server.ui.internal.Messages;
/**
 * Action to rename a server.
 */
public class RenameAction extends AbstractServerAction {
	/*
	 * The tree editing widgets. If treeEditor is null then edit using the
	 * dialog. We keep the editorText around so that we can close it if a new
	 * selection is made.
	 */
	protected TreeEditor treeEditor;

	protected Tree tree;

	protected Text textEditor;

	protected Composite textEditorParent;

	private TextActionHandler textActionHandler;

	// The server being edited if this is being done inline
	protected IServer editedServer;

	protected boolean saving = false;

	public RenameAction(Shell shell, TreeViewer viewer, ISelectionProvider selectionProvider) {
		super(shell, selectionProvider, Messages.actionRename);
		setActionDefinitionId(IWorkbenchActionDefinitionIds.RENAME);
		this.tree = viewer.getTree();
		this.treeEditor = new TreeEditor(tree);
		try {
			selectionChanged((IStructuredSelection) selectionProvider.getSelection());
		} catch (Exception e) {
			// ignore
		}
	}

	public void perform(IServer server) {
		runWithInlineEditor(server);
	}

	/*
	 * Run the receiver using an inline editor from the supplied navigator. The
	 * navigator will tell the action when the path is ready to run.
	 */
	private void runWithInlineEditor(IServer server) {
		queryNewServerNameInline(server);
	}

	/**
	 * On Mac the text widget already provides a border when it has focus, so
	 * there is no need to draw another one. The value of returned by this
	 * method is usd to control the inset we apply to the text field bound's in
	 * order to get space for drawing a border. A value of 1 means a one-pixel
	 * wide border around the text field. A negative value supresses the border.
	 * However, in M9 the system property
	 * "org.eclipse.swt.internal.carbon.noFocusRing" has been introduced as a
	 * temporary workaround for bug #28842. The existence of the property turns
	 * the native focus ring off if the widget is contained in a main window
	 * (not dialog). The check for the property should be removed after a final
	 * fix for #28842 has been provided.
	 */
	private static int getCellEditorInset(Control c) {
		// special case for MacOS X
		if ("carbon".equals(SWT.getPlatform())) { //$NON-NLS-1$
			if (System
					.getProperty("org.eclipse.swt.internal.carbon.noFocusRing") == null || c.getShell().getParent() != null) { //$NON-NLS-1$
				return -2; // native border
			}
		}
		return 1; // one pixel wide black border
	}

	/**
	 * Get the Tree being edited.
	 * 
	 * @returnTree
	 */
	private Tree getTree() {
		return tree;
	}

	private Composite createParent() {
		Tree tree2 = getTree();
		Composite result = new Composite(tree2, SWT.NONE);
		TreeItem[] selectedItems = tree2.getSelection();
		treeEditor.horizontalAlignment = SWT.LEFT;
		treeEditor.grabHorizontal = true;
		treeEditor.setEditor(result, selectedItems[0]);
		return result;
	}

	/**
	 * Return the new name to be given to the target resource or
	 * <code>null<code>
	 * if the query was canceled. Rename the currently selected server using the table editor. 
	 * Continue the action when the user is done.
	 *
	 * @param server the server to rename
	 */
	private void queryNewServerNameInline(final IServer server) {
		// Make sure text editor is created only once. Simply reset text
		// editor when action is executed more than once. Fixes bug 22269
		if (textEditorParent == null) {
			createTextEditor(server);
		}
		textEditor.setText(server.getName());

		// Open text editor with initial size
		textEditorParent.setVisible(true);
		Point textSize = textEditor.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		textSize.x += textSize.y; // Add extra space for new characters
		Point parentSize = textEditorParent.getSize();
		int inset = getCellEditorInset(textEditorParent);
		textEditor.setBounds(2, inset, Math.min(textSize.x, parentSize.x - 4),
				parentSize.y - 2 * inset);
		textEditorParent.redraw();
		textEditor.selectAll();
		textEditor.setFocus();
	}

	/**
	 * Create the text editor widget.
	 * 
	 * @param server the server to rename
	 */
	private void createTextEditor(final IServer server) {
		// Create text editor parent. This draws a nice bounding rect
		textEditorParent = createParent();
		textEditorParent.setVisible(false);
		final int inset = getCellEditorInset(textEditorParent);
		if (inset > 0) {
			textEditorParent.addListener(SWT.Paint, new Listener() {
				public void handleEvent(Event e) {
					Point textSize = textEditor.getSize();
					Point parentSize = textEditorParent.getSize();
					e.gc.drawRectangle(0, 0, Math.min(textSize.x + 4,
							parentSize.x - 1), parentSize.y - 1);
				}
			});
		}
		// Create inner text editor
		textEditor = new Text(textEditorParent, SWT.NONE);
		textEditor.setFont(tree.getFont());
		textEditorParent.setBackground(textEditor.getBackground());
		textEditor.addListener(SWT.Modify, new Listener() {
			public void handleEvent(Event e) {
				Point textSize = textEditor.computeSize(SWT.DEFAULT,
						SWT.DEFAULT);
				textSize.x += textSize.y; // Add extra space for new
				// characters.
				Point parentSize = textEditorParent.getSize();
				textEditor.setBounds(2, inset, Math.min(textSize.x,
						parentSize.x - 4), parentSize.y - 2 * inset);
				textEditorParent.redraw();
			}
		});
		textEditor.addListener(SWT.Traverse, new Listener() {
			public void handleEvent(Event event) {

				// Workaround for Bug 20214 due to extra
				// traverse events
				switch (event.detail) {
				case SWT.TRAVERSE_ESCAPE:
					// Do nothing in this case
					disposeTextWidget();
					event.doit = true;
					event.detail = SWT.TRAVERSE_NONE;
					break;
				case SWT.TRAVERSE_RETURN:
					saveChangesAndDispose(server);
					event.doit = true;
					event.detail = SWT.TRAVERSE_NONE;
					break;
				}
			}
		});
		textEditor.addFocusListener(new FocusAdapter() {
			public void focusLost(FocusEvent fe) {
				saveChangesAndDispose(server);
			}
		});

		if (textActionHandler != null) {
			textActionHandler.addText(textEditor);
		}
	}

	/**
	 * Close the text widget and reset the editorText field.
	 */
	protected void disposeTextWidget() {
		if (textActionHandler != null)
			textActionHandler.removeText(textEditor);

		if (textEditorParent != null) {
			textEditorParent.dispose();
			textEditorParent = null;
			textEditor = null;
			treeEditor.setEditor(null, null);
		}
	}

	/**
	 * Save the changes and dispose of the text widget.
	 * 
	 * @param server the server to rename
	 */
	protected void saveChangesAndDispose(IServer server) {
		if (saving == true)
			return;
		
		saving = true;
		// Cache the resource to avoid selection loss since a selection of
		// another item can trigger this method
		editedServer = server;
		final String newName = textEditor.getText();
		// Run this in an async to make sure that the operation that triggered
		// this action is completed. Otherwise this leads to problems when the
		// icon of the item being renamed is clicked (i.e., which causes the
		// rename text widget to lose focus and trigger this method)
		getTree().getShell().getDisplay().asyncExec(new Runnable() {
			public void run() {
				try {
					if (!newName.equals(editedServer.getName())) {
						if (ServerPlugin.isNameInUse(editedServer, newName)) {
							MessageDialog.openError(shell, Messages.defaultDialogTitle, Messages.errorDuplicateName);
						} else {
							try {
								IServerWorkingCopy wc = editedServer.createWorkingCopy();
								wc.setName(newName);
								wc.save(false, null);
							} catch (CoreException ce) {
								// ignore for now
							}
						}
					}
					editedServer = null;
					// Dispose the text widget regardless
					disposeTextWidget();
					// Ensure the Navigator tree has focus, which it may not if
					// the text widget previously had focus
					if (tree != null && !tree.isDisposed()) {
						tree.setFocus();
					}
				} finally {
					saving = false;
				}
			}
		});
	}
}