/*******************************************************************************
 * Copyright (c) 2004, 2010 Wind River Systems, Inc. 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: 
 *     Markus Schorn - initial API and implementation 
 *     IBM Corporation - Bug 112366
 *     Sergey Prigogin (Google)
 ******************************************************************************/ 
package org.eclipse.cdt.internal.ui.refactoring.rename;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextEditChangeGroup;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
import org.eclipse.ltk.core.refactoring.participants.ValidateEditChecker;
import org.eclipse.osgi.util.NLS;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEditGroup;

import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexFileLocation;
import org.eclipse.cdt.core.index.IIndexName;
import org.eclipse.cdt.ui.refactoring.CTextFileChange;

/**
 * Abstract base for all different rename processors used by the top processor.
 */
public abstract class CRenameProcessorDelegate {
    private CRenameProcessor fTopProcessor;
    private ArrayList<CRefactoringMatch> fMatches;
    protected String fProcessorBaseName;
    private int fAvailableOptions=
    		CRefactory.OPTION_IN_CODE_REFERENCES |
	        CRefactory.OPTION_IN_COMMENT | 
	        CRefactory.OPTION_IN_MACRO_DEFINITION |
	        CRefactory.OPTION_IN_STRING_LITERAL |
    		CRefactory.OPTION_EXHAUSTIVE_FILE_SEARCH; 

    private int fOptionsForcingPreview=
    		CRefactory.OPTION_IN_COMMENT | 
	        CRefactory.OPTION_IN_MACRO_DEFINITION |
	        CRefactory.OPTION_IN_PREPROCESSOR_DIRECTIVE |
	        CRefactory.OPTION_IN_STRING_LITERAL;

    private int fOptionsEnablingExhaustiveSearch= fOptionsForcingPreview |
    		CRefactory.OPTION_IN_INACTIVE_CODE | CRefactory.OPTION_IN_CODE_REFERENCES;

    protected CRenameProcessorDelegate(CRenameProcessor topProcessor, String name) {
        fTopProcessor= topProcessor;
        fProcessorBaseName= name;
    }
    
    final public CRefactoringArgument getArgument() {
        return fTopProcessor.getArgument();
    }

    final public String getReplacementText() {
        return fTopProcessor.getReplacementText();
    }

    final public int getSelectedScope() {
        return fTopProcessor.getExhaustiveSearchScope();
    }

    final public int getSelectedOptions() {
        return fTopProcessor.getSelectedOptions();
    }

    final public String getSelectedWorkingSet() {
        return fTopProcessor.getWorkingSetName();
    }

    final public CRefactory getManager() {
        return fTopProcessor.getManager();
    }

    final public ASTManager getAstManager() {
        return fTopProcessor.getAstManager();
    }

    final public IIndex getIndex() {
    	return fTopProcessor.getIndex();
    }

    final public String getProcessorName() {
        String identifier= getArgument().getName();
        if (identifier != null) {
            return NLS.bind(RenameMessages.CRenameProcessorDelegate_wizard_title, fProcessorBaseName,
            		identifier);
        }
        return null;
    }

    /**
     * The options presented by the page in the refactoring wizard.
     */
    public void setAvailableOptions(int options) {
        fAvailableOptions= options;
    }

    final int getAvailableOptions() {
        return fAvailableOptions;
    }

    /**
     * The options each of which forces the preview, when selected.
     */
    public void setOptionsForcingPreview(int options) {
        fOptionsForcingPreview= options;
    }
    
    final int getOptionsForcingPreview() {
    	return fOptionsForcingPreview;
    }

    /**
     * Sets the options that enable exhaustive file search.
     * @see #getOptionsEnablingExhaustiveSearch()
     */
    void setOptionsEnablingExhaustiveSearch(int options) {
        fOptionsEnablingExhaustiveSearch= options;
    }

    /**
     * The options that may need exhaustive file search since index lookup is not guaranteed to
     * return all files participating in refactoring. When one of these options is selected,
     * the exhaustive file search is enabled.
     */
    final int getOptionsEnablingExhaustiveSearch() {
        return fOptionsEnablingExhaustiveSearch;
    }

    protected int getSearchScope() {
        return getSelectedScope();
    }

    /**
     * Builds an index-based file filter for the name search.
     * 
     * @param bindings bindings being renamed
     * @return A set of files containing references to the bindings.
     */
    private Collection<IFile> getReferringFiles(IBinding[] bindings) {
		ArrayList<IFile> files = new ArrayList<>();
    	IIndex index = getIndex();
    	if (index == null) {
    		return files;
    	}
		Set<IIndexFileLocation> locations = new HashSet<>();
    	try {
    		index.acquireReadLock();
        	for (IBinding binding : bindings) {
				IIndexName[] names = index.findNames(binding,
						IIndex.FIND_ALL_OCCURRENCES | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES);
				for (IIndexName name : names) {
					locations.add(name.getFile().getLocation());
				}
        	}
		} catch (InterruptedException e) {
			return files;
		} catch (CoreException e) {
			return files;
		} finally {
    		index.releaseReadLock();
    	}

    	files.ensureCapacity(locations.size());
		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		for (IIndexFileLocation location : locations) {
			String fullPath= location.getFullPath();
			if (fullPath != null) {
				IResource file= workspaceRoot.findMember(fullPath);
				if (file instanceof IFile) {
					files.add((IFile) file);
				}
			}
		}

		return files;
	}

    public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
        return new RefactoringStatus();
    }

    public RefactoringStatus checkFinalConditions(IProgressMonitor monitor, CheckConditionsContext context)
    		throws CoreException, OperationCanceledException {
        RefactoringStatus result= new RefactoringStatus();
        monitor.beginTask(RenameMessages.CRenameProcessorDelegate_task_checkFinalCondition, 2);
        IFile file= getArgument().getSourceFile();
        //assert file != null;

        IBinding[] renameBindings= getBindingsToBeRenamed(result);

        // Perform text search.
        fMatches= new ArrayList<>();
        TextSearchWrapper txtSearch= getManager().getTextSearch();
        Collection<IFile> filesToSearch = getReferringFiles(renameBindings);
        if (!filesToSearch.contains(file)) {
        	filesToSearch.add(file);
        }
        IStatus stat= txtSearch.searchWord(filesToSearch.toArray(new IFile[filesToSearch.size()]),
        		getSearchScope(), file, getSelectedWorkingSet(), getManager().getCCppPatterns(),
        		getArgument().getName(), new SubProgressMonitor(monitor, 1), fMatches);
        if (monitor.isCanceled()) {
            throw new OperationCanceledException();
        }
        result.merge(RefactoringStatus.create(stat));
        if (result.hasFatalError()) {
            return result;
        }
        selectMatchesByLocation(fMatches);
        analyzeTextMatches(renameBindings, fMatches, new SubProgressMonitor(monitor, 1), result);
        if (result.hasFatalError()) {
            return result;
        }
        
        HashSet<IFile> fileset= new HashSet<>();
        int potentialMatchCount= 0;
        int commentCount= 0;
        for (Iterator<CRefactoringMatch> iter = fMatches.iterator(); iter.hasNext();) {
            CRefactoringMatch tm = iter.next();
            if (tm.isInComment()) {
                commentCount++;
                fileset.add(tm.getFile());
            } else {
                switch (tm.getAstInformation()) {
                case CRefactoringMatch.AST_REFERENCE_OTHER:
                    iter.remove();
                    break;
                case CRefactoringMatch.POTENTIAL:
                    potentialMatchCount++;
                    fileset.add(tm.getFile());
                    break;
                default:
                    fileset.add(tm.getFile());
                    break;
                }
            }
        }
        if (potentialMatchCount != 0) {
            String msg= null;
            if (potentialMatchCount == 1) {
                msg= RenameMessages.CRenameProcessorDelegate_warning_potentialMatch_singular;
            } else {
                msg= NLS.bind(RenameMessages.CRenameProcessorDelegate_warning_potentialMatch_plural,
                		potentialMatchCount);
            }
            result.addWarning(msg);
        }
        if (commentCount != 0) {
            String msg= null;
            if (commentCount == 1) {
                msg= RenameMessages.CRenameProcessorDelegate_warning_commentMatch_singular;
            } else {
                msg= NLS.bind(RenameMessages.CRenameProcessorDelegate_warning_commentMatch_plural,
                		commentCount);
            }
            result.addWarning(msg);
        }
        IFile[] files= fileset.toArray(new IFile[fileset.size()]);
        if (context != null) {
            ValidateEditChecker editChecker=
            		(ValidateEditChecker) context.getChecker(ValidateEditChecker.class);
            editChecker.addFiles(files);
        }
        monitor.done();
        return result;
    }

	protected void analyzeTextMatches(IBinding[] renameBindings, Collection<CRefactoringMatch> matches,
			IProgressMonitor monitor, RefactoringStatus status) {
        if (renameBindings.length > 0 && getArgument().getArgumentKind() != CRefactory.ARGUMENT_UNKNOWN) {
            ASTManager mngr= getAstManager();
            mngr.setValidBindings(renameBindings);
            mngr.setRenameTo(getReplacementText());
            mngr.analyzeTextMatches(fTopProcessor.getIndex(), matches, monitor, status);
        }
    }

    private void selectMatchesByLocation(Collection<CRefactoringMatch> matches) {
        int acceptTextLocation= getAcceptedLocations(getSelectedOptions());
        for (Iterator<CRefactoringMatch> iter = matches.iterator(); iter.hasNext();) {
            CRefactoringMatch match = iter.next();
            int location= match.getLocation();
            if (location != 0 && (location & acceptTextLocation) == 0) {
                iter.remove();
            }
        }
    }

    protected int getAcceptedLocations(int selectedOptions) {
        return selectedOptions;
    }

    public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
        if (fMatches.isEmpty()) {
            return null;
        }
        Collections.sort(fMatches, new Comparator<CRefactoringMatch>() {
            @Override
			public int compare(CRefactoringMatch m1, CRefactoringMatch m2) {
                IFile f1= m1.getFile();
                IFile f2= m2.getFile();
                int cmp= f1.getName().compareTo(f2.getName());
                if (cmp != 0) return cmp;
    
                cmp= f1.getFullPath().toString().compareTo(f2.getFullPath().toString());   
                if (cmp != 0) return cmp;
                
                return m1.getOffset() - m2.getOffset();
            }});
        pm.beginTask(RenameMessages.CRenameProcessorDelegate_task_createChange, fMatches.size());
        final String identifier= getArgument().getName();
        final String replacement= getReplacementText();
        CompositeChange overallChange= new CompositeChange(getProcessorName()); 
        IFile file= null;
        TextFileChange fileChange= null;
        MultiTextEdit fileEdit= null;
        for (CRefactoringMatch match : fMatches) {
            switch (match.getAstInformation()) {
            case CRefactoringMatch.AST_REFERENCE_OTHER:
                continue;
            case CRefactoringMatch.IN_COMMENT:
            case CRefactoringMatch.POTENTIAL:
                break;
            case CRefactoringMatch.AST_REFERENCE:
                break;
            }
            if (match.getAstInformation() != CRefactoringMatch.AST_REFERENCE_OTHER) {
                IFile mfile= match.getFile();
                if (file == null || !file.equals(mfile) || fileEdit == null || fileChange == null) {
                    file= mfile;
                    fileEdit= new MultiTextEdit();
                    fileChange = new CTextFileChange(file.getName(), file);
                    fileChange.setEdit(fileEdit);
                    overallChange.add(fileChange);
                }
                
                ReplaceEdit replaceEdit= new ReplaceEdit(match.getOffset(), identifier.length(), replacement);
                fileEdit.addChild(replaceEdit);
                TextEditGroup editGroup= new TextEditGroup(match.getLabel(), replaceEdit);
                TextEditChangeGroup changeGroup= new TextEditChangeGroup(fileChange, editGroup);
                fileChange.addTextEditChangeGroup(changeGroup);
            }
            pm.worked(1);
        }
        return overallChange;
    }

    /**
     * Returns the array of bindings that must be renamed.
     */
    protected IBinding[] getBindingsToBeRenamed(RefactoringStatus status) {
        return new IBinding[] { getArgument().getBinding() };
    }

	/**
	 * @return a save mode from {@link org.eclipse.cdt.internal.ui.refactoring.RefactoringSaveHelper}
	 */
	public abstract int getSaveMode();
}
