/*******************************************************************************
 * Copyright (c) 2000, 2006 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.ui.actions;

import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;
import org.eclipse.ui.dialogs.ISelectionValidator;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;

/**
 * Standard action for copying the currently selected resources elsewhere
 * in the workspace. All resources being copied as a group must be siblings.
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p>
 */
public class CopyResourceAction extends SelectionListenerAction implements
        ISelectionValidator {

    /**
     * The id of this action.
     */
    public static final String ID = PlatformUI.PLUGIN_ID
            + ".CopyResourceAction"; //$NON-NLS-1$

    /**
     * The shell in which to show any dialogs.
     */
    private Shell shell;

    /**
     * The operation to run.  This is created only during the life-cycle of the
     * run method.
     */
    protected CopyFilesAndFoldersOperation operation;

	private String[] modelProviderIds;

    /**
     * Returns a new name for a copy of the resource at the given path in the given
     * workspace. This name could be determined either automatically or by querying
     * the user. This name will <b>not</b> be verified by the caller, so it must be
     * valid and unique.
     * <p>
     * Note this method is for internal use only.
     * </p>
     *
     * @param originalName the full path of the resource
     * @param workspace the workspace
     * @return the new full path for the copy, or <code>null</code> if the resource
     *   should not be copied
     */
    public static IPath getNewNameFor(IPath originalName, IWorkspace workspace) {
        return CopyFilesAndFoldersOperation.getAutoNewNameFor(originalName,
                workspace);
    }

    /**
     * Creates a new action.
     *
     * @param shell the shell for any dialogs
     */
    public CopyResourceAction(Shell shell) {
        this(shell, IDEWorkbenchMessages.CopyResourceAction_title);
        PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
				IIDEHelpContextIds.COPY_RESOURCE_ACTION);
    }

    /**
     * Creates a new action with the given text.
     *
     * @param shell the shell for any dialogs
     * @param name the string used as the name for the action, 
     *   or <code>null</code> if there is no name
     */
    CopyResourceAction(Shell shell, String name) {
        super(name);
        setToolTipText(IDEWorkbenchMessages.CopyResourceAction_toolTip);
        setId(CopyResourceAction.ID);
        if (shell == null) {
            throw new IllegalArgumentException();
        }
        this.shell = shell;
    }

    /**
     * Returns the operation to perform when this action runs.
     * 
     * @return the operation to perform when this action runs.
     */
    protected CopyFilesAndFoldersOperation createOperation() {
        return new CopyFilesAndFoldersOperation(getShell());
    }

    /**
     * Returns the path of the container to initially select in the container
     * selection dialog, or <code>null</code> if there is no initial selection
     * @return The initial container; <code>null</code> if none.
     */
    IContainer getInitialContainer() {
        List resources = getSelectedResources();
        if (resources.size() > 0) {
            IResource resource = (IResource) resources.get(0);
            return resource.getParent();
        }
        return null;
    }

    /**
     * Returns an array of resources to use for the operation from 
     * the provided list.
     * 
     * @param resourceList The list of resources to converted into an array.
     * @return an array of resources to use for the operation
     */
    protected IResource[] getResources(List resourceList) {
        return (IResource[]) resourceList.toArray(new IResource[resourceList
                .size()]);
    }

    /**
     * Returns the shell in which to show any dialogs
     * @return The shell for parenting dialogs; never <code>null</code>.
     */
    Shell getShell() {
        return shell;
    }

    /**
     * The <code>CopyResourceAction</code> implementation of this 
     * <code>ISelectionValidator</code> method checks whether the given path
     * is a good place to copy the selected resources.
     */
    public String isValid(Object destination) {
        IWorkspaceRoot root = IDEWorkbenchPlugin.getPluginWorkspace().getRoot();
        IContainer container = (IContainer) root
                .findMember((IPath) destination);

        if (container != null) {
            // create a new operation here. 
            // isValid is API and may be called in any context.
            CopyFilesAndFoldersOperation newOperation = createOperation();
            List sources = getSelectedResources();
            IResource[] resources = (IResource[]) sources
                    .toArray(new IResource[sources.size()]);
            return newOperation.validateDestination(container, resources);
        }
        return null;
    }

    /**
     * Asks the user for the destination of this action.
     *
     * @return the path on an existing or new resource container, or 
     *  <code>null</code> if the operation should be abandoned
     */
    IPath queryDestinationResource() {
        // start traversal at root resource, should probably start at a
        // better location in the tree
        ContainerSelectionDialog dialog = new ContainerSelectionDialog(shell,
                getInitialContainer(), true, IDEWorkbenchMessages.CopyResourceAction_selectDestination);
        dialog.setValidator(this);
        dialog.showClosedProjects(false);
        dialog.open();
        Object[] result = dialog.getResult();
        if (result != null && result.length == 1) {
            return (IPath) result[0];
        }
        return null;
    }

    /* (non-Javadoc)
     * Method declared on IAction.
     */
    public void run() {
        try {
            operation = createOperation();
            operation.setModelProviderIds(getModelProviderIds());

            // WARNING: do not query the selected resources more than once
            // since the selection may change during the run, 
            // e.g. due to window activation when the prompt dialog is dismissed.
            // For more details, see Bug 60606 [Navigator] (data loss) Navigator deletes/moves the wrong file
            List sources = getSelectedResources();

            IPath destination = queryDestinationResource();
            if (destination == null) {
				return;
			}

            IWorkspaceRoot root = IDEWorkbenchPlugin.getPluginWorkspace()
                    .getRoot();
            IContainer container = (IContainer) root.findMember(destination);
            if (container == null) {
                return;
            }

            runOperation(getResources(sources), container);
        } finally {
            operation = null;
        }
    }

    /**
     * Runs the operation created in <code>createOperaiton</code>
     * 
     * @param resources source resources to pass to the operation
     * @param destination destination container to pass to the operation
     */
    protected void runOperation(IResource[] resources, IContainer destination) {
        operation.copyResources(resources, destination);
    }

    /**
     * The <code>CopyResourceAction</code> implementation of this
     * <code>SelectionListenerAction</code> method enables this action only if 
     * all of the one or more selections are sibling resources which are 
     * local (depth infinity).
     */
    protected boolean updateSelection(IStructuredSelection selection) {
        if (!super.updateSelection(selection)) {
            return false;
        }
        if (getSelectedNonResources().size() > 0) {
            return false;
        }

        // to enable this command all selected resources must be siblings
        List selectedResources = getSelectedResources();
        if (selectedResources.size() == 0) {
			return false;
		}
        IContainer firstParent = ((IResource) selectedResources.get(0))
                .getParent();
        if (firstParent == null) {
            return false;
        }
        Iterator resourcesEnum = selectedResources.iterator();
        while (resourcesEnum.hasNext()) {
            IResource currentResource = (IResource) resourcesEnum.next();
            if (!currentResource.exists()) {
                return false;
            }
            if (currentResource.getType() == IResource.PROJECT) {
                return false;
            }
            IContainer parent = currentResource.getParent();
            if ((parent != null) && (!parent.equals(firstParent))) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * Returns the model provider ids that are known to the client
     * that instantiated this operation.
     * 
     * @return the model provider ids that are known to the client
     * that instantiated this operation.
     * @since 3.2
     */
	public String[] getModelProviderIds() {
		return modelProviderIds;
	}

	/**
     * Sets the model provider ids that are known to the client
     * that instantiated this operation. Any potential side effects
     * reported by these models during validation will be ignored.
     * 
	 * @param modelProviderIds the model providers known to the client
	 * who is using this operation.
	 * @since 3.2
	 */
	public void setModelProviderIds(String[] modelProviderIds) {
		this.modelProviderIds = modelProviderIds;
	}
}
