/*******************************************************************************
 * Copyright (c) 2000, 2009 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.core.builder;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.*;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.impl.CompilerStats;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;

import java.util.*;

public class BatchImageBuilder extends AbstractImageBuilder {

	IncrementalImageBuilder incrementalBuilder; // if annotations or secondary types have to be processed after the compile loop
	ArrayList secondaryTypes; // qualified names for all secondary types found during batch compile
	StringSet typeLocatorsWithUndefinedTypes; // type locators for all source files with errors that may be caused by 'not found' secondary types

protected BatchImageBuilder(JavaBuilder javaBuilder, boolean buildStarting) {
	super(javaBuilder, buildStarting, null);
	this.nameEnvironment.isIncrementalBuild = false;
	this.incrementalBuilder = null;
	this.secondaryTypes = null;
	this.typeLocatorsWithUndefinedTypes = null;
}

public void build() {
	if (JavaBuilder.DEBUG)
		System.out.println("FULL build"); //$NON-NLS-1$

	try {
		this.notifier.subTask(Messages.bind(Messages.build_cleaningOutput, this.javaBuilder.currentProject.getName()));
		JavaBuilder.removeProblemsAndTasksFor(this.javaBuilder.currentProject);
		cleanOutputFolders(true);
		this.notifier.updateProgressDelta(0.05f);

		this.notifier.subTask(Messages.build_analyzingSources);
		ArrayList sourceFiles = new ArrayList(33);
		addAllSourceFiles(sourceFiles);
		this.notifier.updateProgressDelta(0.10f);

		if (sourceFiles.size() > 0) {
			SourceFile[] allSourceFiles = new SourceFile[sourceFiles.size()];
			sourceFiles.toArray(allSourceFiles);

			this.notifier.setProgressPerCompilationUnit(0.75f / allSourceFiles.length);
			this.workQueue.addAll(allSourceFiles);
			compile(allSourceFiles);

			if (this.typeLocatorsWithUndefinedTypes != null)
				if (this.secondaryTypes != null && !this.secondaryTypes.isEmpty())
					rebuildTypesAffectedBySecondaryTypes();
			if (this.incrementalBuilder != null)
				this.incrementalBuilder.buildAfterBatchBuild();
		}

		if (this.javaBuilder.javaProject.hasCycleMarker())
			this.javaBuilder.mustPropagateStructuralChanges();
	} catch (CoreException e) {
		throw internalException(e);
	} finally {
		if (JavaBuilder.SHOW_STATS)
			printStats();
		cleanUp();
	}
}

protected void acceptSecondaryType(ClassFile classFile) {
	if (this.secondaryTypes != null)
		this.secondaryTypes.add(classFile.fileName());
}

protected void cleanOutputFolders(boolean copyBack) throws CoreException {
	boolean deleteAll = JavaCore.CLEAN.equals(
		this.javaBuilder.javaProject.getOption(JavaCore.CORE_JAVA_BUILD_CLEAN_OUTPUT_FOLDER, true));
	if (deleteAll) {
		if (this.javaBuilder.participants != null)
			for (int i = 0, l = this.javaBuilder.participants.length; i < l; i++)
				this.javaBuilder.participants[i].cleanStarting(this.javaBuilder.javaProject);

		ArrayList visited = new ArrayList(this.sourceLocations.length);
		for (int i = 0, l = this.sourceLocations.length; i < l; i++) {
			this.notifier.subTask(Messages.bind(Messages.build_cleaningOutput, this.javaBuilder.currentProject.getName()));
			ClasspathMultiDirectory sourceLocation = this.sourceLocations[i];
			if (sourceLocation.hasIndependentOutputFolder) {
				IContainer outputFolder = sourceLocation.binaryFolder;
				if (!visited.contains(outputFolder)) {
					visited.add(outputFolder);
					IResource[] members = outputFolder.members();
					for (int j = 0, m = members.length; j < m; j++) {
						IResource member = members[j];
						if (!member.isDerived()) {
							member.accept(
								new IResourceVisitor() {
									public boolean visit(IResource resource) throws CoreException {
										resource.setDerived(true, null);
										return resource.getType() != IResource.FILE;
									}
								}
							);
						}
						member.delete(IResource.FORCE, null);
					}
				}
				this.notifier.checkCancel();
				if (copyBack)
					copyExtraResourcesBack(sourceLocation, true);
			} else {
				boolean isOutputFolder = sourceLocation.sourceFolder.equals(sourceLocation.binaryFolder);
				final char[][] exclusionPatterns =
					isOutputFolder
						? sourceLocation.exclusionPatterns
						: null; // ignore exclusionPatterns if output folder == another source folder... not this one
				final char[][] inclusionPatterns =
					isOutputFolder
						? sourceLocation.inclusionPatterns
						: null; // ignore inclusionPatterns if output folder == another source folder... not this one
				sourceLocation.binaryFolder.accept(
					new IResourceProxyVisitor() {
						public boolean visit(IResourceProxy proxy) throws CoreException {
							if (proxy.getType() == IResource.FILE) {
								if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(proxy.getName())) {
									IResource resource = proxy.requestResource();
									if (exclusionPatterns != null || inclusionPatterns != null)
										if (Util.isExcluded(resource.getFullPath(), inclusionPatterns, exclusionPatterns, false))
											return false;
									if (!resource.isDerived())
										resource.setDerived(true, null);
									resource.delete(IResource.FORCE, null);
								}
								return false;
							}
							if (exclusionPatterns != null && inclusionPatterns == null) // must walk children if inclusionPatterns != null
								if (Util.isExcluded(proxy.requestFullPath(), null, exclusionPatterns, true))
									return false;
							BatchImageBuilder.this.notifier.checkCancel();
							return true;
						}
					},
					IResource.NONE
				);
				this.notifier.checkCancel();
			}
			this.notifier.checkCancel();
		}
	} else if (copyBack) {
		for (int i = 0, l = this.sourceLocations.length; i < l; i++) {
			ClasspathMultiDirectory sourceLocation = this.sourceLocations[i];
			if (sourceLocation.hasIndependentOutputFolder)
				copyExtraResourcesBack(sourceLocation, false);
			this.notifier.checkCancel();
		}
	}
}

protected void cleanUp() {
	this.incrementalBuilder = null;
	this.secondaryTypes = null;
	this.typeLocatorsWithUndefinedTypes = null;
	super.cleanUp();
}

protected void compile(SourceFile[] units, SourceFile[] additionalUnits, boolean compilingFirstGroup) {
	if (additionalUnits != null && this.secondaryTypes == null)
		this.secondaryTypes = new ArrayList(7);
	super.compile(units, additionalUnits, compilingFirstGroup);
}

protected void copyExtraResourcesBack(ClasspathMultiDirectory sourceLocation, final boolean deletedAll) throws CoreException {
	// When, if ever, does a builder need to copy resources files (not .java or .class) into the output folder?
	// If we wipe the output folder at the beginning of the build then all 'extra' resources must be copied to the output folder.

	this.notifier.subTask(Messages.build_copyingResources);
	final int segmentCount = sourceLocation.sourceFolder.getFullPath().segmentCount();
	final char[][] exclusionPatterns = sourceLocation.exclusionPatterns;
	final char[][] inclusionPatterns = sourceLocation.inclusionPatterns;
	final IContainer outputFolder = sourceLocation.binaryFolder;
	final boolean isAlsoProject = sourceLocation.sourceFolder.equals(this.javaBuilder.currentProject);
	sourceLocation.sourceFolder.accept(
		new IResourceProxyVisitor() {
			public boolean visit(IResourceProxy proxy) throws CoreException {
				IResource resource = null;
				switch(proxy.getType()) {
					case IResource.FILE :
						if (org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(proxy.getName()) ||
							org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(proxy.getName())) return false;

						resource = proxy.requestResource();
						if (BatchImageBuilder.this.javaBuilder.filterExtraResource(resource)) return false;
						if (exclusionPatterns != null || inclusionPatterns != null)
							if (Util.isExcluded(resource.getFullPath(), inclusionPatterns, exclusionPatterns, false))
								return false;

						IPath partialPath = resource.getFullPath().removeFirstSegments(segmentCount);
						IResource copiedResource = outputFolder.getFile(partialPath);
						if (copiedResource.exists()) {
							if (deletedAll) {
								IResource originalResource = findOriginalResource(partialPath);
								String id = originalResource.getFullPath().removeFirstSegments(1).toString();
								createProblemFor(
									resource,
									null,
									Messages.bind(Messages.build_duplicateResource, id),
									BatchImageBuilder.this.javaBuilder.javaProject.getOption(JavaCore.CORE_JAVA_BUILD_DUPLICATE_RESOURCE, true));
								return false;
							}
							copiedResource.delete(IResource.FORCE, null); // last one wins
						}
						createFolder(partialPath.removeLastSegments(1), outputFolder); // ensure package folder exists
						copyResource(resource, copiedResource);
						return false;
					case IResource.FOLDER :
						resource = proxy.requestResource();
						if (BatchImageBuilder.this.javaBuilder.filterExtraResource(resource)) return false;
						if (isAlsoProject && isExcludedFromProject(resource.getFullPath())) return false; // the sourceFolder == project
						if (exclusionPatterns != null && inclusionPatterns == null) // must walk children if inclusionPatterns != null
							if (Util.isExcluded(resource.getFullPath(), null, exclusionPatterns, true))
								return false;
				}
				return true;
			}
		},
		IResource.NONE
	);
}

protected IResource findOriginalResource(IPath partialPath) {
	for (int i = 0, l = this.sourceLocations.length; i < l; i++) {
		ClasspathMultiDirectory sourceLocation = this.sourceLocations[i];
		if (sourceLocation.hasIndependentOutputFolder) {
			IResource originalResource = sourceLocation.sourceFolder.getFile(partialPath);
			if (originalResource.exists()) return originalResource;
		}
	}
	return null;
}

private void printStats() {
	if (this.compiler == null) return;
	CompilerStats compilerStats = this.compiler.stats;
	long time = compilerStats.elapsedTime();
	long lineCount = compilerStats.lineCount;
	double speed = ((int) (lineCount * 10000.0 / time)) / 10.0;
	System.out.println(">FULL BUILD STATS for: "+this.javaBuilder.javaProject.getElementName()); //$NON-NLS-1$
	System.out.println(">   compiled " + lineCount + " lines in " + time + "ms:" + speed + "lines/s"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
	System.out.print(">   parse: " + compilerStats.parseTime + " ms (" + ((int) (compilerStats.parseTime * 1000.0 / time)) / 10.0 + "%)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	System.out.print(", resolve: " + compilerStats.resolveTime + " ms (" + ((int) (compilerStats.resolveTime * 1000.0 / time)) / 10.0 + "%)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	System.out.print(", analyze: " + compilerStats.analyzeTime + " ms (" + ((int) (compilerStats.analyzeTime * 1000.0 / time)) / 10.0 + "%)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	System.out.println(", generate: " + compilerStats.generateTime + " ms (" + ((int) (compilerStats.generateTime * 1000.0 / time)) / 10.0 + "%)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}

protected void processAnnotationResults(CompilationParticipantResult[] results) {
	// to compile the compilation participant results, we need to incrementally recompile all affected types
	// whenever the generated types are initially added or structurally changed
	if (this.incrementalBuilder == null)
		this.incrementalBuilder = new IncrementalImageBuilder(this);
	this.incrementalBuilder.processAnnotationResults(results);
}

protected void rebuildTypesAffectedBySecondaryTypes() {
	// to compile types that could not find 'missing' secondary types because of multiple
	// compile groups, we need to incrementally recompile all affected types as if the missing
	// secondary types have just been added, see bug 146324
	if (this.incrementalBuilder == null)
		this.incrementalBuilder = new IncrementalImageBuilder(this);

	int count = this.secondaryTypes.size();
	StringSet qualifiedNames = new StringSet(count * 2);
	StringSet simpleNames = new StringSet(count);
	StringSet rootNames = new StringSet(3);
	while (--count >=0) {
		char[] secondaryTypeName = (char[]) this.secondaryTypes.get(count);
		IPath path = new Path(null, new String(secondaryTypeName));
		this.incrementalBuilder.addDependentsOf(path, false, qualifiedNames, simpleNames, rootNames);
	}
	this.incrementalBuilder.addAffectedSourceFiles(
		qualifiedNames,
		simpleNames,
		rootNames,
		this.typeLocatorsWithUndefinedTypes);
}

protected void storeProblemsFor(SourceFile sourceFile, CategorizedProblem[] problems) throws CoreException {
	if (sourceFile == null || problems == null || problems.length == 0) return;

	for (int i = problems.length; --i >= 0;) {
		CategorizedProblem problem = problems[i];
		if (problem != null && problem.getID() == IProblem.UndefinedType) {
			if (this.typeLocatorsWithUndefinedTypes == null)
				this.typeLocatorsWithUndefinedTypes = new StringSet(3);
			this.typeLocatorsWithUndefinedTypes.add(sourceFile.typeLocator());
			break;
		}
	}

	super.storeProblemsFor(sourceFile, problems);
}

public String toString() {
	return "batch image builder for:\n\tnew state: " + this.newState; //$NON-NLS-1$
}
}
