/*******************************************************************************
 * Copyright (c) 2009, 2011 Alena Laskavaia
 * 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:
 *     Alena Laskavaia  - initial API and implementation
 *     Sergey Prigogin (Google)
 *******************************************************************************/
package org.eclipse.cdt.codan.internal.core;

import java.util.Map;

import org.eclipse.cdt.codan.core.CodanCorePlugin;
import org.eclipse.cdt.codan.core.Messages;
import org.eclipse.cdt.codan.core.model.CheckerLaunchMode;
import org.eclipse.cdt.codan.core.model.Checkers;
import org.eclipse.cdt.codan.core.model.IChecker;
import org.eclipse.cdt.codan.core.model.ICheckerInvocationContext;
import org.eclipse.cdt.codan.core.model.ICodanBuilder;
import org.eclipse.cdt.codan.core.model.IRunnableInEditorChecker;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;

/**
 * Implementation of {@link ICodanBuilder}
 */
public class CodanBuilder extends IncrementalProjectBuilder implements ICodanBuilder {
	/**
	 * Codan builder id
	 */
	public static final String BUILDER_ID = "org.eclipse.cdt.codan.core.codanBuilder"; //$NON-NLS-1$

	private class CodanDeltaVisitor implements IResourceDeltaVisitor {
		private IProgressMonitor monitor;

		/**
		 * @param monitor
		 */
		public CodanDeltaVisitor(IProgressMonitor monitor) {
			this.monitor = monitor;
		}

		public boolean visit(IResourceDelta delta) throws CoreException {
			IResource resource = delta.getResource();
			switch (delta.getKind()) {
				case IResourceDelta.ADDED:
					// handle added resource
					processResourceDelta(resource, monitor);
					break;
				case IResourceDelta.REMOVED:
					// handle removed resource
					break;
				case IResourceDelta.CHANGED:
					// handle changed resource
					processResourceDelta(resource, monitor);
					break;
			}
			// return true to continue visiting children.
			return true;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.core.internal.events.InternalBuilder#build(int,
	 * java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
	 */
	@SuppressWarnings("rawtypes")
	@Override
	protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
		if (kind == FULL_BUILD) {
			fullBuild(monitor);
		} else {
			IResourceDelta delta = getDelta(getProject());
			if (delta == null) {
				fullBuild(monitor);
			} else {
				incrementalBuild(delta, monitor);
			}
		}
		return null;
	}

	public void processResource(IResource resource, IProgressMonitor monitor) {
		processResource(resource, monitor, null, CheckerLaunchMode.RUN_ON_FULL_BUILD);
	}

	/**
	 * Run code analysis on given resource in a given mode
	 *
	 * @param resource - resource to process
	 * @param monitor - progress monitor
	 * @param mode - launch mode, @see {@link CheckerLaunchMode}
	 * @since 2.0
	 */
	public void processResource(IResource resource, IProgressMonitor monitor, CheckerLaunchMode mode) {
		processResource(resource, monitor, null, mode);
	}

	private void processResourceDelta(IResource resource, IProgressMonitor monitor) {
		processResource(resource, monitor, CheckerLaunchMode.RUN_ON_INC_BUILD);
	}

	protected void processResource(IResource resource, IProgressMonitor monitor, Object model, CheckerLaunchMode checkerLaunchMode) {
		CheckersRegistry chegistry = CheckersRegistry.getInstance();
		int checkers = chegistry.getCheckersSize();
		int memsize = 0;
		if (resource instanceof IContainer) {
			try {
				IResource[] members = ((IContainer) resource).members();
				memsize = members.length;
			} catch (CoreException e) {
				CodanCorePlugin.log(e);
			}
		}
		int tick = 1000;
		// System.err.println("processing " + resource);
		monitor.beginTask(Messages.CodanBuilder_Code_Analysis_On + resource, checkers + memsize * tick);
		try {
			CheckersTimeStats.getInstance().checkerStart(CheckersTimeStats.ALL);
			ICheckerInvocationContext context = new CheckerInvocationContext(resource);
			try {
				for (IChecker checker : chegistry) {
					try {
						if (monitor.isCanceled())
							return;
						if (doesCheckerSupportLaunchMode(checker, checkerLaunchMode)
								&& checker.enabledInContext(resource)
								&& chegistry.isCheckerEnabledForLaunchMode(checker, resource, checkerLaunchMode)) {
							synchronized (checker) {
								try {
									checker.before(resource);
									if (chegistry.isCheckerEnabled(checker, resource)) {
										try {
											CheckersTimeStats.getInstance().checkerStart(checker.getClass().getName());
											if (checkerLaunchMode == CheckerLaunchMode.RUN_AS_YOU_TYPE) {
												((IRunnableInEditorChecker) checker).processModel(model, context);
											} else {
												checker.processResource(resource, context);
											}
										} finally {
											CheckersTimeStats.getInstance().checkerStop(checker.getClass().getName());
										}
									}
								} finally {
									checker.after(resource);
								}
							}
						}
						monitor.worked(1);
					} catch (OperationCanceledException e) {
						return;
					} catch (Throwable e) {
						CodanCorePlugin.log(e);
					}
				}
			} finally {
				context.dispose();
				CheckersTimeStats.getInstance().checkerStop(CheckersTimeStats.ALL);
				//CheckersTimeStats.getInstance().printStats();
			}
			if (resource instanceof IContainer
					&& (checkerLaunchMode == CheckerLaunchMode.RUN_ON_FULL_BUILD || checkerLaunchMode == CheckerLaunchMode.RUN_ON_DEMAND)) {
				try {
					IResource[] members = ((IContainer) resource).members();
					for (int i = 0; i < members.length; i++) {
						if (monitor.isCanceled())
							return;
						IResource member = members[i];
						processResource(member, new SubProgressMonitor(monitor, tick));
					}
				} catch (CoreException e) {
					CodanCorePlugin.log(e);
				}
			}
		} finally {
			monitor.done();
		}
	}

	private boolean doesCheckerSupportLaunchMode(IChecker checker, CheckerLaunchMode mode) {
		if (mode == CheckerLaunchMode.RUN_AS_YOU_TYPE)
			return Checkers.canCheckerRunAsYouType(checker);
		return true;
	}

	protected void fullBuild(final IProgressMonitor monitor) throws CoreException {
		processResource(getProject(), monitor);
	}

	protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException {
		// the visitor does the work.
		delta.accept(new CodanDeltaVisitor(monitor));
	}

	/**
	 * Run all checkers that support "check as you type" mode
	 *
	 * @param model - model of given resource such as ast
	 * @param resource - resource to process
	 * @param monitor - progress monitor
	 */
	public void runInEditor(Object model, IResource resource, IProgressMonitor monitor) {
		if (model == null)
			return;
		processResource(resource, monitor, model, CheckerLaunchMode.RUN_AS_YOU_TYPE);
	}
}
