/*******************************************************************************
 * Copyright (c) 2000, 2010 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;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.util.Util;

public class SetContainerOperation extends ChangeClasspathOperation {

	IPath containerPath;
	IJavaProject[] affectedProjects;
	IClasspathContainer[] respectiveContainers;

	/*
	 * Creates a new SetContainerOperation.
	 */
	public SetContainerOperation(IPath containerPath, IJavaProject[] affectedProjects, IClasspathContainer[] respectiveContainers) {
		super(new IJavaElement[] {JavaModelManager.getJavaModelManager().getJavaModel()}, !ResourcesPlugin.getWorkspace().isTreeLocked());
		this.containerPath = containerPath;
		this.affectedProjects = affectedProjects;
		this.respectiveContainers = respectiveContainers;
	}

	protected void executeOperation() throws JavaModelException {
		checkCanceled();
		try {
			beginTask("", 1); //$NON-NLS-1$
			if (JavaModelManager.CP_RESOLVE_VERBOSE)
				verbose_set_container();
			if (JavaModelManager.CP_RESOLVE_VERBOSE_ADVANCED)
				verbose_set_container_invocation_trace();

			JavaModelManager manager = JavaModelManager.getJavaModelManager();
			if (manager.containerPutIfInitializingWithSameEntries(this.containerPath, this.affectedProjects, this.respectiveContainers))
				return;

			final int projectLength = this.affectedProjects.length;
			final IJavaProject[] modifiedProjects;
			System.arraycopy(this.affectedProjects, 0, modifiedProjects = new IJavaProject[projectLength], 0, projectLength);

			// filter out unmodified project containers
			int remaining = 0;
			for (int i = 0; i < projectLength; i++){
				if (isCanceled())
					return;
				JavaProject affectedProject = (JavaProject) this.affectedProjects[i];
				IClasspathContainer newContainer = this.respectiveContainers[i];
				if (newContainer == null) newContainer = JavaModelManager.CONTAINER_INITIALIZATION_IN_PROGRESS; // 30920 - prevent infinite loop
				boolean found = false;
				if (JavaProject.hasJavaNature(affectedProject.getProject())){
					IClasspathEntry[] rawClasspath = affectedProject.getRawClasspath();
					for (int j = 0, cpLength = rawClasspath.length; j <cpLength; j++) {
						IClasspathEntry entry = rawClasspath[j];
						if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER && entry.getPath().equals(this.containerPath)){
							found = true;
							break;
						}
					}
				}
				if (!found) {
					modifiedProjects[i] = null; // filter out this project - does not reference the container path, or isnt't yet Java project
					manager.containerPut(affectedProject, this.containerPath, newContainer);
					continue;
				}
				IClasspathContainer oldContainer = manager.containerGet(affectedProject, this.containerPath);
				if (oldContainer == JavaModelManager.CONTAINER_INITIALIZATION_IN_PROGRESS) {
					oldContainer = null;
				}
				if ((oldContainer != null && oldContainer.equals(this.respectiveContainers[i]))
						|| (oldContainer == this.respectiveContainers[i])/*handle case where old and new containers are null (see bug 149043*/) {
					modifiedProjects[i] = null; // filter out this project - container did not change
					continue;
				}
				remaining++;
				manager.containerPut(affectedProject, this.containerPath, newContainer);
			}

			if (remaining == 0) return;

			// trigger model refresh
			try {
				for(int i = 0; i < projectLength; i++){
					this.progressMonitor.setWorkRemaining(projectLength - i);
					if (isCanceled())
						return;

					JavaProject affectedProject = (JavaProject)modifiedProjects[i];
					if (affectedProject == null) continue; // was filtered out
					if (JavaModelManager.CP_RESOLVE_VERBOSE_ADVANCED)
						verbose_update_project(affectedProject);

					// force resolved classpath to be recomputed
					ClasspathChange classpathChange = affectedProject.getPerProjectInfo().resetResolvedClasspath();

					// if needed, generate delta, update project ref, create markers, ...
					classpathChanged(classpathChange, i==0/*refresh external linked folder only once*/);

					if (this.canChangeResources) {
						// touch project to force a build if needed
						try {
							affectedProject.getProject().touch(this.progressMonitor.split(1));
						} catch (CoreException e) {
							// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=148970
							if (!ExternalJavaProject.EXTERNAL_PROJECT_NAME.equals(affectedProject.getElementName()))
								throw e;
						}
					}
				}
			} catch(CoreException e) {
				if (JavaModelManager.CP_RESOLVE_VERBOSE || JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE)
					verbose_failure(e);
				if (e instanceof JavaModelException) {
					throw (JavaModelException)e;
				} else {
					throw new JavaModelException(e);
				}
			} finally {
				for (int i = 0; i < projectLength; i++) {
					if (this.respectiveContainers[i] == null) {
						manager.containerPut(this.affectedProjects[i], this.containerPath, null); // reset init in progress marker
					}
				}
			}
		} finally {
			done();
		}
	}

	private void verbose_failure(CoreException e) {
		Util.verbose(
			"CPContainer SET  - FAILED DUE TO EXCEPTION\n" + //$NON-NLS-1$
			"	container path: " + this.containerPath, //$NON-NLS-1$
			System.err);
		e.printStackTrace();
	}

	private void verbose_update_project(JavaProject affectedProject) {
		Util.verbose(
			"CPContainer SET  - updating affected project due to setting container\n" + //$NON-NLS-1$
			"	project: " + affectedProject.getElementName() + '\n' + //$NON-NLS-1$
			"	container path: " + this.containerPath); //$NON-NLS-1$
	}

	private void verbose_set_container() {
		Util.verbose(
			"CPContainer SET  - setting container\n" + //$NON-NLS-1$
			"	container path: " + this.containerPath + '\n' + //$NON-NLS-1$
			"	projects: {" +//$NON-NLS-1$
			org.eclipse.jdt.internal.compiler.util.Util.toString(
				this.affectedProjects,
				new org.eclipse.jdt.internal.compiler.util.Util.Displayable(){
					public String displayString(Object o) { return ((IJavaProject) o).getElementName(); }
				}) +
			"}\n	values: {\n"  +//$NON-NLS-1$
			org.eclipse.jdt.internal.compiler.util.Util.toString(
				this.respectiveContainers,
				new org.eclipse.jdt.internal.compiler.util.Util.Displayable(){
					public String displayString(Object o) {
						StringBuffer buffer = new StringBuffer("		"); //$NON-NLS-1$
						if (o == null) {
							buffer.append("<null>"); //$NON-NLS-1$
							return buffer.toString();
						}
						IClasspathContainer container = (IClasspathContainer) o;
						buffer.append(container.getDescription());
						buffer.append(" {\n"); //$NON-NLS-1$
						IClasspathEntry[] entries = container.getClasspathEntries();
						if (entries != null){
							for (int i = 0; i < entries.length; i++){
								buffer.append(" 			"); //$NON-NLS-1$
								buffer.append(entries[i]);
								buffer.append('\n');
							}
						}
						buffer.append(" 		}"); //$NON-NLS-1$
						return buffer.toString();
					}
				}) +
			"\n	}");//$NON-NLS-1$
	}

	private void verbose_set_container_invocation_trace() {
		Util.verbose(
			"CPContainer SET  - setting container\n" + //$NON-NLS-1$
			"	invocation stack trace:"); //$NON-NLS-1$
			new Exception("<Fake exception>").printStackTrace(System.out); //$NON-NLS-1$
	}

}
