/*******************************************************************************
 * Copyright (c) 2006-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
 * Matt McCutchen <hashproduct+eclipse@gmail.com> - Bug 94808 [Change Sets] "&" not showing up in dropdown menu
 *******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.mappings;

import java.util.*;

import org.eclipse.core.resources.*;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Control;
import org.eclipse.team.core.diff.*;
import org.eclipse.team.core.mapping.IResourceDiffTree;
import org.eclipse.team.core.mapping.provider.ResourceDiffTree;
import org.eclipse.team.internal.ccvs.core.mapping.ChangeSetModelProvider;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.core.subscribers.*;
import org.eclipse.team.internal.ui.*;
import org.eclipse.team.internal.ui.mapping.ResourceModelActionProvider;
import org.eclipse.team.internal.ui.mapping.ResourceModelTraversalCalculator;
import org.eclipse.team.internal.ui.synchronize.*;
import org.eclipse.team.ui.synchronize.*;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.BaseSelectionListenerAction;
import org.eclipse.ui.navigator.INavigatorContentExtension;
import org.eclipse.ui.navigator.INavigatorContentService;

public class ChangeSetActionProvider extends ResourceModelActionProvider {

	/**
     * Menu group that can be added to the context menu
     */
    public final static String CHANGE_SET_GROUP = "changeSetActions"; //$NON-NLS-1$
    
	// Constants for persisting sorting options
	private static final String P_LAST_COMMENTSORT = TeamUIPlugin.ID + ".P_LAST_COMMENT_SORT"; //$NON-NLS-1$
	
	private MenuManager sortByComment;
	private CreateChangeSetAction createChangeSet;
	private MenuManager addToChangeSet;
    private EditChangeSetAction editChangeSet;
    private RemoveChangeSetAction removeChangeSet;
    private MakeDefaultChangeSetAction makeDefault;

	private OpenChangeSetAction openCommitSet;
    
	private class CreateChangeSetAction extends ModelParticipantAction {
	    
        public CreateChangeSetAction(ISynchronizePageConfiguration configuration) {
            super(TeamUIMessages.ChangeLogModelProvider_0, configuration); 
        }

        public void run() {
        	final IDiff[] diffs = getLocalChanges(getStructuredSelection());
        	syncExec(new Runnable() {
				public void run() {
					createChangeSet(diffs);
				}
			});
        }
        
		private void createChangeSet(IDiff[] diffs) {
            ActiveChangeSet set =  getChangeSetCapability().createChangeSet(getConfiguration(), diffs);
            if (set != null) {
                getActiveChangeSetManager().add(set);
            }
        }
        
		protected boolean isEnabledForSelection(IStructuredSelection selection) {
			return isContentProviderEnabled() && containsLocalChanges(selection);
		}
	}
	
	/**
	 * Escape a string so it can be used as an action text without '&'
	 * being interpreted as a mnemonic. Specifically, turn each '&' into '&&'.
	 */
	private static String escapeActionText(String x) {
		// Loosely based on org.eclipse.jface.action.LegacyActionTools#removeMnemonics
		int ampersandIndex = x.indexOf('&');
		if (ampersandIndex == -1)
			return x;
		
		int len = x.length();
		StringBuffer sb = new StringBuffer(2 * len + 1);
		int doneIndex = 0;
		while (ampersandIndex != -1) {
			sb.append(x.substring(doneIndex, ampersandIndex));
			sb.append("&&"); //$NON-NLS-1$
			doneIndex = ampersandIndex + 1;
			ampersandIndex = x.indexOf('&', doneIndex);
		}
		if (doneIndex < len)
			sb.append(x.substring(doneIndex, len));
		return sb.toString();
	}

	private class AddToChangeSetAction extends ModelParticipantAction {
		 
        private final ActiveChangeSet set;
	    
        public AddToChangeSetAction(ISynchronizePageConfiguration configuration, ActiveChangeSet set, ISelection selection) {
            super(set == null ? TeamUIMessages.ChangeSetActionGroup_2 : escapeActionText(set.getTitle()), configuration); 
            this.set = set;
            selectionChanged(selection);
        }
        
        public void run() {
        	IDiff[] diffArray = getLocalChanges(getStructuredSelection());
            if (set != null) {
				set.add(diffArray);
            } else {
                ChangeSet[] sets = getActiveChangeSetManager().getSets();
                IResource[] resources = getResources(diffArray);
                for (int i = 0; i < sets.length; i++) {
                    ActiveChangeSet activeSet = (ActiveChangeSet)sets[i];
					activeSet.remove(resources);
                }
            }
        }

		private IResource[] getResources(IDiff[] diffArray) {
			List result = new ArrayList();
			for (int i = 0; i < diffArray.length; i++) {
				IDiff diff = diffArray[i];
				IResource resource = ResourceDiffTree.getResourceFor(diff);
				if (resource != null) {
					result.add(resource);
				}
			}
			return (IResource[]) result.toArray(new IResource[result.size()]);
		}

		protected boolean isEnabledForSelection(IStructuredSelection selection) {
			return isContentProviderEnabled() && containsLocalChanges(selection);
		}
	}

	private abstract class ChangeSetAction extends BaseSelectionListenerAction {

        public ChangeSetAction(String title, ISynchronizePageConfiguration configuration) {
            super(title);
        }
        
        /* (non-Javadoc)
         * @see org.eclipse.ui.actions.BaseSelectionListenerAction#updateSelection(org.eclipse.jface.viewers.IStructuredSelection)
         */
        protected boolean updateSelection(IStructuredSelection selection) {
            return getSelectedSet() != null;
        }

        protected ActiveChangeSet getSelectedSet() {
            IStructuredSelection selection = getStructuredSelection();
            if (selection.size() == 1) {
                Object first = selection.getFirstElement();
                if (first instanceof ActiveChangeSet) {
					ActiveChangeSet activeChangeSet = (ActiveChangeSet) first;
					if (activeChangeSet.isUserCreated())
						return activeChangeSet;
				}
            }
            return null;
        }
	}
	
	private class EditChangeSetAction extends ChangeSetAction {

        public EditChangeSetAction(ISynchronizePageConfiguration configuration) {
            super(TeamUIMessages.ChangeLogModelProvider_6, configuration); 
        }
        
        public void run() {
            ActiveChangeSet set = getSelectedSet();
            if (set == null) return;
            getChangeSetCapability().editChangeSet(getSynchronizePageConfiguration(), set);
        }
	}
	
	private class RemoveChangeSetAction extends ChangeSetAction {

        public RemoveChangeSetAction(ISynchronizePageConfiguration configuration) {
            super("Remove Change Set", configuration); //$NON-NLS-1$
        }
        
        public void run() {
            ActiveChangeSet set = getSelectedSet();
            if (set == null) return;
            if (MessageDialog.openConfirm(getSynchronizePageConfiguration().getSite().getShell(), TeamUIMessages.ChangeSetActionGroup_0, NLS.bind(TeamUIMessages.ChangeSetActionGroup_1, new String[] { set.getTitle() }))) { // 
                getActiveChangeSetManager().remove(set);
            }
        }
	}
	
	private class MakeDefaultChangeSetAction extends ChangeSetAction {
        public MakeDefaultChangeSetAction(ISynchronizePageConfiguration configuration) {
            super(TeamUIMessages.ChangeLogModelProvider_9, configuration); 
        }
        
        public void run() {
            ActiveChangeSet set = getSelectedSet();
            if (set == null) return;
    		getActiveChangeSetManager().makeDefault(set);
        }
	}
	
	/* *****************************************************************************
	 * Action that allows changing the model providers sort order.
	 */
	private class ToggleSortOrderAction extends Action {
		private int criteria;
		protected ToggleSortOrderAction(String name, int criteria) {
			super(name, IAction.AS_RADIO_BUTTON);
			this.criteria = criteria;
			update();		
		}

		public void run() {
			int sortCriteria = getSortCriteria(getSynchronizePageConfiguration());
			if (isChecked() && sortCriteria != criteria) {
			    setSortCriteria(getSynchronizePageConfiguration(), criteria);
				update();
				((SynchronizePageConfiguration)getSynchronizePageConfiguration()).getPage().getViewer().refresh();
			}
		}
		
		public void update() {
		    setChecked(criteria == getSortCriteria(getSynchronizePageConfiguration()));
		}
	}
	
    public static int getSortCriteria(ISynchronizePageConfiguration configuration) {
    	int sortCriteria = ChangeSetSorter.DATE;
    	if (configuration != null) {
	    	Object o = configuration.getProperty(P_LAST_COMMENTSORT);
	    	if (o instanceof Integer) {
				Integer wrapper = (Integer) o;
				sortCriteria = wrapper.intValue();
			} else {
				try {
					IDialogSettings pageSettings = configuration.getSite().getPageSettings();
					if(pageSettings != null) {
						sortCriteria = pageSettings.getInt(P_LAST_COMMENTSORT);
					}
				} catch(NumberFormatException e) {
					// ignore and use the defaults.
				}
			}
		}
		switch (sortCriteria) {
        case ChangeSetSorter.COMMENT:
        case ChangeSetSorter.DATE:
        case ChangeSetSorter.USER:
            break;
        default:
            sortCriteria = ChangeSetSorter.DATE;
            break;
        }
		return sortCriteria;
    }
    
	public static void setSortCriteria(ISynchronizePageConfiguration configuration, int criteria) {
		configuration.setProperty(P_LAST_COMMENTSORT, new Integer(criteria));
		IDialogSettings pageSettings = configuration.getSite().getPageSettings();
		if(pageSettings != null) {
			pageSettings.put(P_LAST_COMMENTSORT, criteria);
		}
	}
	
	public ChangeSetActionProvider() {
		super();
	}

	protected void initialize() {
		super.initialize();
		if (getChangeSetCapability().supportsCheckedInChangeSets()) {
			sortByComment = new MenuManager(TeamUIMessages.ChangeLogModelProvider_0a);	 
			sortByComment.add(new ToggleSortOrderAction(TeamUIMessages.ChangeLogModelProvider_1a, ChangeSetSorter.COMMENT)); 
			sortByComment.add(new ToggleSortOrderAction(TeamUIMessages.ChangeLogModelProvider_2a, ChangeSetSorter.DATE)); 
			sortByComment.add(new ToggleSortOrderAction(TeamUIMessages.ChangeLogModelProvider_3a, ChangeSetSorter.USER));
			openCommitSet = new OpenChangeSetAction(getSynchronizePageConfiguration());
		}
		if (getChangeSetCapability().supportsActiveChangeSets()) {
			addToChangeSet = new MenuManager(TeamUIMessages.ChangeLogModelProvider_12); 
			addToChangeSet.setRemoveAllWhenShown(true);
			addToChangeSet.addMenuListener(new IMenuListener() {
	            public void menuAboutToShow(IMenuManager manager) {
	                addChangeSets(manager);
	            }
	        });
			createChangeSet = new CreateChangeSetAction(getSynchronizePageConfiguration());
			addToChangeSet.add(createChangeSet);
			addToChangeSet.add(new Separator());
			editChangeSet = new EditChangeSetAction(getSynchronizePageConfiguration());
			makeDefault = new MakeDefaultChangeSetAction(getSynchronizePageConfiguration());
			removeChangeSet = new RemoveChangeSetAction(getSynchronizePageConfiguration());
		}
	}

	public void fillContextMenu(IMenuManager menu) {
		if (isContentProviderEnabled()) {
			super.fillContextMenu(menu);
			if (getChangeSetCapability().enableCheckedInChangeSetsFor(getSynchronizePageConfiguration())) {
				appendToGroup(
						menu, 
						"file-bottom",  //$NON-NLS-1$
						openCommitSet);
				appendToGroup(menu, ISynchronizePageConfiguration.SORT_GROUP, sortByComment);
			}
			if (getChangeSetCapability().enableActiveChangeSetsFor(getSynchronizePageConfiguration())) {
				appendToGroup(
						menu, 
						CHANGE_SET_GROUP, 
						addToChangeSet);
				appendToGroup(
						menu, 
						CHANGE_SET_GROUP, 
						editChangeSet);
				appendToGroup(
						menu, 
						CHANGE_SET_GROUP, 
						removeChangeSet);
				appendToGroup(
						menu, 
						CHANGE_SET_GROUP, 
						makeDefault);
			}
		}
	}
	
	public void dispose() {
	    if (addToChangeSet != null) {
			addToChangeSet.dispose();
			addToChangeSet.removeAll();
	    }
	    if (sortByComment != null) {
			sortByComment.dispose();
			sortByComment.removeAll();
	    }
		super.dispose();
	}
	
    protected void addChangeSets(IMenuManager manager) {
        ChangeSet[] sets = getActiveChangeSetManager().getSortedSets();
        ISelection selection = getContext().getSelection();
        createChangeSet.selectionChanged(selection);
		addToChangeSet.add(createChangeSet);
		addToChangeSet.add(new Separator());
        for (int i = 0; i < sets.length; i++) {
            ActiveChangeSet set = (ActiveChangeSet)sets[i];
            AddToChangeSetAction action = new AddToChangeSetAction(getSynchronizePageConfiguration(), set, selection);
            manager.add(action);
        }
        addToChangeSet.add(new Separator());
        // Action that removes change set resources
        addToChangeSet.add(new AddToChangeSetAction(getSynchronizePageConfiguration(), null, selection));
    }
	
	private boolean appendToGroup(IContributionManager manager, String groupId, IContributionItem item) {
		if (manager == null || item == null) return false;
		IContributionItem group = manager.find(groupId);
		if (group != null) {
			manager.appendToGroup(group.getId(), item);
			return true;
		}
		return false;
	}
	
	private boolean appendToGroup(IContributionManager manager, String groupId, IAction action) {
		if (manager == null || action == null) return false;
		IContributionItem group = manager.find(groupId);
		if (group != null) {
			manager.appendToGroup(group.getId(), action);
			//registerActionWithWorkbench(action);
			return true;
		}
		return false;
	}
	
    public ChangeSetCapability getChangeSetCapability() {
        ISynchronizeParticipant participant = getSynchronizePageConfiguration().getParticipant();
        if (participant instanceof IChangeSetProvider) {
            IChangeSetProvider provider = (IChangeSetProvider) participant;
            return provider.getChangeSetCapability();
        }
        return null;
    }
    
    private void syncExec(final Runnable runnable) {
		final Control ctrl = getSynchronizePageConfiguration().getPage().getViewer().getControl();
		Utils.syncExec(runnable, ctrl);
    }

	private ActiveChangeSetManager getActiveChangeSetManager() {
		return CVSUIPlugin.getPlugin().getChangeSetManager();
	}

	
    public IDiff[] getLocalChanges(IStructuredSelection selection) {
		if (selection instanceof ITreeSelection) {
			ITreeSelection ts = (ITreeSelection) selection;
			TreePath[] paths = ts.getPaths();
			List result = new ArrayList();
			for (int i = 0; i < paths.length; i++) {
				TreePath path = paths[i];
				IDiff[] diffs = getLocalChanges(path);
				for (int j = 0; j < diffs.length; j++) {
					IDiff diff = diffs[j];
					result.add(diff);
				}
			}
			return (IDiff[]) result.toArray(new IDiff[result.size()]);
		}
		return new IDiff[0];
	}

	private IDiff[] getLocalChanges(TreePath path) {
		IResourceDiffTree tree = getDiffTree(path);
		if (path.getSegmentCount() == 1 && path.getLastSegment() instanceof IDiffTree) {
			return ((ResourceDiffTree)tree).getDiffs();
		}
		ResourceTraversal[] traversals = getTraversals(path.getLastSegment());
		return tree.getDiffs(traversals);
	}

	private IResourceDiffTree getDiffTree(TreePath path) {
		return getContentProvider().getDiffTree(path);
	}

	public boolean containsLocalChanges(IStructuredSelection selection) {
		if (selection instanceof ITreeSelection) {
			ITreeSelection ts = (ITreeSelection) selection;
			TreePath[] paths = ts.getPaths();
			for (int i = 0; i < paths.length; i++) {
				TreePath path = paths[i];
				if (containsLocalChanges(path)) {
					return true;
				}
			}
		}
		return false;
	}

	private boolean containsLocalChanges(TreePath path) {
		IResourceDiffTree tree = getDiffTree(path);
		ResourceTraversal[] traversals = getTraversals(path.getLastSegment());
		return tree.hasMatchingDiffs(traversals, getVisibleLocalChangesFilter());
	}

	private ResourceTraversal[] getTraversals(Object element) {
		if (element instanceof ChangeSet) {
			ChangeSet set = (ChangeSet) element;
			return new ResourceTraversal[] { new ResourceTraversal(set.getResources(), IResource.DEPTH_ZERO, IResource.NONE) };
		}
		if (element instanceof IProject) {
			IProject project = (IProject) element;
			return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { project }, IResource.DEPTH_INFINITE, IResource.NONE) };
		}
		if (element instanceof IFile) {
			IFile file = (IFile) element;
			return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { file }, IResource.DEPTH_ZERO, IResource.NONE) };
		}
		if (element instanceof IFolder) {
			IFolder folder = (IFolder) element;
			if (getLayout().equals(IPreferenceIds.COMPRESSED_LAYOUT)) {
				return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { folder }, IResource.DEPTH_ONE, IResource.NONE) };
			} else if (getLayout().equals(IPreferenceIds.TREE_LAYOUT)) {
				return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { folder }, IResource.DEPTH_INFINITE, IResource.NONE) };
			} else if (getLayout().equals(IPreferenceIds.FLAT_LAYOUT)) {
				return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { folder }, IResource.DEPTH_ZERO, IResource.NONE) };
			}
		}
		return new ResourceTraversal[0];
	}
	
	private FastDiffFilter getVisibleLocalChangesFilter() {
	    return new FastDiffFilter() {
			public boolean select(IDiff diff) {
				if (diff instanceof IThreeWayDiff && isVisible(diff)) {
					IThreeWayDiff twd = (IThreeWayDiff) diff;
					if (twd.getDirection() == IThreeWayDiff.OUTGOING || twd.getDirection() == IThreeWayDiff.CONFLICTING) {
						return true;
					}
				}
				return false;
			}
		};
	}

	private boolean isVisible(IDiff diff) {
		return ((SynchronizePageConfiguration)getSynchronizePageConfiguration()).isVisible(diff);
	}
	
	protected ResourceModelTraversalCalculator getTraversalCalculator() {
		return ResourceModelTraversalCalculator.getTraversalCalculator(getSynchronizePageConfiguration());
	}
	
	private ChangeSetContentProvider getContentProvider() {
		INavigatorContentExtension extension = getExtension();
		if (extension != null) {
			ITreeContentProvider provider = extension.getContentProvider();
			if (provider instanceof ChangeSetContentProvider) {
				return (ChangeSetContentProvider) provider;
			}
		}
		return null;
	}

	private INavigatorContentExtension getExtension() {
		INavigatorContentService service = getActionSite().getContentService();
		Set set = service.findContentExtensionsByTriggerPoint(getModelProvider());
		for (Iterator iter = set.iterator(); iter.hasNext();) {
			INavigatorContentExtension extension = (INavigatorContentExtension) iter.next();
			return extension;
		}
		return null;
	}

	private Object getModelProvider() {
		return ChangeSetModelProvider.getProvider();
	}

	private String getLayout() {
		return TeamUIPlugin.getPlugin().getPreferenceStore().getString(IPreferenceIds.SYNCVIEW_DEFAULT_LAYOUT);
	}
	
	public void setContext(ActionContext context) {
		super.setContext(context);
		if (context != null) {
	        if (editChangeSet != null)
		        editChangeSet.selectionChanged((IStructuredSelection)getContext().getSelection());
	        if (removeChangeSet != null)
	            removeChangeSet.selectionChanged((IStructuredSelection)getContext().getSelection());
	        if (makeDefault != null)
		        makeDefault.selectionChanged((IStructuredSelection)getContext().getSelection());
		}
	}

	protected boolean isContentProviderEnabled() {
		ChangeSetContentProvider provider = getContentProvider();
		if (provider != null) {
			return provider.isEnabled();
		}
		return false;
	}
}
