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

import java.util.ArrayList;
import java.util.Locale;

import net.sourceforge.phpdt.core.IJavaModelMarker;
import net.sourceforge.phpdt.core.JavaCore;
import net.sourceforge.phpdt.core.JavaModelException;
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.problem.AbortCompilation;
import net.sourceforge.phpdt.internal.compiler.problem.ProblemHandler;
import net.sourceforge.phpdt.internal.core.util.Util;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

/**
 * The abstract superclass of Java builders. Provides the building and
 * compilation mechanism in common with the batch and incremental builders.
 */
public abstract class AbstractImageBuilder implements ICompilerRequestor {

	protected PHPBuilder javaBuilder;

	protected State newState;

	// local copies
	protected NameEnvironment nameEnvironment;

	protected ClasspathMultiDirectory[] sourceLocations;

	protected BuildNotifier notifier;

	protected String encoding;

	protected Compiler compiler;

	protected WorkQueue workQueue;

	protected ArrayList problemSourceFiles;

	protected boolean compiledAllAtOnce;

	private boolean inCompiler;

	public static int MAX_AT_ONCE = 1000;

	protected AbstractImageBuilder(PHPBuilder javaBuilder) {
		this.javaBuilder = javaBuilder;
		this.newState = new State(javaBuilder);

		// local copies
		this.nameEnvironment = javaBuilder.nameEnvironment;
		this.sourceLocations = this.nameEnvironment.sourceLocations;
		this.notifier = javaBuilder.notifier;

		this.encoding = javaBuilder.javaProject.getOption(
				JavaCore.CORE_ENCODING, true);
		this.compiler = newCompiler();
		this.workQueue = new WorkQueue();
		this.problemSourceFiles = new ArrayList(3);
	}

	public void acceptResult(CompilationResult result) {
		// In Batch mode, we write out the class files, hold onto the dependency
		// info
		// & additional types and report problems.

		// In Incremental mode, when writing out a class file we need to compare
		// it
		// against the previous file, remembering if structural changes occured.
		// Before reporting the new problems, we need to update the problem
		// count &
		// remove the old problems. Plus delete additional class files that no
		// longer exist.

		SourceFile compilationUnit = (SourceFile) result.getCompilationUnit(); // go
																				// directly
																				// back
																				// to
																				// the
																				// sourceFile
		if (!workQueue.isCompiled(compilationUnit)) {
			// try {
			workQueue.finished(compilationUnit);
			try {
				updateProblemsFor(compilationUnit, result); // record
															// compilation
															// problems before
															// potentially
															// adding duplicate
															// errors
				updateTasksFor(compilationUnit, result); // record tasks
			} catch (CoreException e) {
				throw internalException(e);
			}

			// String typeLocator = compilationUnit.typeLocator();
			// ClassFile[] classFiles = result.getClassFiles();
			// int length = classFiles.length;
			// ArrayList duplicateTypeNames = null;
			// ArrayList definedTypeNames = new ArrayList(length);
			// for (int i = 0; i < length; i++) {
			// ClassFile classFile = classFiles[i];
			// char[][] compoundName = classFile.getCompoundName();
			// char[] typeName = compoundName[compoundName.length - 1];
			// boolean isNestedType = CharOperation.contains('$', typeName);
			//
			// // Look for a possible collision, if one exists, report an error
			// but do not write the class file
			// if (isNestedType) {
			// String qualifiedTypeName = new
			// String(classFile.outerMostEnclosingClassFile().fileName());
			// if (newState.isDuplicateLocator(qualifiedTypeName, typeLocator))
			// continue;
			// } else {
			// String qualifiedTypeName = new String(classFile.fileName()); //
			// the qualified type name "p1/p2/A"
			// if (newState.isDuplicateLocator(qualifiedTypeName, typeLocator))
			// {
			// if (duplicateTypeNames == null)
			// duplicateTypeNames = new ArrayList();
			// duplicateTypeNames.add(compoundName);
			// createErrorFor(compilationUnit.resource,
			// ProjectPrefUtil.bind("build.duplicateClassFile", new
			// String(typeName)));
			// //$NON-NLS-1$
			// continue;
			// }
			// newState.recordLocatorForType(qualifiedTypeName, typeLocator);
			// }
			// definedTypeNames.add(writeClassFile(classFile,
			// compilationUnit.sourceLocation.binaryFolder, !isNestedType));
			// }

			// finishedWith(typeLocator, result,
			// compilationUnit.getMainTypeName(), definedTypeNames,
			// duplicateTypeNames);
			notifier.compiled(compilationUnit);
			// } catch (CoreException e) {
			// Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$
			// createErrorFor(compilationUnit.resource,
			// Util.bind("build.inconsistentClassFile")); //$NON-NLS-1$
			// }
		}
	}

	protected void cleanUp() {
		this.nameEnvironment.cleanup();

		this.javaBuilder = null;
		this.nameEnvironment = null;
		this.sourceLocations = null;
		this.notifier = null;
		this.compiler = null;
		this.workQueue = null;
		this.problemSourceFiles = null;
	}

	/*
	 * Compile the given elements, adding more elements to the work queue if
	 * they are affected by the changes.
	 */
	protected void compile(SourceFile[] units) {
		int toDo = units.length;
		if (this.compiledAllAtOnce = toDo <= MAX_AT_ONCE) {
			// do them all now
			if (PHPBuilder.DEBUG)
				for (int i = 0; i < toDo; i++)
					System.out
							.println("About to compile " + units[i].typeLocator()); //$NON-NLS-1$
			compile(units, null);
		} else {
			int i = 0;
			boolean compilingFirstGroup = true;
			while (i < toDo) {
				int doNow = toDo < MAX_AT_ONCE ? toDo : MAX_AT_ONCE;
				int index = 0;
				SourceFile[] toCompile = new SourceFile[doNow];
				while (i < toDo && index < doNow) {
					// Although it needed compiling when this method was called,
					// it may have
					// already been compiled when it was referenced by another
					// unit.
					SourceFile unit = units[i++];
					if (compilingFirstGroup || workQueue.isWaiting(unit)) {
						if (PHPBuilder.DEBUG)
							System.out
									.println("About to compile " + unit.typeLocator()); //$NON-NLS-1$
						toCompile[index++] = unit;
					}
				}
				if (index < doNow)
					System.arraycopy(toCompile, 0,
							toCompile = new SourceFile[index], 0, index);
				SourceFile[] additionalUnits = new SourceFile[toDo - i];
				System.arraycopy(units, i, additionalUnits, 0,
						additionalUnits.length);
				compilingFirstGroup = false;
				compile(toCompile, additionalUnits);
			}
		}
	}

	void compile(SourceFile[] units, SourceFile[] additionalUnits) {
		if (units.length == 0)
			return;
		notifier.aboutToCompile(units[0]); // just to change the message

		// extend additionalFilenames with all hierarchical problem types found
		// during this entire build
		if (!problemSourceFiles.isEmpty()) {
			int toAdd = problemSourceFiles.size();
			int length = additionalUnits == null ? 0 : additionalUnits.length;
			if (length == 0)
				additionalUnits = new SourceFile[toAdd];
			else
				System.arraycopy(additionalUnits, 0,
						additionalUnits = new SourceFile[length + toAdd], 0,
						length);
			for (int i = 0; i < toAdd; i++)
				additionalUnits[length + i] = (SourceFile) problemSourceFiles
						.get(i);
		}
		String[] initialTypeNames = new String[units.length];
		for (int i = 0, l = units.length; i < l; i++)
			initialTypeNames[i] = units[i].initialTypeName;
		nameEnvironment.setNames(initialTypeNames, additionalUnits);
		notifier.checkCancel();
		try {
			inCompiler = true;
			compiler.compile(units);
		} catch (AbortCompilation ignored) {
			// ignore the AbortCompilcation coming from
			// BuildNotifier.checkCancelWithinCompiler()
			// the Compiler failed after the user has chose to cancel... likely
			// due to an OutOfMemory error
		} finally {
			inCompiler = false;
		}
		// Check for cancel immediately after a compile, because the compiler
		// may
		// have been cancelled but without propagating the correct exception
		notifier.checkCancel();
	}

	protected void createErrorFor(IResource resource, String message) {
		try {
			IMarker marker = resource
					.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
			int severity = IMarker.SEVERITY_ERROR;
			if (message.equals(Util.bind("build.duplicateResource"))) //$NON-NLS-1$
				if (JavaCore.WARNING.equals(javaBuilder.javaProject.getOption(
						JavaCore.CORE_JAVA_BUILD_DUPLICATE_RESOURCE, true)))
					severity = IMarker.SEVERITY_WARNING;
			marker.setAttributes(new String[] { IMarker.MESSAGE,
					IMarker.SEVERITY, IMarker.CHAR_START, IMarker.CHAR_END },
					new Object[] { message, new Integer(severity),
							new Integer(0), new Integer(1) });
		} catch (CoreException e) {
			throw internalException(e);
		}
	}

	// protected void finishedWith(String sourceLocator, CompilationResult
	// result, char[] mainTypeName) throws CoreException {//,
	// ArrayList definedTypeNames, ArrayList duplicateTypeNames) throws
	// CoreException {
	// if (duplicateTypeNames == null) {
	// newState.record(sourceLocator, result.qualifiedReferences,
	// result.simpleNameReferences, mainTypeName, definedTypeNames);
	// return;
	// }
	//
	// char[][][] qualifiedRefs = result.qualifiedReferences;
	// char[][] simpleRefs = result.simpleNameReferences;
	// // for each duplicate type p1.p2.A, add the type name A (package was
	// already added)
	// next : for (int i = 0, l = duplicateTypeNames.size(); i < l; i++) {
	// char[][] compoundName = (char[][]) duplicateTypeNames.get(i);
	// char[] typeName = compoundName[compoundName.length - 1];
	// int sLength = simpleRefs.length;
	// for (int j = 0; j < sLength; j++)
	// if (CharOperation.equals(simpleRefs[j], typeName))
	// continue next;
	// System.arraycopy(simpleRefs, 0, simpleRefs = new char[sLength + 1][], 0,
	// sLength);
	// simpleRefs[sLength] = typeName;
	// }
	// newState.record(sourceLocator, qualifiedRefs, simpleRefs, mainTypeName,
	// definedTypeNames);
	// }

	// protected IContainer createFolder(IPath packagePath, IContainer
	// outputFolder) throws CoreException {
	// if (packagePath.isEmpty()) return outputFolder;
	// IFolder folder = outputFolder.getFolder(packagePath);
	// if (!folder.exists()) {
	// createFolder(packagePath.removeLastSegments(1), outputFolder);
	// folder.create(true, true, null);
	// folder.setDerived(true);
	// }
	// return folder;
	// }

	protected RuntimeException internalException(CoreException t) {
		ImageBuilderInternalException imageBuilderException = new ImageBuilderInternalException(
				t);
		if (inCompiler)
			return new AbortCompilation(true, imageBuilderException);
		return imageBuilderException;
	}

	protected Compiler newCompiler() {
		// called once when the builder is initialized... can override if needed
		return new Compiler(nameEnvironment, DefaultErrorHandlingPolicies
				.proceedWithAllProblems(), javaBuilder.javaProject
				.getOptions(true), this, ProblemFactory
				.getProblemFactory(Locale.getDefault()));
	}

	protected boolean isExcludedFromProject(IPath childPath)
			throws JavaModelException {
		// answer whether the folder should be ignored when walking the project
		// as a source folder
		if (childPath.segmentCount() > 2)
			return false; // is a subfolder of a package

		for (int j = 0, k = sourceLocations.length; j < k; j++) {
			// if
			// (childPath.equals(sourceLocations[j].binaryFolder.getFullPath()))
			// return true;
			if (childPath.equals(sourceLocations[j].sourceFolder.getFullPath()))
				return true;
		}
		// skip default output folder which may not be used by any source folder
		return false; // childPath.equals(javaBuilder.javaProject.getOutputLocation());
	}

	/**
	 * Creates a marker from each problem and adds it to the resource. The
	 * marker is as follows: - its type is T_PROBLEM - its plugin ID is the
	 * JavaBuilder's plugin ID - its message is the problem's message - its
	 * priority reflects the severity of the problem - its range is the
	 * problem's range - it has an extra attribute "ID" which holds the
	 * problem's id
	 */
	protected void storeProblemsFor(SourceFile sourceFile, IProblem[] problems)
			throws CoreException {
		if (sourceFile == null || problems == null || problems.length == 0)
			return;

		// String missingClassFile = null;
		IResource resource = sourceFile.resource;
		for (int i = 0, l = problems.length; i < l; i++) {
			IProblem problem = problems[i];
			int id = problem.getID();
			switch (id) {
			case IProblem.IsClassPathCorrect:
				// PHPBuilder.removeProblemsAndTasksFor(javaBuilder.currentProject);
				// // make this the only problem for this project
				// String[] args = problem.getArguments();
				// missingClassFile = args[0];
				break;
			case IProblem.SuperclassMustBeAClass:
			case IProblem.SuperInterfaceMustBeAnInterface:
			case IProblem.HierarchyCircularitySelfReference:
			case IProblem.HierarchyCircularity:
			case IProblem.HierarchyHasProblems:
			case IProblem.SuperclassNotFound:
			case IProblem.SuperclassNotVisible:
			case IProblem.SuperclassAmbiguous:
			case IProblem.SuperclassInternalNameProvided:
			case IProblem.SuperclassInheritedNameHidesEnclosingName:
			case IProblem.InterfaceNotFound:
			case IProblem.InterfaceNotVisible:
			case IProblem.InterfaceAmbiguous:
			case IProblem.InterfaceInternalNameProvided:
			case IProblem.InterfaceInheritedNameHidesEnclosingName:
				// ensure that this file is always retrieved from source for the
				// rest of the build
				if (!problemSourceFiles.contains(sourceFile))
					problemSourceFiles.add(sourceFile);
				break;
			}

			if (id != IProblem.Task) {
				IMarker marker = resource
						.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
				marker
						.setAttributes(
								new String[] { IMarker.MESSAGE,
										IMarker.SEVERITY, IJavaModelMarker.ID,
										IMarker.CHAR_START, IMarker.CHAR_END,
										IMarker.LINE_NUMBER,
										IJavaModelMarker.ARGUMENTS },
								new Object[] {
										problem.getMessage(),
										new Integer(
												problem.isError() ? IMarker.SEVERITY_ERROR
														: IMarker.SEVERITY_WARNING),
										new Integer(id),
										new Integer(problem.getSourceStart()),
										new Integer(problem.getSourceEnd() + 1),
										new Integer(problem
												.getSourceLineNumber()),
										Util
												.getProblemArgumentsForMarker(problem
														.getArguments()) });
			}

			/*
			 * Do NOT want to populate the Java Model just to find the matching
			 * Java element. Also cannot query compilation units located in
			 * folders with invalid package names such as 'a/b.c.d/e'. //
			 * compute a user-friendly location IJavaElement element =
			 * JavaCore.create(resource); if (element instanceof
			 * net.sourceforge.phpdt.core.ICompilationUnit) { // try to find a
			 * finer grain element net.sourceforge.phpdt.core.ICompilationUnit
			 * unit = (net.sourceforge.phpdt.core.ICompilationUnit) element;
			 * IJavaElement fragment =
			 * unit.getElementAt(problem.getSourceStart()); if (fragment !=
			 * null) element = fragment; } String location = null; if (element
			 * instanceof JavaElement) location = ((JavaElement)
			 * element).readableName(); if (location != null)
			 * marker.setAttribute(IMarker.LOCATION, location);
			 */

			// if (missingClassFile != null)
			// throw new MissingClassFileException(missingClassFile);
		}
	}

	protected void storeTasksFor(SourceFile sourceFile, IProblem[] tasks)
			throws CoreException {
		if (sourceFile == null || tasks == null || tasks.length == 0)
			return;

		IResource resource = sourceFile.resource;
		for (int i = 0, l = tasks.length; i < l; i++) {
			IProblem task = tasks[i];
			if (task.getID() == IProblem.Task) {
				IMarker marker = resource
						.createMarker(IJavaModelMarker.TASK_MARKER);
				int priority = IMarker.PRIORITY_NORMAL;
				String compilerPriority = task.getArguments()[2];
				if (JavaCore.COMPILER_TASK_PRIORITY_HIGH
						.equals(compilerPriority))
					priority = IMarker.PRIORITY_HIGH;
				else if (JavaCore.COMPILER_TASK_PRIORITY_LOW
						.equals(compilerPriority))
					priority = IMarker.PRIORITY_LOW;
				marker.setAttributes(new String[] { IMarker.MESSAGE,
						IMarker.PRIORITY, IMarker.DONE, IMarker.CHAR_START,
						IMarker.CHAR_END, IMarker.LINE_NUMBER,
						IMarker.USER_EDITABLE, }, new Object[] {
						task.getMessage(), new Integer(priority),
						new Boolean(false), new Integer(task.getSourceStart()),
						new Integer(task.getSourceEnd() + 1),
						new Integer(task.getSourceLineNumber()),
						new Boolean(false), });
			}
		}
	}

	protected void updateProblemsFor(SourceFile sourceFile,
			CompilationResult result) throws CoreException {
		IProblem[] problems = result.getProblems();
		if (problems == null || problems.length == 0)
			return;
		// axelcl start insert - calculate line numbers
		if (problems != null) {
			for (int i = 0; i < problems.length; i++) {
				if (problems[i].getSourceLineNumber() == 1) {
					problems[i].setSourceLineNumber(ProblemHandler
							.searchLineNumber(result.lineSeparatorPositions,
									problems[i].getSourceStart()));
				}
			}
		}
		// axelcl end insert
		notifier.updateProblemCounts(problems);
		storeProblemsFor(sourceFile, problems);
	}

	protected void updateTasksFor(SourceFile sourceFile,
			CompilationResult result) throws CoreException {
		IProblem[] tasks = result.getTasks();
		if (tasks == null || tasks.length == 0)
			return;

		storeTasksFor(sourceFile, tasks);
	}

	// protected char[] writeClassFile(ClassFile classFile, IContainer
	// outputFolder, boolean isSecondaryType) throws CoreException {
	// String fileName = new String(classFile.fileName()); // the qualified type
	// name "p1/p2/A"
	// IPath filePath = new Path(fileName);
	// IContainer container = outputFolder;
	// if (filePath.segmentCount() > 1) {
	// container = createFolder(filePath.removeLastSegments(1), outputFolder);
	// filePath = new Path(filePath.lastSegment());
	// }
	//
	// IFile file =
	// container.getFile(filePath.addFileExtension(JavaBuilder.CLASS_EXTENSION));
	// writeClassFileBytes(classFile.getBytes(), file, fileName,
	// isSecondaryType);
	// // answer the name of the class file as in Y or Y$M
	// return filePath.lastSegment().toCharArray();
	// }
	//
	// protected void writeClassFileBytes(byte[] bytes, IFile file, String
	// qualifiedFileName, boolean isSecondaryType) throws
	// CoreException {
	// if (file.exists()) {
	// // Deal with shared output folders... last one wins... no collision cases
	// detected
	// if (JavaBuilder.DEBUG)
	// System.out.println("Writing changed class file " +
	// file.getName());//$NON-NLS-1$
	// file.setContents(new ByteArrayInputStream(bytes), true, false, null);
	// if (!file.isDerived())
	// file.setDerived(true);
	// } else {
	// // Default implementation just writes out the bytes for the new class
	// file...
	// if (JavaBuilder.DEBUG)
	// System.out.println("Writing new class file " +
	// file.getName());//$NON-NLS-1$
	// file.create(new ByteArrayInputStream(bytes), IResource.FORCE, null);
	// file.setDerived(true);
	// }
	// }
}