/***************************************************************************************************
 * Copyright (c) 2005 Eteration A.S. 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: Eteration A.S. - initial API and implementation
 **************************************************************************************************/
package org.eclipse.jst.j2ee.ejb.annotations.internal.xdoclet;

import java.util.Map;

import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;

public class XDocletBuilder extends IncrementalProjectBuilder implements IExecutableExtension {

	private static final String JAVASOURCE_TYPE = "org.eclipse.jdt.core.javaSource"; //$NON-NLS-1$

	private static final boolean performValidateEdit = false;

	private static final String[] XDOCLETBUILDINCLUDEFILES = { "ejb-jar.xml", "web.xml", "bean.java", "servlet.java",
			"controller.java", "ejb.java", "mdb.java" };

	private static boolean isGloballyEnabled = true;

	/**
	 * Add the XDocletBuilder to the build spec of a single IProject
	 * 
	 * @param project -
	 *            the IProject to add to, when needed
	 */
	public static void add(IProgressMonitor monitor, IProject project, Object validateEditContext) {
		if (project == null || !project.isAccessible()) {
			return;
		}
		boolean isBuilderPresent = false;
		try {
			IFile descriptionFile = project.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
			if (descriptionFile.exists() && descriptionFile.isAccessible()) {
				IProjectDescription description = project.getDescription();
				ICommand[] commands = description.getBuildSpec();
				if (commands != null) {
					for (int i = 0; i < commands.length; i++) {
						String builderName = commands[i].getBuilderName();
						// builder name will be null if it has not been set
						if (builderName != null && builderName.equals(getBuilderId())) {
							isBuilderPresent = true;
							break;
						}
					}
				}
				if (!isBuilderPresent && !monitor.isCanceled()) {
					// validate for edit
					IStatus status = null;
					if (performValidateEdit) {
						ISchedulingRule validateEditRule = null;
						try {

							IFile[] validateFiles = new IFile[] { descriptionFile };
							IWorkspace workspace = descriptionFile.getWorkspace();
							validateEditRule = workspace.getRuleFactory().validateEditRule(validateFiles);
							Job.getJobManager().beginRule(validateEditRule, monitor);
							status = workspace.validateEdit(validateFiles, null);
						} finally {
							if (validateEditRule != null) {
								Job.getJobManager().endRule(validateEditRule);
							}
						}
					}
					if (status == null || status.isOK()) {
						// add the builder
						ICommand newCommand = description.newCommand();
						newCommand.setBuilderName(getBuilderId());
						ICommand[] newCommands = null;
						if (commands != null) {
							newCommands = new ICommand[commands.length + 1];
							System.arraycopy(commands, 0, newCommands, 0, commands.length);
							newCommands[commands.length] = newCommand;
						} else {
							newCommands = new ICommand[1];
							newCommands[0] = newCommand;
						}
						description.setBuildSpec(newCommands);
						/*
						 * This 'refresh' was added since unit tests were
						 * throwing exceptions about being out of sync. That may
						 * indicate a "deeper" problem such as needing to use
						 * scheduling rules, (although there don't appear to be
						 * examples of that) or something similar.
						 */
						// project.refreshLocal(IResource.DEPTH_ZERO,
						// subMonitorFor(monitor, 1,
						// IProgressMonitor.UNKNOWN));
						try {
							project.setDescription(description, monitor);
						} catch (CoreException e) {
							if (performValidateEdit) {
								Logger
										.log(
												Logger.WARNING,
												"Description for project \"" + project.getName() + "\" could not be updated despite successful build"); //$NON-NLS-2$//$NON-NLS-1$					
							} else {
								Logger.log(Logger.WARNING,
										"Description for project \"" + project.getName() + "\" could not be updated"); //$NON-NLS-2$//$NON-NLS-1$					
							}
						}
					}
				}
			} else {
				Logger.log(Logger.WARNING, "Description for project \"" + project.getName() + "\" could not be updated"); //$NON-NLS-2$//$NON-NLS-1$
			}
		} catch (Exception e) {
			// if we can't read the information, the project isn't open,
			// so it can't run auto-validate
			Logger.logException("Exception caught when adding Model Builder", e); //$NON-NLS-1$
		}
	}

	/**
	 * Adds the Builder to every project in the Workspace
	 * 
	 * @param root
	 */
	public synchronized static void add(IProgressMonitor monitor, IWorkspaceRoot root, Object validateEditContext) {
		IProject[] allProjects = root.getProjects();
		IProgressMonitor localMonitor = monitor;
		localMonitor.beginTask("Starting to add builder to projects with EJB modules", 1); //$NON-NLS-1$
		for (int i = 0; i < allProjects.length && !monitor.isCanceled(); i++) {
			if (XDocletPreferenceStore.forProject(allProjects[i]).getBooleanProperty(XDocletPreferenceStore.XDOCLETBUILDERACTIVE)) {
				add(localMonitor, allProjects[i], validateEditContext);
			}
			localMonitor.worked(1);
		}
		localMonitor.done();
	}

	public static String getBuilderId() {
		return "org.eclipse.jst.j2ee.ejb.annotations.internal.emitter.model.xdocletbuilder"; //$NON-NLS-1$
	}

	public static IProgressMonitor monitorFor(IProgressMonitor monitor) {
		if (monitor == null)
			return new NullProgressMonitor();
		return monitor;
	}

	private String fName = "XDoclet Builder"; //$NON-NLS-1$

	/**
	 * 
	 */
	public XDocletBuilder() {
		super();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.internal.events.InternalBuilder#build(int,
	 *      java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
	 */
	protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
		IProject currentProject = getProject();
		// Currently, just use the Task Tags preference
		boolean locallyEnabled = XDocletPreferenceStore.forProject(currentProject).getBooleanProperty(
				XDocletPreferenceStore.XDOCLETBUILDERACTIVE);
		if (!locallyEnabled || currentProject == null || !currentProject.isAccessible()  || !isValidRuntime(currentProject)) {
			return new IProject[] { currentProject };
		}

		IResourceDelta delta = getDelta(currentProject);
		IProgressMonitor localMonitor = monitor;
		localMonitor.beginTask(getDisplayName(), 1);

		if (!localMonitor.isCanceled()) {
			// check the kind of delta if one was given
			if (kind == FULL_BUILD || kind == CLEAN_BUILD || delta == null) {
				doFullBuild(kind, args, localMonitor, getProject());
			} else {
				doIncrementalBuild(kind, args, localMonitor);
			}
		}
		localMonitor.worked(1);
		localMonitor.done();

		return new IProject[] { getProject() };
	}

	void build(int kind, Map args, IResource resource, IContentType[] types, IProgressMonitor monitor) throws CoreException {
		boolean validRuntime = resource != null && isValidRuntime(resource.getProject());
		if (!monitor.isCanceled() && resource.getType() == IResource.FILE && validRuntime) {
			XDocletAntProjectBuilder antProjectBuilder = XDocletAntProjectBuilder.Factory.newInstance(resource);
			if (antProjectBuilder != null)
				antProjectBuilder.buildUsingAnt(resource, monitor);

		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.resources.IncrementalProjectBuilder#clean(org.eclipse.core.runtime.IProgressMonitor)
	 */
	protected void clean(IProgressMonitor monitor) throws CoreException {
		super.clean(monitor);
		IProject currentProject = getProject();
		if (!isGloballyEnabled || currentProject == null || !currentProject.isAccessible()) {
			return;
		}
		// doFullBuild(IncrementalProjectBuilder.CLEAN_BUILD, new HashMap(0),
		// monitor, getProject());
	}

	boolean isXDocletAnnotatedResource(IResource resource) {
		IContentType[] types = null;
		if (resource.getType() == IResource.FILE && resource.isAccessible()) {
			IContentDescription d = null;
			try {
				// optimized description lookup, might not succeed
				d = ((IFile) resource).getContentDescription();
				if (d != null && JAVASOURCE_TYPE.equals(d.getContentType().getId())) {
					return XDoxletAnnotationUtil.isXDocletAnnotatedResource(resource);
				}
			} catch (CoreException e) {
				// should not be possible given the accessible and file type
				// check above
			}
			if (types == null) {
				types = Platform.getContentTypeManager().findContentTypesFor(resource.getName());
				for (int i = 0; i < types.length; i++) {
					IContentType type = types[i];
					if (JAVASOURCE_TYPE.equals(type.getId())) {
						return XDoxletAnnotationUtil.isXDocletAnnotatedResource(resource);
					}
				}
			}
			return false;
		} else if (resource.getType() == IResource.FILE && !resource.isAccessible()) {
			// Deleted - Check to see if this is an xdoclet bean!
			// This is a crude hack to make sure the build runs is a resource is
			// deleted.
			// **Bean.java **Servlet.java **Mdb.java and deployment descriptors
			String name = resource.getName();
			for (int i = 0; name != null && i < XDOCLETBUILDINCLUDEFILES.length; i++) {
				String fileName = XDOCLETBUILDINCLUDEFILES[i];
				if (name.toLowerCase().endsWith(fileName))
					return true;

			}
		}
		return false;
	}

	/**
	 * Iterate through the list of resources and build each one
	 * 
	 * @param monitor
	 * @param resources
	 */
	protected void doFullBuild(int kind, Map args, IProgressMonitor monitor, IProject project) {

		final IProgressMonitor subMonitor = monitor;
		final int localKind = kind;
		final Map localArgs = args;

		final IProgressMonitor visitorMonitor = monitor;
		IResourceVisitor internalBuilder = new IResourceVisitor() {
			// xdoclet builder completes the whole project at once so no need to
			// repeat the build with each annotated bean. Stop after the first
			// one
			boolean buildComplete = false;

			public boolean visit(IResource resource) throws CoreException {
				if (resource.getType() == IResource.FILE && buildComplete == false) {
					// for any supported file type, record the resource
					if (!buildComplete && isXDocletAnnotatedResource(resource)) {
						build(localKind, localArgs, resource, null, subMonitor);
						buildComplete = true;
						visitorMonitor.worked(1);
					}
					return false;
				}
				return true;
			}

		};
		try {
			project.accept(internalBuilder);
		} catch (CoreException e) {
			Logger.logException(e);
		}
	}

	/**
	 * 
	 */
	protected void doIncrementalBuild(int kind, Map args, IProgressMonitor monitor) {
		IResourceDelta projectDelta = getDelta(getProject());
		if (projectDelta == null) {
			throw new IllegalArgumentException("delta is null, should do a full build"); //$NON-NLS-1$
		}

		final Map localArgs = args;
		final int localKind = kind;
		final IProgressMonitor localMonitor = monitor;
		IResourceDeltaVisitor participantVisitor = new IResourceDeltaVisitor() {
			// xdoclet builder completes the whole project at once so no need to
			// repeat the build with each annotated bean. Stop after the first
			// one
			boolean buildComplete = false;

			public boolean visit(IResourceDelta delta) throws CoreException {
				if (!localMonitor.isCanceled() && delta.getResource().getType() == IResource.FILE) {
					if (!buildComplete && isXDocletAnnotatedResource(delta.getResource())) {
						build(localKind, localArgs, delta.getResource(), null, localMonitor);
						buildComplete = true;
					}
				}
				return delta.getAffectedChildren().length > 0;
			}
		};
		try {
			projectDelta.accept(participantVisitor);
		} catch (CoreException e) {
			Logger.logException(e);
		}
		monitor.worked(1);
	}

	private String getDisplayName() {
		return fName;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement,
	 *      java.lang.String, java.lang.Object)
	 */
	public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException {
		if (config != null) {
			fName = config.getDeclaringExtension().getLabel();
		}
	}

	public static void shutdown() {
		// Default
	}

	public static void startup() {
		// Default
	}
	
	public boolean isValidRuntime(IProject currentProject) {
		XDocletBuilderValidator validator = new XDocletBuilderValidator();
		boolean valid = false;
		try {
			valid = validator.validate(currentProject);
		} catch (CoreException e) {
		}
		return valid;
	}
}
