/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package net.sourceforge.phpdt.internal.core;

import java.util.Locale;
import java.util.Map;

import net.sourceforge.phpdt.core.ICompilationUnit;
import net.sourceforge.phpdt.core.IJavaElement;
import net.sourceforge.phpdt.core.IJavaModelStatusConstants;
import net.sourceforge.phpdt.core.IJavaProject;
import net.sourceforge.phpdt.core.IPackageFragment;
import net.sourceforge.phpdt.core.IProblemRequestor;
import net.sourceforge.phpdt.core.JavaCore;
import net.sourceforge.phpdt.core.JavaModelException;
import net.sourceforge.phpdt.core.WorkingCopyOwner;
import net.sourceforge.phpdt.core.compiler.CharOperation;
import net.sourceforge.phpdt.core.compiler.IProblem;
import net.sourceforge.phpdt.internal.compiler.CompilationResult;
import net.sourceforge.phpdt.internal.compiler.Compiler;
import net.sourceforge.phpdt.internal.compiler.DefaultErrorHandlingPolicies;
import net.sourceforge.phpdt.internal.compiler.ICompilerRequestor;
import net.sourceforge.phpdt.internal.compiler.IErrorHandlingPolicy;
import net.sourceforge.phpdt.internal.compiler.IProblemFactory;
import net.sourceforge.phpdt.internal.compiler.ast.CompilationUnitDeclaration;
import net.sourceforge.phpdt.internal.compiler.env.INameEnvironment;
import net.sourceforge.phpdt.internal.compiler.env.ISourceType;
import net.sourceforge.phpdt.internal.compiler.lookup.PackageBinding;
import net.sourceforge.phpdt.internal.compiler.parser.SourceTypeConverter;
import net.sourceforge.phpdt.internal.compiler.parser.UnitParser;
import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilation;
import net.sourceforge.phpdt.internal.compiler.problem.DefaultProblemFactory;
import net.sourceforge.phpdt.internal.core.util.Util;

import org.eclipse.core.runtime.IProgressMonitor;

/**
 * Responsible for resolving types inside a compilation unit being reconciled,
 * reporting the discovered problems to a given IProblemRequestor.
 */
public class CompilationUnitProblemFinder extends Compiler {

	/**
	 * Answer a new CompilationUnitVisitor using the given name environment and
	 * compiler options. The environment and options will be in effect for the
	 * lifetime of the compiler. When the compiler is run, compilation results
	 * are sent to the given requestor.
	 * 
	 * @param environment
	 *            org.eclipse.jdt.internal.compiler.api.env.INameEnvironment
	 *            Environment used by the compiler in order to resolve type and
	 *            package names. The name environment implements the actual
	 *            connection of the compiler to the outside world (e.g. in batch
	 *            mode the name environment is performing pure file accesses,
	 *            reuse previous build state or connection to repositories).
	 *            Note: the name environment is responsible for implementing the
	 *            actual classpath rules.
	 * 
	 * @param policy
	 *            org.eclipse.jdt.internal.compiler.api.problem.IErrorHandlingPolicy
	 *            Configurable part for problem handling, allowing the compiler
	 *            client to specify the rules for handling problems (stop on
	 *            first error or accumulate them all) and at the same time
	 *            perform some actions such as opening a dialog in UI when
	 *            compiling interactively.
	 * @see org.eclipse.jdt.internal.compiler.api.problem.DefaultErrorHandlingPolicies
	 * 
	 * @param settings
	 *            The settings to use for the resolution.
	 * 
	 * @param requestor
	 *            org.eclipse.jdt.internal.compiler.api.ICompilerRequestor
	 *            Component which will receive and persist all compilation
	 *            results and is intended to consume them as they are produced.
	 *            Typically, in a batch compiler, it is responsible for writing
	 *            out the actual .class files to the file system.
	 * @see org.eclipse.jdt.internal.compiler.api.CompilationResult
	 * 
	 * @param problemFactory
	 *            org.eclipse.jdt.internal.compiler.api.problem.IProblemFactory
	 *            Factory used inside the compiler to create problem
	 *            descriptors. It allows the compiler client to supply its own
	 *            representation of compilation problems in order to avoid
	 *            object conversions. Note that the factory is not supposed to
	 *            accumulate the created problems, the compiler will gather them
	 *            all and hand them back as part of the compilation unit result.
	 */
	protected CompilationUnitProblemFinder(INameEnvironment environment,
			IErrorHandlingPolicy policy, Map settings,
			ICompilerRequestor requestor, IProblemFactory problemFactory) {

		super(environment, policy, settings, requestor, problemFactory, true);
	}

	/**
	 * Add additional source types
	 */
	public void accept(ISourceType[] sourceTypes, PackageBinding packageBinding) {
		// ensure to jump back to toplevel type for first one (could be a
		// member)
		// while (sourceTypes[0].getEnclosingType() != null)
		// sourceTypes[0] = sourceTypes[0].getEnclosingType();

		CompilationResult result = new CompilationResult(sourceTypes[0]
				.getFileName(), 1, 1, 10); // this.options.maxProblemsPerUnit);

		// need to hold onto this
		CompilationUnitDeclaration unit = SourceTypeConverter
				.buildCompilationUnit(sourceTypes,// sourceTypes[0] is always
													// toplevel here
						true, // need field and methods
						true, // need member types
						true, // need field initialization
						lookupEnvironment.problemReporter, result);

		if (unit != null) {
			this.lookupEnvironment.buildTypeBindings(unit);
			this.lookupEnvironment.completeTypeBindings(unit, true);
		}
	}

	/*
	 * Low-level API performing the actual compilation
	 */
	protected static IErrorHandlingPolicy getHandlingPolicy() {
		return DefaultErrorHandlingPolicies.proceedWithAllProblems();
	}

	protected static INameEnvironment getNameEnvironment(
			ICompilationUnit sourceUnit) throws JavaModelException {
		return (SearchableEnvironment) ((JavaProject) sourceUnit
				.getJavaProject()).getSearchableNameEnvironment();
	}

	/*
	 * Answer the component to which will be handed back compilation results
	 * from the compiler
	 */
	protected static ICompilerRequestor getRequestor() {
		return new ICompilerRequestor() {
			public void acceptResult(CompilationResult compilationResult) {
			}
		};
	}

	protected static IProblemFactory getProblemFactory(final char[] fileName,
			final IProblemRequestor problemRequestor,
			final IProgressMonitor monitor) {

		return new DefaultProblemFactory(Locale.getDefault()) {
			public IProblem createProblem(char[] originatingFileName,
					int problemId, String[] problemArguments,
					String[] messageArguments, int severity, int startPosition,
					int endPosition, int lineNumber) {

				if (monitor != null && monitor.isCanceled()) {
					throw new AbortCompilation(true, null); // silent abort
				}

				IProblem problem = super.createProblem(originatingFileName,
						problemId, problemArguments, messageArguments,
						severity, startPosition, endPosition, lineNumber);
				// only report local problems
				if (CharOperation.equals(originatingFileName, fileName)) {
					if (JavaModelManager.VERBOSE) {
						System.out
								.println("PROBLEM FOUND while reconciling : " + problem.getMessage());//$NON-NLS-1$
					}
					problemRequestor.acceptProblem(problem);
				}
				if (monitor != null && monitor.isCanceled()) {
					throw new AbortCompilation(true, null); // silent abort
				}

				return problem;
			}
		};
	}

	public static CompilationUnitDeclaration process(
			CompilationUnitDeclaration unit, ICompilationUnit unitElement,
			char[] contents, UnitParser parser,
			WorkingCopyOwner workingCopyOwner,
			IProblemRequestor problemRequestor, IProblemFactory problemFactory,
			boolean cleanupCU, IProgressMonitor monitor)
			throws JavaModelException {

		char[] fileName = unitElement.getElementName().toCharArray();

		JavaProject project = (JavaProject) unitElement.getJavaProject();
		CompilationUnitProblemFinder problemFinder = new CompilationUnitProblemFinder(
				project.newSearchableNameEnvironment(workingCopyOwner),
				getHandlingPolicy(), project.getOptions(true), getRequestor(),
				problemFactory);
		if (parser != null) {
			problemFinder.parser = parser;
		}

		try {

			IPackageFragment packageFragment = (IPackageFragment) unitElement
					.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
			char[][] expectedPackageName = null;
			if (packageFragment != null) {
				expectedPackageName = CharOperation.splitOn('.',
						packageFragment.getElementName().toCharArray());
			}
			if (unit == null) {
				unit = problemFinder.resolve(
						new BasicCompilationUnit(contents, expectedPackageName,
								new String(fileName), unitElement), true, // verify
																			// methods
						true); // , // analyze code
				// true); // generate code
			} else {
				problemFinder.resolve(unit, null, // no need for source
						true, // verify methods
						true); // , // analyze code
				// true); // generate code
			}
			reportProblems(unit, problemRequestor, monitor);
			return unit;
		} catch (RuntimeException e) {
			// avoid breaking other tools due to internal compiler failure
			// (40334)
			Util.log(e, "Exception occurred during problem detection: "); //$NON-NLS-1$ 
			throw new JavaModelException(e,
					IJavaModelStatusConstants.COMPILER_FAILURE);
		} finally {
			if (cleanupCU && unit != null) {
				unit.cleanUp();
			}
			problemFinder.lookupEnvironment.reset();
		}
	}

	public static CompilationUnitDeclaration process(
			ICompilationUnit unitElement, char[] contents,
			WorkingCopyOwner workingCopyOwner,
			IProblemRequestor problemRequestor, boolean cleanupCU,
			IProgressMonitor monitor) throws JavaModelException {

		return process(null/* no CompilationUnitDeclaration */, unitElement,
				contents, null/* use default Parser */, workingCopyOwner,
				problemRequestor, new DefaultProblemFactory(), cleanupCU,
				monitor);
	}

	public static CompilationUnitDeclaration process(
			ICompilationUnit unitElement, IProblemRequestor problemRequestor,
			IProgressMonitor monitor) throws JavaModelException {

		char[] fileName = unitElement.getElementName().toCharArray();

		IJavaProject project = unitElement.getJavaProject();
		CompilationUnitProblemFinder problemFinder = new CompilationUnitProblemFinder(
				getNameEnvironment(unitElement), getHandlingPolicy(), project
						.getOptions(true), getRequestor(), getProblemFactory(
						fileName, problemRequestor, monitor));

		CompilationUnitDeclaration unit = null;
		try {
			String encoding = project.getOption(JavaCore.CORE_ENCODING, true);

			IPackageFragment packageFragment = (IPackageFragment) unitElement
					.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
			char[][] expectedPackageName = null;
			if (packageFragment != null) {
				expectedPackageName = CharOperation.splitOn('.',
						packageFragment.getElementName().toCharArray());
			}
			unit = problemFinder.resolve(new BasicCompilationUnit(unitElement
					.getSource().toCharArray(), expectedPackageName,
					new String(fileName), encoding), true, // verify methods
					true); // analyze code
			// true); // generate code
			return unit;
		} finally {
			if (unit != null) {
				unit.cleanUp();
			}
			problemFinder.lookupEnvironment.reset();
		}
	}

	private static void reportProblems(CompilationUnitDeclaration unit,
			IProblemRequestor problemRequestor, IProgressMonitor monitor) {
		CompilationResult unitResult = unit.compilationResult;
		IProblem[] problems = unitResult.getAllProblems();
		for (int i = 0, problemLength = problems == null ? 0 : problems.length; i < problemLength; i++) {
			if (JavaModelManager.VERBOSE) {
				System.out
						.println("PROBLEM FOUND while reconciling : " + problems[i].getMessage());//$NON-NLS-1$
			}
			if (monitor != null && monitor.isCanceled())
				break;

			problemRequestor.acceptProblem(problems[i]);
		}
	}
}
