/*******************************************************************************
 * Copyright (c) 2000, 2011 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.jdt.internal.corext.refactoring.code;

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

import org.eclipse.ltk.core.refactoring.RefactoringStatus;

import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;

import org.eclipse.jdt.internal.corext.dom.Selection;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;

public class LocalTypeAnalyzer extends ASTVisitor {

	private Selection fSelection;
	private List<AbstractTypeDeclaration> fTypeDeclarationsBefore= new ArrayList<AbstractTypeDeclaration>(2);
	private List<AbstractTypeDeclaration> fTypeDeclarationsSelected= new ArrayList<AbstractTypeDeclaration>(2);
	private String fBeforeTypeReferenced;
	private String fSelectedTypeReferenced;

	//---- Analyzing statements ----------------------------------------------------------------

	public static RefactoringStatus perform(BodyDeclaration declaration, Selection selection) {
		LocalTypeAnalyzer analyzer= new LocalTypeAnalyzer(selection);
		declaration.accept(analyzer);
		RefactoringStatus result= new RefactoringStatus();
		analyzer.check(result);
		return result;
	}

	private LocalTypeAnalyzer(Selection selection) {
		fSelection= selection;
	}

	@Override
	public boolean visit(SimpleName node) {
		if (node.isDeclaration())
			return true;
		IBinding binding= node.resolveBinding();
		if (binding instanceof ITypeBinding)
			processLocalTypeBinding((ITypeBinding) binding, fSelection.getVisitSelectionMode(node));

		return true;
	}

	@Override
	public boolean visit(TypeDeclaration node) {
		return visitType(node);
	}

	@Override
	public boolean visit(AnnotationTypeDeclaration node) {
		return visitType(node);
	}

	@Override
	public boolean visit(EnumDeclaration node) {
		return visitType(node);
	}

	private boolean visitType(AbstractTypeDeclaration node) {
		int mode= fSelection.getVisitSelectionMode(node);
		switch (mode) {
			case Selection.BEFORE:
				fTypeDeclarationsBefore.add(node);
				break;
			case Selection.SELECTED:
				fTypeDeclarationsSelected.add(node);
				break;
		}
		return true;
	}

	private void processLocalTypeBinding(ITypeBinding binding, int mode) {
		switch (mode) {
			case Selection.SELECTED:
				if (fBeforeTypeReferenced != null)
					break;
				if (checkBinding(fTypeDeclarationsBefore, binding))
					fBeforeTypeReferenced= RefactoringCoreMessages.LocalTypeAnalyzer_local_type_from_outside;
				break;
			case Selection.AFTER:
				if (fSelectedTypeReferenced != null)
					break;
				if (checkBinding(fTypeDeclarationsSelected, binding))
					fSelectedTypeReferenced= RefactoringCoreMessages.LocalTypeAnalyzer_local_type_referenced_outside;
				break;
		}
	}

	private boolean checkBinding(List<AbstractTypeDeclaration> declarations, ITypeBinding binding) {
		for (Iterator<AbstractTypeDeclaration> iter= declarations.iterator(); iter.hasNext();) {
			AbstractTypeDeclaration declaration= iter.next();
			if (declaration.resolveBinding() == binding) {
				return true;
			}
		}
		return false;
	}

	private void check(RefactoringStatus status) {
		if (fBeforeTypeReferenced != null)
			status.addFatalError(fBeforeTypeReferenced);
		if (fSelectedTypeReferenced != null)
			status.addFatalError(fSelectedTypeReferenced);
	}
}