/*******************************************************************************
 * 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.compiler;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;

import net.sourceforge.phpdt.core.compiler.IProblem;
import net.sourceforge.phpdt.internal.compiler.ast.CompilationUnitDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.TypeDeclaration;
import net.sourceforge.phpdt.internal.compiler.env.IBinaryType;
import net.sourceforge.phpdt.internal.compiler.env.ICompilationUnit;
import net.sourceforge.phpdt.internal.compiler.env.INameEnvironment;
import net.sourceforge.phpdt.internal.compiler.env.ISourceType;
import net.sourceforge.phpdt.internal.compiler.impl.CompilerOptions;
import net.sourceforge.phpdt.internal.compiler.impl.ITypeRequestor;
import net.sourceforge.phpdt.internal.compiler.lookup.LookupEnvironment;
import net.sourceforge.phpdt.internal.compiler.lookup.PackageBinding;
import net.sourceforge.phpdt.internal.compiler.parser.UnitParser;
import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilation;
import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilationUnit;
import net.sourceforge.phpdt.internal.compiler.problem.ProblemReporter;
import net.sourceforge.phpdt.internal.compiler.problem.ProblemSeverities;
import net.sourceforge.phpdt.internal.compiler.util.Util;

public class Compiler implements ITypeRequestor, ProblemSeverities {
	public UnitParser parser;

	public ICompilerRequestor requestor;

	public CompilerOptions options;

	public ProblemReporter problemReporter;

	// management of unit to be processed
	// public CompilationUnitResult currentCompilationUnitResult;
	public CompilationUnitDeclaration[] unitsToProcess;

	public int totalUnits; // (totalUnits-1) gives the last unit in
							// unitToProcess

	// name lookup
	public LookupEnvironment lookupEnvironment;

	// ONCE STABILIZED, THESE SHOULD RETURN TO A FINAL FIELD
	public static boolean DEBUG = false;

	public int parseThreshold = -1;

	// number of initial units parsed at once (-1: none)
	/*
	 * Static requestor reserved to listening compilation results in debug mode,
	 * so as for example to monitor compiler activity independantly from a
	 * particular builder implementation. It is reset at the end of compilation,
	 * and should not persist any information after having been reset.
	 */
	// public static IDebugRequestor DebugRequestor = null;
	/**
	 * Answer a new compiler 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.DefaultErrorHandlingPolicies
	 * 
	 * @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.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.
	 */
	public Compiler(INameEnvironment environment, IErrorHandlingPolicy policy,
			Map settings, final ICompilerRequestor requestor,
			IProblemFactory problemFactory) {
		// create a problem handler given a handling policy
		this.options = new CompilerOptions(settings);
		// wrap requestor in DebugRequestor if one is specified
		// if(DebugRequestor == null) {
		this.requestor = requestor;
		// } else {
		// this.requestor = new ICompilerRequestor(){
		// public void acceptResult(CompilationResult result){
		// if (DebugRequestor.isActive()){
		// DebugRequestor.acceptDebugResult(result);
		// }
		// requestor.acceptResult(result);
		// }
		// };
		// }
		this.problemReporter = new ProblemReporter(policy, this.options,
				problemFactory);
		this.lookupEnvironment = new LookupEnvironment(this, problemReporter,
				environment); // options, problemReporter, environment);
		this.parser = new UnitParser(problemReporter);
		// this.options.parseLiteralExpressionsAsConstants,
		// options.sourceLevel >= CompilerOptions.JDK1_4);
	}

	/**
	 * Answer a new compiler 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.DefaultErrorHandlingPolicies
	 * 
	 * @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.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.
	 * @param parseLiteralExpressionsAsConstants
	 *            <code>boolean</code> This parameter is used to optimize the
	 *            literals or leave them as they are in the source. If you put
	 *            true, "Hello" . " world" will be converted to "Hello world".
	 */
	public Compiler(INameEnvironment environment, IErrorHandlingPolicy policy,
			Map settings, final ICompilerRequestor requestor,
			IProblemFactory problemFactory,
			boolean parseLiteralExpressionsAsConstants) {
		// create a problem handler given a handling policy
		this.options = new CompilerOptions(settings);
		// wrap requestor in DebugRequestor if one is specified
		// if(DebugRequestor == null) {
		this.requestor = requestor;
		// } else {
		// this.requestor = new ICompilerRequestor(){
		// public void acceptResult(CompilationResult result){
		// if (DebugRequestor.isActive()){
		// DebugRequestor.acceptDebugResult(result);
		// }
		// requestor.acceptResult(result);
		// }
		// };
		// }
		this.problemReporter = new ProblemReporter(policy, this.options,
				problemFactory);
		this.lookupEnvironment = new LookupEnvironment(this, problemReporter,
				environment);// options, problemReporter, environment);
		this.parser = new UnitParser(problemReporter);
		// parseLiteralExpressionsAsConstants,
		// this.options.sourceLevel >= CompilerOptions.JDK1_4);
	}

	/**
	 * Add an additional binary type
	 */
	public void accept(IBinaryType binaryType, PackageBinding packageBinding) {
		lookupEnvironment.createBinaryTypeFrom(binaryType, packageBinding);
	}

	/**
	 * Add an additional compilation unit into the loop -> build compilation
	 * unit declarations, their bindings and record their results.
	 */
	public void accept(ICompilationUnit sourceUnit) {
		// Switch the current policy and compilation result for this unit to the
		// requested one.
		CompilationResult unitResult = new CompilationResult(sourceUnit,
				totalUnits, totalUnits, this.options.maxProblemsPerUnit);
		try {
			// diet parsing for large collection of unit
			CompilationUnitDeclaration parsedUnit;
			if (totalUnits < parseThreshold) {
				parsedUnit = parser.parse(sourceUnit, unitResult, false);
			} else {
				parsedUnit = parser.dietParse(sourceUnit, unitResult);
			}
			if (options.verbose) {
				String count = String.valueOf(totalUnits + 1);
				System.out.println(Util.bind("compilation.request", //$NON-NLS-1$
						new String[] { count, count,
								new String(sourceUnit.getFileName()) }));
			}
			// initial type binding creation
			lookupEnvironment.buildTypeBindings(parsedUnit);
			this.addCompilationUnit(sourceUnit, parsedUnit);
			// binding resolution
			lookupEnvironment.completeTypeBindings(parsedUnit);
		} catch (AbortCompilationUnit e) {
			// at this point, currentCompilationUnitResult may not be
			// sourceUnit, but
			// some other
			// one requested further along to resolve sourceUnit.
			if (unitResult.compilationUnit == sourceUnit) { // only report once
				requestor.acceptResult(unitResult.tagAsAccepted());
			} else {
				throw e; // want to abort enclosing request to compile
			}
		}
	}

	/**
	 * Add additional source types
	 */
	public void accept(ISourceType[] sourceTypes, PackageBinding packageBinding) {
		problemReporter.abortDueToInternalError(Util.bind(
				"abort.againstSourceModel ", //$NON-NLS-1$
				String.valueOf(sourceTypes[0].getName()), String
						.valueOf(sourceTypes[0].getFileName())));
	}

	protected void addCompilationUnit(ICompilationUnit sourceUnit,
			CompilationUnitDeclaration parsedUnit) {
		// append the unit to the list of ones to process later on
		int size = unitsToProcess.length;
		if (totalUnits == size)
			// when growing reposition units starting at position 0
			System
					.arraycopy(
							unitsToProcess,
							0,
							(unitsToProcess = new CompilationUnitDeclaration[size * 2]),
							0, totalUnits);
		unitsToProcess[totalUnits++] = parsedUnit;
	}

	/**
	 * Add the initial set of compilation units into the loop -> build
	 * compilation unit declarations, their bindings and record their results.
	 */
	protected void beginToCompile(ICompilationUnit[] sourceUnits) {
		int maxUnits = sourceUnits.length;
		totalUnits = 0;
		unitsToProcess = new CompilationUnitDeclaration[maxUnits];
		// Switch the current policy and compilation result for this unit to the
		// requested one.
		for (int i = 0; i < maxUnits; i++) {
			CompilationUnitDeclaration parsedUnit;
			CompilationResult unitResult = new CompilationResult(
					sourceUnits[i], i, maxUnits,
					this.options.maxProblemsPerUnit);
			try {
				// diet parsing for large collection of units
				if (totalUnits < parseThreshold) {
					parsedUnit = parser
							.parse(sourceUnits[i], unitResult, false);
				} else {
					parsedUnit = parser.dietParse(sourceUnits[i], unitResult);
				}
				if (options.verbose) {
					System.out
							.println(Util.bind("compilation.request", //$NON-NLS-1$
									new String[] {
											String.valueOf(i + 1),
											String.valueOf(maxUnits),
											new String(sourceUnits[i]
													.getFileName()) }));
				}
				// initial type binding creation
				// lookupEnvironment.buildTypeBindings(parsedUnit);
				this.addCompilationUnit(sourceUnits[i], parsedUnit);
				// } catch (AbortCompilationUnit e) {
				// requestor.acceptResult(unitResult.tagAsAccepted());
			} finally {
				sourceUnits[i] = null; // no longer hold onto the unit
			}
		}
		// binding resolution
		lookupEnvironment.completeTypeBindings();
	}

	/**
	 * General API -> compile each of supplied files -> recompile any required
	 * types for which we have an incomplete principle structure
	 */
	public void compile(ICompilationUnit[] sourceUnits) {
		CompilationUnitDeclaration unit = null;
		int i = 0;
		try {
			// build and record parsed units
			beginToCompile(sourceUnits);
			// process all units (some more could be injected in the loop by the
			// lookup environment)
			for (; i < totalUnits; i++) {
				unit = unitsToProcess[i];
				try {
					if (options.verbose)
						System.out.println(Util.bind("compilation.process", //$NON-NLS-1$
								new String[] {
										String.valueOf(i + 1),
										String.valueOf(totalUnits),
										new String(unitsToProcess[i]
												.getFileName()) }));
					process(unit, i);
				} finally {
					// cleanup compilation unit result
					unit.cleanUp();
					if (options.verbose)
						System.out.println(Util.bind("compilation.done", //$NON-NLS-1$
								new String[] {
										String.valueOf(i + 1),
										String.valueOf(totalUnits),
										new String(unitsToProcess[i]
												.getFileName()) }));
				}
				unitsToProcess[i] = null; // release reference to processed
											// unit
				// declaration
				requestor.acceptResult(unit.compilationResult.tagAsAccepted());
			}
		} catch (AbortCompilation e) {
			this.handleInternalException(e, unit);
		} catch (Error e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} catch (RuntimeException e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} finally {
			this.reset();
		}
		// if (options.verbose) {
		// if (totalUnits > 1) {
		// System.out.println(
		// ProjectPrefUtil.bind("compilation.units" ,
		// String.valueOf(totalUnits)));
		// //$NON-NLS-1$
		// } else {
		// System.out.println(
		// ProjectPrefUtil.bind("compilation.unit" ,
		// String.valueOf(totalUnits)));
		// //$NON-NLS-1$
		// }
		// }
	}

	protected void getMethodBodies(CompilationUnitDeclaration unit, int place) {
		// fill the methods bodies in order for the code to be generated
		if (unit.ignoreMethodBodies) {
			unit.ignoreFurtherInvestigation = true;
			return;
			// if initial diet parse did not work, no need to dig into method
			// bodies.
		}
		if (place < parseThreshold)
			return; // work already done ...
		// real parse of the method....
		parser.scanner.setSource(unit.compilationResult.compilationUnit
				.getContents());
		if (unit.types != null) {
			for (int i = unit.types.size(); --i >= 0;)
				if (unit.types.get(i) instanceof TypeDeclaration) {
					((TypeDeclaration) unit.types.get(i)).parseMethod(parser,
							unit);
				}
		}
	}

	/*
	 * Compiler crash recovery in case of unexpected runtime exceptions
	 */
	protected void handleInternalException(Throwable internalException,
			CompilationUnitDeclaration unit, CompilationResult result) {
		/* dump a stack trace to the console */
		internalException.printStackTrace();
		/* find a compilation result */
		if ((unit != null)) // basing result upon the current unit if available
			result = unit.compilationResult; // current unit being processed
												// ?
		if ((result == null) && (unitsToProcess != null) && (totalUnits > 0))
			result = unitsToProcess[totalUnits - 1].compilationResult;
		// last unit in beginToCompile ?
		if (result != null) {
			/* create and record a compilation problem */
			StringWriter stringWriter = new StringWriter();
			PrintWriter writer = new PrintWriter(stringWriter);
			internalException.printStackTrace(writer);
			StringBuffer buffer = stringWriter.getBuffer();
			String[] pbArguments = new String[] { Util
					.bind("compilation.internalError")
					//$NON-NLS-1$
					+ "\n" //$NON-NLS-1$
					+ buffer.toString() };
			result.record(problemReporter.createProblem(result.getFileName(),
					IProblem.Unclassified, pbArguments, pbArguments, Error, // severity
					0, // source start
					0, // source end
					0, // line number
					unit, result), unit);
			/* hand back the compilation result */
			if (!result.hasBeenAccepted) {
				requestor.acceptResult(result.tagAsAccepted());
			}
		}
	}

	/*
	 * Compiler recovery in case of internal AbortCompilation event
	 */
	protected void handleInternalException(AbortCompilation abortException,
			CompilationUnitDeclaration unit) {
		/*
		 * special treatment for SilentAbort: silently cancelling the
		 * compilation process
		 */
		if (abortException.isSilent) {
			if (abortException.silentException == null) {
				return;
			} else {
				throw abortException.silentException;
			}
		}
		/* uncomment following line to see where the abort came from */
		// abortException.printStackTrace();
		// Exception may tell which compilation result it is related, and which
		// problem caused it
		CompilationResult result = abortException.compilationResult;
		if ((result == null) && (unit != null))
			result = unit.compilationResult; // current unit being processed
												// ?
		if ((result == null) && (unitsToProcess != null) && (totalUnits > 0))
			result = unitsToProcess[totalUnits - 1].compilationResult;
		// last unit in beginToCompile ?
		if (result != null && !result.hasBeenAccepted) {
			/* distant problem which could not be reported back there */
			if (abortException.problemId != 0) {
				result.record(problemReporter.createProblem(result
						.getFileName(), abortException.problemId,
						abortException.problemArguments,
						abortException.messageArguments, Error, // severity
						0, // source start
						0, // source end
						0, // line number
						unit, result), unit);
			} else {
				/*
				 * distant internal exception which could not be reported back
				 * there
				 */
				if (abortException.exception != null) {
					this.handleInternalException(abortException.exception,
							null, result);
					return;
				}
			}
			/* hand back the compilation result */
			if (!result.hasBeenAccepted) {
				requestor.acceptResult(result.tagAsAccepted());
			}
		} else {
			/*
			 * if (abortException.problemId != 0){ IProblem problem =
			 * problemReporter.createProblem( "???".toCharArray(),
			 * abortException.problemId, abortException.problemArguments, Error, //
			 * severity 0, // source start 0, // source end 0); // line number
			 * System.out.println(problem.getMessage()); }
			 */
			abortException.printStackTrace();
		}
	}

	/**
	 * Process a compilation unit already parsed and build.
	 */
	public void process(CompilationUnitDeclaration unit, int i) {
		getMethodBodies(unit, i);
		// fault in fields & methods
		if (unit.scope != null)
			unit.scope.faultInTypes();
		// verify inherited methods
		// if (unit.scope != null)
		// unit.scope.verifyMethods(lookupEnvironment.methodVerifier());
		// type checking
		unit.resolve();
		// flow analysis
		unit.analyseCode();
		// code generation
		// unit.generateCode();
		// reference info
		// if (options.produceReferenceInfo && unit.scope != null)
		// unit.scope.storeDependencyInfo();
		// refresh the total number of units known at this stage
		unit.compilationResult.totalUnitsKnown = totalUnits;
	}

	public void reset() {
		lookupEnvironment.reset();
		parser.scanner.source = null;
		unitsToProcess = null;
		// if (DebugRequestor != null) DebugRequestor.reset();
	}

	/**
	 * Internal API used to resolve a given compilation unit. Can run a subset
	 * of the compilation process
	 */
	public CompilationUnitDeclaration resolve(CompilationUnitDeclaration unit,
			ICompilationUnit sourceUnit, boolean verifyMethods,
			boolean analyzeCode) {

		try {
			if (unit == null) {
				// build and record parsed units
				parseThreshold = 0; // will request a full parse
				beginToCompile(new ICompilationUnit[] { sourceUnit });
				// process all units (some more could be injected in the loop by
				// the lookup environment)
				unit = unitsToProcess[0];
			} else {
				// initial type binding creation
				lookupEnvironment.buildTypeBindings(unit);

				// binding resolution
				lookupEnvironment.completeTypeBindings();
			}
			// TODO : jsurfer check this
			// this.parser.getMethodBodies(unit);
			getMethodBodies(unit, 0);

			if (unit.scope != null) {
				// fault in fields & methods
				unit.scope.faultInTypes();
				if (unit.scope != null && verifyMethods) {
					// http://dev.eclipse.org/bugs/show_bug.cgi?id=23117
					// verify inherited methods
					unit.scope
							.verifyMethods(lookupEnvironment.methodVerifier());
				}
				// type checking
				unit.resolve();

				// flow analysis
				// if (analyzeCode) unit.analyseCode();

				// code generation
				// if (generateCode) unit.generateCode();
			}
			if (unitsToProcess != null)
				unitsToProcess[0] = null; // release reference to processed
											// unit declaration
			requestor.acceptResult(unit.compilationResult.tagAsAccepted());
			return unit;
		} catch (AbortCompilation e) {
			this.handleInternalException(e, unit);
			return unit == null ? unitsToProcess[0] : unit;
		} catch (Error e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} catch (RuntimeException e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} finally {
			// No reset is performed there anymore since,
			// within the CodeAssist (or related tools),
			// the compiler may be called *after* a call
			// to this resolve(...) method. And such a call
			// needs to have a compiler with a non-empty
			// environment.
			// this.reset();
		}
	}

	/**
	 * Internal API used to resolve a given compilation unit. Can run a subset
	 * of the compilation process
	 */
	public CompilationUnitDeclaration resolve(ICompilationUnit sourceUnit,
			boolean verifyMethods, boolean analyzeCode) {
		// boolean generateCode) {
		CompilationUnitDeclaration unit = null;
		try {
			// build and record parsed units
			parseThreshold = 0; // will request a full parse
			beginToCompile(new ICompilationUnit[] { sourceUnit });
			// process all units (some more could be injected in the loop by the
			// lookup environment)
			unit = unitsToProcess[0];
			getMethodBodies(unit, 0);
			if (unit.scope != null) {
				// // fault in fields & methods
				// unit.scope.faultInTypes();
				// if (unit.scope != null && verifyMethods) {
				// // http://dev.eclipse.org/bugs/show_bug.cgi?id=23117
				// // verify inherited methods
				// unit.scope.verifyMethods(lookupEnvironment.methodVerifier());
				// }
				// // type checking
				// unit.resolve();
				// flow analysis
				// if (analyzeCode) unit.analyseCode();
				// code generation
				// if (generateCode) unit.generateCode();
			}
			unitsToProcess[0] = null; // release reference to processed unit
			// declaration
			requestor.acceptResult(unit.compilationResult.tagAsAccepted());
			return unit;
		} catch (AbortCompilation e) {
			this.handleInternalException(e, unit);
			return unit == null ? unitsToProcess[0] : unit;
		} catch (Error e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} catch (RuntimeException e) {
			this.handleInternalException(e, unit, null);
			throw e; // rethrow
		} finally {
			// No reset is performed there anymore since,
			// within the CodeAssist (or related tools),
			// the compiler may be called *after* a call
			// to this resolve(...) method. And such a call
			// needs to have a compiler with a non-empty
			// environment.
			// this.reset();
		}
	}

}