/*******************************************************************************
 * Copyright (c) 2000, 2012 QNX Software Systems and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     QNX Software Systems - Initial API and implementation
 *     Warren Paul (Nokia)
 *     Markus Schorn (Wind River Systems)
 *     Anton Leherbauer (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.model;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.IBinaryParser.IBinaryFile;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModelUtil;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICElementDelta;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IOutputEntry;
import org.eclipse.cdt.core.model.IParent;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.core.runtime.jobs.Job;

public class BinaryRunner {

	class BinaryRunnerOperation extends CModelOperation {

		BinaryRunnerOperation(ICProject cproj) {
			super(cproj);
		}

		/* (non-Javadoc)
		 * @see org.eclipse.cdt.internal.core.model.CModelOperation#isReadOnly()
		 */
		@Override
		public boolean isReadOnly() {
			return true;
		}

		@Override
		protected void executeOperation() throws CModelException {
			ICProject cproj = (ICProject) getElementsToProcess()[0];
			IParent[] containers = new IParent[2];
			containers[0] = cproj.getBinaryContainer();
			containers[1] = cproj.getArchiveContainer();
			CModelManager factory = CModelManager.getDefault();
			ICElement root = factory.getCModel();
			CElementDelta cdelta = new CElementDelta(root);
			cdelta.changed(cproj, ICElementDelta.F_CONTENT);
			for (IParent container : containers) {
				if (fMonitor.isCanceled()) {
					return;
				}
				ICElement[] children = container.getChildren();
				if (children.length > 0) {
					cdelta.added((ICElement) container);
					for (ICElement element : children) {
						cdelta.added(element);
					}
				}
			}
			addDelta(cdelta);
		}

	}

	private final ICProject cproject;
	private final Job runnerJob; // final fields don't need synchronization
	private IOutputEntry[] entries = new IOutputEntry[0];
	private boolean isStopped = false; // access to isStopped must be synchronized.

	public BinaryRunner(IProject prj) {
		cproject = CModelManager.getDefault().create(prj);
		runnerJob = createRunnerJob();
		try {
			entries = cproject.getOutputEntries();
		} catch (CModelException e) {
		}
	}

	private Job createRunnerJob() {
		String taskName = CCorePlugin.getResourceString("CoreModel.BinaryRunner.Binary_Search_Thread"); //$NON-NLS-1$
		taskName += " (" + cproject.getElementName() + ")"; //$NON-NLS-1$//$NON-NLS-2$
		Job job = new Job(taskName) {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				IStatus status = Status.OK_STATUS;
				try {
					if (cproject == null || entries.length == 0 || monitor.isCanceled()) {
						status = Status.CANCEL_STATUS;
					} else {
						monitor.beginTask(getName(), IProgressMonitor.UNKNOWN);

						BinaryContainer vbin = (BinaryContainer) cproject.getBinaryContainer();
						ArchiveContainer vlib = (ArchiveContainer) cproject.getArchiveContainer();

						vlib.removeChildren();
						vbin.removeChildren();

						// traverse project, but only if at least one binary parser is configured
						BinaryParserConfig[] parsers = CModelManager.getDefault()
								.getBinaryParser(cproject.getProject());
						if (parsers.length > 0) {
							cproject.getProject().accept(new Visitor(monitor), IContainer.INCLUDE_PHANTOMS);
						}

						if (!monitor.isCanceled()) {
							CModelOperation op = new BinaryRunnerOperation(cproject);
							op.runOperation(monitor);
						}
					}
				} catch (CoreException e) {
					// Ignore the error and just cancel the binary thread
					//status = e.getStatus();
					status = Status.CANCEL_STATUS;
				} finally {
					monitor.done();
				}
				return status;
			}
		};
		job.setPriority(Job.LONG);
		return job;
	}

	public void start() {
		synchronized (runnerJob) {
			if (!isStopped) {
				runnerJob.schedule();
			}
		}
	}

	/**
	 * wrap the wait call and the interrupteException.
	 */
	public void waitIfRunning() {
		try {
			Job currentJob = Job.getJobManager().currentJob();
			if (!runnerJob.equals(currentJob)) {
				runnerJob.join();
			}
		} catch (InterruptedException | IllegalStateException e) {
			CCorePlugin.log(e);
		}
	}

	/**
	 * Cancels the binary runner
	 */
	public void stop() {
		synchronized (runnerJob) {
			isStopped = true; // make sure job is not scheduled afterwards
			runnerJob.cancel();
		}
	}

	private class Visitor implements IResourceProxyVisitor {
		private final IProgressMonitor vMonitor;
		private final IProject project;
		private final IContentType textContentType;

		public Visitor(IProgressMonitor monitor) {
			vMonitor = monitor;
			this.project = cproject.getProject();
			IContentTypeManager mgr = Platform.getContentTypeManager();
			textContentType = mgr.getContentType("org.eclipse.core.runtime.text"); //$NON-NLS-1$
		}

		@Override
		public boolean visit(IResourceProxy proxy) throws CoreException {
			if (vMonitor.isCanceled()) {
				return false;
			}
			vMonitor.worked(1);
			// Attempt to speed things up by rejecting up front
			// Things we know should not be Binary files.

			// check if it's a file resource
			// and bail out early
			if (proxy.getType() != IResource.FILE) {
				return true;
			}

			// check against known content types
			// if the file has an extension
			String name = proxy.getName();
			if (name.contains(".")) { //$NON-NLS-1$
				IContentType contentType = CCorePlugin.getContentType(project, name);
				if (contentType != null && textContentType != null) {
					if (contentType.isKindOf(textContentType)) {
						return true;
					} else if (textContentType.isAssociatedWith(name)) {
						return true;
					}
				}
			}

			// give a hint to the user of what we are doing
			vMonitor.subTask(name);

			// we have a candidate
			IPath path = proxy.requestFullPath();
			if (path != null) {
				for (IOutputEntry entrie : entries) {
					if (isOnOutputEntry(entrie, path)) {
						IFile file = (IFile) proxy.requestResource();
						CModelManager factory = CModelManager.getDefault();
						IBinaryFile bin = factory.createBinaryFile(file);
						if (bin != null) {
							// Create the file will add it to the {Archive,Binary}Container.
							factory.create(file, bin, cproject);
						}
						return true;
					}
				}
			}
			return true;
		}

		private boolean isOnOutputEntry(IOutputEntry entry, IPath path) {
			if (entry.getPath().isPrefixOf(path)
					&& !CoreModelUtil.isExcluded(path, entry.fullExclusionPatternChars())) {
				return true;
			}
			return false;
		}
	}
}
