/*******************************************************************************
 * Copyright (c) 2007, 2016 Google, Inc 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:
 * 	   Sergey Prigogin (Google) - initial API and implementation
 *     Markus Schorn (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.pdom.indexer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CCorePreferenceConstants;
import org.eclipse.cdt.core.dom.ast.IASTComment;
import org.eclipse.cdt.core.index.IIndexFileLocation;
import org.eclipse.cdt.core.index.IndexLocationFactory;
import org.eclipse.cdt.core.model.ICModelMarker;
import org.eclipse.cdt.internal.core.pdom.ITodoTaskUpdater;
import org.eclipse.cdt.internal.core.pdom.indexer.TodoTaskParser.Task;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
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.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.osgi.util.NLS;

public class TodoTaskUpdater implements ITodoTaskUpdater {
	private static final String SOURCE_ID = "CDT"; //$NON-NLS-1$
	private static final String[] TASK_MARKER_ATTRIBUTE_NAMES = { IMarker.MESSAGE, IMarker.PRIORITY, IMarker.CHAR_START,
			IMarker.CHAR_END, IMarker.LINE_NUMBER, IMarker.USER_EDITABLE, IMarker.SOURCE_ID, };

	private final TodoTaskParser taskParser;

	public TodoTaskUpdater() {
		String value = CCorePlugin.getOption(CCorePreferenceConstants.TODO_TASK_TAGS);
		if (value == null) {
			value = CCorePreferenceConstants.DEFAULT_TASK_TAGS;
		}
		String[] tags = split(value, ","); //$NON-NLS-1$
		char[][] taskTags = new char[tags.length][];
		for (int i = 0; i < tags.length; i++) {
			taskTags[i] = tags[i].toCharArray();
		}

		value = CCorePlugin.getOption(CCorePreferenceConstants.TODO_TASK_PRIORITIES);
		if (value == null) {
			value = CCorePreferenceConstants.DEFAULT_TASK_PRIORITY;
		}
		String[] priorities = split(value, ","); //$NON-NLS-1$
		int[] taskPriorities = new int[taskTags.length];
		for (int i = 0; i < taskPriorities.length; i++) {
			String priority = i < priorities.length ? priorities[i] : CCorePreferenceConstants.DEFAULT_TASK_PRIORITY;
			taskPriorities[i] = CCorePreferenceConstants.TASK_PRIORITY_HIGH.equals(priority) ? IMarker.PRIORITY_HIGH
					: CCorePreferenceConstants.TASK_PRIORITY_LOW.equals(priority) ? IMarker.PRIORITY_LOW
							: IMarker.PRIORITY_NORMAL;
		}

		value = CCorePlugin.getOption(CCorePreferenceConstants.TODO_TASK_CASE_SENSITIVE);
		if (value == null) {
			value = CCorePreferenceConstants.DEFAULT_TASK_CASE_SENSITIVE;
		}
		boolean isTaskCaseSensitive = Boolean.valueOf(value).booleanValue();
		taskParser = new TodoTaskParser(taskTags, taskPriorities, isTaskCaseSensitive);
	}

	@Override
	public void updateTasks(IASTComment[] comments, IIndexFileLocation[] filesToUpdate) {
		class TaskList {
			IFile fFile;
			List<Task> fTasks;

			public TaskList(IFile file) {
				fFile = file;
			}

			public void add(Task task) {
				if (fTasks == null) {
					fTasks = new ArrayList<>();
				}
				fTasks.add(task);
			}
		}

		final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();

		// First collect all valid file-locations.
		final Map<IPath, TaskList> pathToTaskList = new HashMap<>();
		final Set<IProject> projects = new HashSet<>();
		for (final IIndexFileLocation indexFileLocation : filesToUpdate) {
			final String filepath = indexFileLocation.getFullPath();
			if (filepath != null) {
				IFile file = workspaceRoot.getFile(new Path(filepath));
				if (file != null && file.exists()) {
					projects.add(file.getProject());
					pathToTaskList.put(IndexLocationFactory.getAbsolutePath(indexFileLocation), new TaskList(file));
				}
			}
		}

		if (comments.length > 0) {
			final Task[] tasks = taskParser.parse(comments);
			for (final Task task : tasks) {
				TaskList list = pathToTaskList.get(new Path(task.getFileLocation()));
				if (list != null) {
					list.add(task);
				}
			}
		}

		// Update markers in a job in order not to block the indexer (bug 210730).
		if (!pathToTaskList.isEmpty()) {
			WorkspaceJob job = new WorkspaceJob(Messages.TodoTaskUpdater_UpdateJob) {
				@Override
				public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
					MultiStatus status = new MultiStatus(CCorePlugin.PLUGIN_ID, 0, Messages.TodoTaskUpdater_UpdateJob,
							null);

					for (TaskList tasklist : pathToTaskList.values()) {
						final IFile file = tasklist.fFile;
						try {
							if (file.exists()) {
								file.deleteMarkers(ICModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
								final List<Task> tasks = tasklist.fTasks;
								if (tasks != null) {
									for (Task task : tasks) {
										applyTask(task, file);
									}
								}
							}
						} catch (CoreException e) {
							if (file.exists()) {
								status.add(e.getStatus());
							}
						}
					}
					return status;
				}
			};
			job.setRule(new MultiRule(projects.toArray(new IProject[projects.size()])));
			job.setSystem(true);
			job.schedule();
		}
	}

	private void applyTask(Task task, IResource resource) throws CoreException {
		IMarker marker = resource.createMarker(ICModelMarker.TASK_MARKER);
		String description = NLS.bind(Messages.TodoTaskUpdater_taskFormat, task.getTag(), task.getMessage());
		marker.setAttributes(TASK_MARKER_ATTRIBUTE_NAMES,
				new Object[] { description, Integer.valueOf(task.getPriority()), Integer.valueOf(task.getStart()),
						Integer.valueOf(task.getEnd()), Integer.valueOf(task.getLineNumber()), Boolean.FALSE,
						SOURCE_ID });
	}

	private String[] split(String value, String delimiters) {
		StringTokenizer tokenizer = new StringTokenizer(value, delimiters);
		int size = tokenizer.countTokens();
		String[] tokens = new String[size];
		for (int i = 0; i < size; i++)
			tokens[i] = tokenizer.nextToken();
		return tokens;
	}

	public static void removeTasksFor(final IResource resource) {
		if (resource == null || !resource.exists()) {
			return;
		}

		// Delete markers in a job in order not to block the indexer (bug 210730).
		WorkspaceJob job = new WorkspaceJob(Messages.TodoTaskUpdater_DeleteJob) {
			@Override
			public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
				try {
					if (!resource.exists()) {
						return Status.CANCEL_STATUS;
					}
					resource.deleteMarkers(ICModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
				} catch (CoreException e) {
					if (resource.exists()) {
						return e.getStatus();
					}
				}
				return Status.OK_STATUS;
			}
		};
		job.setRule(resource);
		job.setSystem(true);
		job.schedule();
	}
}
