/*******************************************************************************
 * Copyright (c) 2008, 2009 Institute for Software, HSR Hochschule fuer Technik  
 * Rapperswil, University of applied sciences 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: 
 * Institute for Software - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring;

import java.util.ArrayList;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.osgi.util.NLS;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTProblem;
import org.eclipse.cdt.core.dom.ast.IASTProblemDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTProblemExpression;
import org.eclipse.cdt.core.dom.ast.IASTProblemStatement;
import org.eclipse.cdt.core.dom.ast.IASTProblemTypeId;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTTypeId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ISourceRange;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.ui.CUIPlugin;

import org.eclipse.cdt.internal.ui.refactoring.utils.SelectionHelper;

/**
 * The base class for all other refactorings, provides some common implementations for
 * condition checking, change generating, selection handling and translation unit loading.
 * @deprecated Use CRefactoring2.
 */
@Deprecated
public abstract class CRefactoring extends Refactoring {
	private static final int AST_STYLE =
			ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT | ITranslationUnit.AST_SKIP_INDEXED_HEADERS;

	protected String name = Messages.Refactoring_name; 
	protected IFile file;
	protected Region region;
	protected RefactoringStatus initStatus;
	protected IASTTranslationUnit ast;
	protected ICProject project;
	private IIndex fIndex;

	public CRefactoring(IFile file, ISelection selection, ICElement element, ICProject proj) {
		project = proj;
		if (element instanceof ISourceReference) {
			ISourceReference sourceRef= (ISourceReference) element;
			ITranslationUnit tu = sourceRef.getTranslationUnit();
			IResource res= tu.getResource();
			if (res instanceof IFile) 
				this.file= (IFile) res;
		
			try {
				final ISourceRange sourceRange = sourceRef.getSourceRange();
				this.region = new Region(sourceRange.getIdStartPos(), sourceRange.getIdLength());
			} catch (CModelException e) {
				CUIPlugin.log(e);
			}
		} else {
			this.file = file;
			this.region = SelectionHelper.getRegion(selection);
		}

		this.initStatus= new RefactoringStatus();
		if (this.file == null || region == null) {
			initStatus.addFatalError(Messages.Refactoring_SelectionNotValid);  
		}
	}

	private class ProblemFinder extends ASTVisitor {
		private boolean problemFound = false;
		private final RefactoringStatus status;
		
		public ProblemFinder(RefactoringStatus status) {
			this.status = status;
		}
		
		{
			shouldVisitProblems = true;
			shouldVisitDeclarations = true;
			shouldVisitExpressions = true;
			shouldVisitStatements = true;
			shouldVisitTypeIds = true;
		}

		@Override
		public int visit(IASTProblem problem) {
			addWarningToState();
			return ASTVisitor.PROCESS_CONTINUE;
		}
		
		@Override
		public int visit(IASTDeclaration declaration) {
			if (declaration instanceof IASTProblemDeclaration) {
				addWarningToState();
			}
			return ASTVisitor.PROCESS_CONTINUE;
		}
		
		@Override
		public int visit(IASTExpression expression) {
			if (expression instanceof IASTProblemExpression) {
				addWarningToState();
			}
			return ASTVisitor.PROCESS_CONTINUE;
		}

		@Override
		public int visit(IASTStatement statement) {
			if (statement instanceof IASTProblemStatement) {
				addWarningToState();
			}
			return ASTVisitor.PROCESS_CONTINUE;
		}

		@Override
		public int visit(IASTTypeId typeId) {
			if (typeId instanceof IASTProblemTypeId) {
				addWarningToState();
			}
			return ASTVisitor.PROCESS_CONTINUE;
		}

		public boolean hasProblem() {
			return problemFound;
		}
		
		private void addWarningToState() {
			if (!problemFound) {
				status.addWarning(Messages.Refactoring_CompileErrorInTU); 
				problemFound = true;
			}
		}
	}
	
	@Override
	public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
			throws CoreException, OperationCanceledException {
		RefactoringStatus status = new RefactoringStatus();
		return status;
	}

	@Override
	public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
			throws CoreException, OperationCanceledException {
		SubMonitor sm = SubMonitor.convert(pm, 10);
		sm.subTask(Messages.Refactoring_PM_LoadTU); 
		if (isProgressMonitorCanceld(sm, initStatus)) {
			return initStatus;
		}
		if (!loadTranslationUnit(initStatus, sm.newChild(8))) {
			initStatus.addError(Messages.Refactoring_CantLoadTU);  
			return initStatus;
		}
		if (isProgressMonitorCanceld(sm, initStatus)) {
			return initStatus;
		}
		sm.subTask(Messages.Refactoring_PM_CheckTU); 
		translationUnitHasProblem();
		if (translationUnitIsAmbiguous()) {
			initStatus.addError(Messages.Refactoring_Ambiguity); 
		}
		sm.worked(2);
		sm.subTask(Messages.Refactoring_PM_InitRef); 
		sm.done();
		return initStatus;
	}

	protected static boolean isProgressMonitorCanceld(IProgressMonitor sm, RefactoringStatus status) {
		if (sm.isCanceled()) {
			status.addFatalError(Messages.Refactoring_CanceledByUser); 
			return true;
		}
		return false;
	}

	@Override
	public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
		ModificationCollector collector = new ModificationCollector();
		collectModifications(pm, collector);
		CCompositeChange finalChange = null;
		try {
			lockIndex();
			finalChange = collector.createFinalChange();
		} catch (InterruptedException e) {
			throw new OperationCanceledException();
		} finally {
			unlockIndex();
		}
		
		finalChange.setDescription(new RefactoringChangeDescriptor(getRefactoringDescriptor()));
		return finalChange;
	}
	
	abstract protected RefactoringDescriptor getRefactoringDescriptor();

	abstract protected void collectModifications(IProgressMonitor pm, ModificationCollector collector)
			throws CoreException, OperationCanceledException;

	@Override
	public String getName() {
		return name;
	}

	protected boolean loadTranslationUnit(RefactoringStatus status, IProgressMonitor mon) {
		SubMonitor subMonitor = SubMonitor.convert(mon, 10);
		if (file != null) {
			try {
				subMonitor.subTask(Messages.Refactoring_PM_ParseTU);
				ast = loadTranslationUnit(file);
				if (ast == null) {
					subMonitor.done();
					return false;
				}
				subMonitor.worked(2);
				if (isProgressMonitorCanceld(subMonitor, initStatus)) {
					return true;
				}
				subMonitor.subTask(Messages.Refactoring_PM_MergeComments); 

				subMonitor.worked(8);
			} catch (CoreException e) {
				status.addFatalError(e.getMessage()); 
				subMonitor.done();
				return false;
			}
		} else {
			status.addFatalError(Messages.NO_FILE); 
			subMonitor.done();
			return false;
		}
		subMonitor.done();
		return true;
	}

	protected IASTTranslationUnit loadTranslationUnit(IFile file) throws CoreException {
		ITranslationUnit tu = (ITranslationUnit) CCorePlugin.getDefault().getCoreModel().create(file);
		if (tu == null) {
			initStatus.addFatalError(NLS.bind(Messages.CRefactoring_FileNotFound, file.getName()));
			return null;
		}
		return tu.getAST(fIndex, AST_STYLE);
	}

	protected boolean translationUnitHasProblem() {
		ProblemFinder pf = new ProblemFinder(initStatus);
		ast.accept(pf);		
		return pf.hasProblem();
	}
	
	protected boolean translationUnitIsAmbiguous() {
		// ambiguities are resolved before the tu is passed to the refactoring.
		return false;
	}
	
	public void lockIndex() throws CoreException, InterruptedException {
		if (fIndex == null) {
			ICProject[] projects= CoreModel.getDefault().getCModel().getCProjects();
			fIndex= CCorePlugin.getIndexManager().getIndex(projects);
		}
		fIndex.acquireReadLock();
	}
	
	public void unlockIndex() {
		if (fIndex != null) {
			fIndex.releaseReadLock();
		}
		// Marc-Andre Laperle : I don't think we want to null this out, 
		// if the lock is acquired twice then the lock can only be released once
		//fIndex= null;
	}

	public IIndex getIndex() {
		return fIndex;
	}
	
	public IASTTranslationUnit getUnit() {
		return ast;
	}

	protected ArrayList<IASTName> findAllMarkedNames() {
		final ArrayList<IASTName> namesVector = new ArrayList<IASTName>();

		ast.accept(new ASTVisitor() {
			{
				shouldVisitNames = true;
			}

			@Override
			public int visit(IASTName name) {
				if (SelectionHelper.isInSameFileSelection(region, name, file)) {
					if (!(name instanceof ICPPASTQualifiedName)) {
						namesVector.add(name);
					}
				}
				return super.visit(name);
			}
		});
		return namesVector;
	}
}
