/*******************************************************************************
 * Copyright (c) 2001, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 * IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.validation.internal;

import java.util.Map;

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.runtime.CoreException;
import org.eclipse.wst.validation.internal.plugin.ValidationPlugin;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;

/**
 * This class must be called only by the validation framework.
 * 
 * This singleton interacts with the eclipse workbench's Task list. TaskListUtility adds and removes
 * tasks from the list.
 * 
 * This class must not be called outside of an IWorkspaceRunnable or IRunnableWithProgress. Many
 * resource deltas can be generated by the methods in this class.
 */
public class TaskListUtility implements ConfigurationConstants {
	protected static final int DEPTH_INFINITE = IResource.DEPTH_INFINITE;
	protected static final int DEPTH_ZERO = IResource.DEPTH_ZERO;
	protected static final String VALIDATION_MARKER_TARGETOBJECT = "targetObject"; //$NON-NLS-1$
	private final static IMarker[] NO_MARKERS = new IMarker[0];

	public static IWorkspaceRoot getRoot() {
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		return root;
	}

	/**
	 * This method is here for use by the SABER validator's reporter instance ONLY. Do not use. See
	 * defect 260144 for details.
	 */
	@SuppressWarnings("unchecked")
	public static IMarker setPriority(IMarker item, int priority) throws CoreException {
		Map attrib = item.getAttributes();
		attrib.put(IMarker.PRIORITY, new Integer(priority));
		item.setAttributes(attrib);
		return item;
	}
	
	/**
	 * This method adds a message to a resource in the task list.
	 */
	public static IMarker addTask(String pluginId, IResource resource, String location, 
		String messageId, String message, int markerType, String markerName, String targetObjectName, 
		String groupName, int offset, int length) throws CoreException {
		
		if ((message == null) || (resource == null) || (!resource.exists())) {
			return null;
		}

		int severity = getSeverity(markerType);

		// Allow duplicate entries in the task list.
		// Prior to a full validation, the validation framework will remove all messages owned
		// by a validator before it is executed.
		// Prior to an incremental validation, the validation framework will remove all messages,
		// on each of the changed resources, owned by a validator before it is invoked.
		// 
		// It is up to the validator to make sure that it is not adding the same message
		// in more than one place, and also to clear out any old messages which are not cleared
		// by the validation framework.
		IMarker item = null;
		MarkerManager.getDefault().hook(resource);
		if(markerName != null && markerName.length() >0 )
			 item = resource.createMarker(markerName); // add a validation marker
		else
		     item = resource.createMarker(VALIDATION_MARKER); // add a validation marker

		// For performance reasons, replace the multiple setAttribute
		// calls above with a single setAttributes call.
		boolean offsetSet = ((offset != IMessage.OFFSET_UNSET) && (length != IMessage.OFFSET_UNSET));
		int size = (offsetSet) ? 10 : 8; // add CHAR_START, CHAR_END only if the offset is set. If
		// the offset is set, it takes precendence over the line
		// number. (eclipse's rule, not mine.)
		String[] attribNames = new String[size];
		Object[] attribValues = new Object[size];

		// Very first thing, add the owner. That way, if the code dies
		// before things are persisted, hopefully this marker will be persisted.
		// Hopefully, eclipse WILL persist this field, as requested.
		attribNames[0] = VALIDATION_MARKER_OWNER;
		attribValues[0] = pluginId;
		attribNames[1] = VALIDATION_MARKER_SEVERITY; // this validation severity is stored, in
		// addition to the marker severity, to enable
		// more than one severity of message to be
		// displayed. e.g. ERROR | WARNING (using
		// binary OR). The IMarker constants are
		// regular decimal constants.
		attribValues[1] = new Integer(markerType);
		attribNames[2] = VALIDATION_MARKER_TARGETOBJECT; // to distinguish between messages which
		// are registered on an IResource, but
		// against different target objects
		attribValues[2] = ((targetObjectName == null) ? "" : targetObjectName); //$NON-NLS-1$
		attribNames[3] = VALIDATION_MARKER_GROUP;
		attribValues[3] = ((groupName == null) ? "" : groupName); //$NON-NLS-1$
		attribNames[4] = IMarker.MESSAGE;
		attribValues[4] = message;
		attribNames[5] = VALIDATION_MARKER_MESSAGEID;
		attribValues[5] = messageId;

		attribNames[6] = IMarker.SEVERITY; // IMarker.SEVERITY_ERROR, IMarker.SEVERITY_WARNING,
		// IMarker.SEVERITY_INFO
		attribValues[6] = new Integer(severity);
		try {
			// If the location is a line number, store it as a line number
			Integer lineNumber = Integer.valueOf(location);
			attribNames[7] = IMarker.LINE_NUMBER;
			attribValues[7] = lineNumber;
		} catch (NumberFormatException exc) {
			// Otherwise, store it as a text location
			attribNames[7] = IMarker.LOCATION;
			attribValues[7] = location;
		}

		if (offsetSet) {
			attribNames[8] = IMarker.CHAR_START;
			attribValues[8] = new Integer(offset);
			attribNames[9] = IMarker.CHAR_END;
			attribValues[9] = new Integer(offset + length);
		}

		item.setAttributes(attribNames, attribValues);

		return item;
	}

	/**
	 * This method adds a message to a resource in the task list.
	 */
	public static IMarker addTask(String pluginId, IResource resource, String location, 
		String messageId, String message, int markerType, String targetObjectName, 
		String groupName, int offset, int length) throws CoreException {
		
		return addTask(pluginId, resource, location, messageId, 
				message, markerType, null, targetObjectName, groupName, offset, length);
	}

	/**
	 * Given one of the SeverityEnum severities, return the IMarker severity int that is its
	 * equivalent.
	 * 
	 * This method was made public for the SaberReporter. No one other than TaskListUtility, or the
	 * SaberReporter, should use this method!
	 *  
	 */
	private static int getSeverity(int severityEnumValue) {
		switch (severityEnumValue) {
			case (IMessage.HIGH_SEVERITY) : {
				return IMarker.SEVERITY_ERROR;
			}

			case (IMessage.LOW_SEVERITY) : {
				return IMarker.SEVERITY_INFO;
			}

			case (IMessage.NORMAL_SEVERITY) : {
				return IMarker.SEVERITY_WARNING;
			}

			case (IMessage.ALL_MESSAGES) :
			case (IMessage.ERROR_AND_WARNING) :
			default : {
				// assume it's a warning.
				return IMarker.SEVERITY_WARNING;
			}
		}
	}

	private static int getDepth(IResource resource) {
		if (resource instanceof IProject) {
			return DEPTH_INFINITE; // DEPTH_INFINITE means get this project's markers, and the
			// markers belonging to the project's children.
		} else if (resource instanceof IWorkspaceRoot) {
			// Needed for the ValidationMigrator when it checks for orphan tasks.
			return DEPTH_INFINITE; // DEPTH_INFINITE means get all of the markers in the workspace
		}

		return DEPTH_ZERO; // DEPTH_ZERO means just this resource, not its children
	}

	public static IMarker[] getValidationTasks(int severity, IProject project) {
		// DEPTH_INFINITE means get this project's markers, and the markers
		// belonging to the project's children.
		return getValidationTasks(project, severity);
	}

	public static IMarker[] getValidationTasks(IResource resource, int severity) {
		return getValidationTasks(resource, severity, getDepth(resource));
	}

	/**
	 * Return true if the marker is owned by the ownerId.
	 */
	public static boolean isOwner(IMarker marker, String ownerId) {
		try {
			Object owner = marker.getAttribute(VALIDATION_MARKER_OWNER);
			if ((owner == null) || !(owner instanceof String)) {
				// The ValidationMigrator will remove any "unowned" validation markers.
				return false;
			}

			return ((String) owner).equals(ownerId);
		} catch (CoreException e) {
			ValidationPlugin.getPlugin().handleException(e);
			return false;
		}
	}

	private static IMarker[] getValidationTasks(IResource resource, int severity, int depth) {
		IMarker[] tempMarkers = null;
		int validCount = 0;
		IMarker[] allMarkers = null;
		try {
			allMarkers = resource.findMarkers(VALIDATION_MARKER, true, depth);
		} catch (CoreException e) {
			if (Tracing.isLogging())ValidationPlugin.getPlugin().handleException(e);
			return NO_MARKERS;
		}

		// Now filter in the markers, based on severity type.
		if (allMarkers.length != 0) {
			tempMarkers = new IMarker[allMarkers.length];
			for (IMarker marker : allMarkers) {
				Integer filterSeverity = null;
				try {
					filterSeverity = (Integer) marker.getAttribute(VALIDATION_MARKER_SEVERITY);
				}
				catch (CoreException e){
					// Someone may have deleted the marker on us. All we can do is skip it.
					continue;
				}
				if (filterSeverity == null) {
					// odd...marker wasn't created correctly. How could this happen?
					// Default to the current severity and add it to the list.
					try {
						// 226541 - I was seeing markers with valid severities being reset, so I added this
						// additional test.
						if (marker.getAttribute(IMarker.SEVERITY, -1) == -1)
							marker.setAttribute(IMarker.SEVERITY, getSeverity(severity));
					} catch (Exception e) {
						ValidationPlugin.getPlugin().handleException(e);
						continue;
					}
				} else if ((severity & filterSeverity.intValue()) == 0) {
					continue;
				}
				tempMarkers[validCount++] = marker;
			}
		}

		if (validCount == 0) {
			return NO_MARKERS;
		}

		IMarker[] validMarkers = new IMarker[validCount];
		System.arraycopy(tempMarkers, 0, validMarkers, 0, validCount);
		return validMarkers;
	}

	public static IMarker[] getValidationTasks(IResource resource, String messageOwner) {
		return getValidationTasks(resource, new String[]{messageOwner}, getDepth(resource));
	}

	public static IMarker[] getValidationTasks(IResource resource, String[] messageOwners) {
		return getValidationTasks(resource, messageOwners, getDepth(resource));
	}

	private static IMarker[] getValidationTasks(IResource resource, String[] messageOwners, int depth) {
		IMarker[] markers = getValidationTasks(resource, IMessage.ALL_MESSAGES, depth);
		if (markers.length == 0)return NO_MARKERS;

		IMarker[] temp = new IMarker[markers.length];
		int validCount = 0;
		for (IMarker marker : markers) {
			Object owner = null;
			try {
				owner = marker.getAttribute(VALIDATION_MARKER_OWNER);
			} catch (CoreException e) {
				// eat it -- if it no longer exists there is nothing we can do about it
			}
			
			if ((owner == null) || !(owner instanceof String)) {
				// The ValidationMigrator will remove any "unowned" validation markers.
				continue;
			}

			for (String messageOwner : messageOwners) {
				if (((String) owner).equals(messageOwner)) {
					temp[validCount++] = marker;
					break;
				}
			}
		}
		
		if (validCount == 0)return NO_MARKERS;

		IMarker[] result = new IMarker[validCount];
		System.arraycopy(temp, 0, result, 0, validCount);
		return result;
	}

	/**
	 * This method retrieves all validation tasks from the resource. If depth is INFINITE, child
	 * tasks are returned as well. Only the tasks which are owned by the specified messageOwner, and
	 * apply to the named IMessage's target object (objectName) will be returned.
	 */
	private static IMarker[] getValidationTasks(IResource resource, String[] messageOwner, String objectName, String groupName, int depth) throws CoreException {
		if ((messageOwner == null) || (resource == null)) {
			return NO_MARKERS;
		}

		int validCount = 0;
		IMarker[] validList = null;
		IMarker[] markers = getValidationTasks(resource, messageOwner, depth);
		if (markers != null) {
			validList = new IMarker[markers.length];
			for (int i = 0; i < markers.length; i++) {
				IMarker marker = markers[i];

				// If more than one target object resolves to the same resource, removing one
				// target's
				// messages should not remove the other target object's messages.
				if (objectName != null) {
					Object targetObject = marker.getAttribute(VALIDATION_MARKER_TARGETOBJECT);
					if ((targetObject == null) || !(targetObject instanceof String) || !(((String) targetObject).equals(objectName))) {
						continue;
					}
				}

				if (groupName != null) {
					Object group = marker.getAttribute(VALIDATION_MARKER_GROUP);
					if ((group == null) || !(group instanceof String) || !(((String) group).equals(groupName))) {
						continue;
					}
				}

				validList[validCount++] = marker;
			}
		}

		if (validCount == 0) {
			return NO_MARKERS;
		}

		IMarker[] result = new IMarker[validCount];
		System.arraycopy(validList, 0, result, 0, validCount);
		return result;
	}

	/**
	 * Remove all validation messages from the resource and its children.
	 */
	public static void removeAllTasks(IResource resource) {
		if (resource == null) {
			return;
		}

		try {
			IMarker[] markers = getValidationTasks(resource, IMessage.ALL_MESSAGES);
			ResourcesPlugin.getWorkspace().deleteMarkers(markers);
		} catch (CoreException e) {
			ValidationPlugin.getPlugin().handleException(e);
		}
	}

	/**
	 * This method removes all tasks from the resource. If the resource is an IProject, all tasks
	 * are also removed from the project's children.
	 */
	public static void removeAllTasks(IResource resource, String[] owners) throws CoreException {
		removeAllTasks(resource, owners, null); // null means remove messages from all target
		// objects
	}

	/**
	 * This method removes all messages from a resource in the task list.
	 */
	public static void removeAllTasks(IResource resource, String owner, String objectName) throws CoreException {
		removeAllTasks(resource, new String[]{owner}, objectName);
	}

	public static void removeAllTasks(IResource resource, String[] owners, String objectName) throws CoreException {
		removeAllTasks(resource, owners, objectName, getDepth(resource));
	}

	protected static void removeAllTasks(IResource resource, String[] owners, String objectName, int depth) throws CoreException {
		removeTaskSubset(resource, owners, objectName, null, depth); // null means no group name
	}

	/**
	 * This method removes a subset of tasks from the project, including child tasks. Every task
	 * which belongs to the group, identified by groupName, will be removed.
	 */
	public static void removeTaskSubset(IResource resource, String[] owners, String objectName, String groupName) throws CoreException {
		removeTaskSubset(resource, owners, objectName, groupName, getDepth(resource));
	}

	/**
	 * This method removes a subset of tasks from the project, including child tasks. Every task
	 * which belongs to the group, identified by groupName, will be removed.
	 */
	protected static void removeTaskSubset(IResource resource, String[] owners, String objectName, String groupName, int depth) throws CoreException {
		if ((owners == null) || (resource == null)) {
			return;
		}

		IMarker[] allTasks = getValidationTasks(resource, owners, objectName, groupName, depth);
		if (allTasks.length > 0) {
			ResourcesPlugin.getWorkspace().deleteMarkers(allTasks);
		}
	}

	/**
	 * This method changes all validator markers which are owned by "from" to make their owner "to".
	 */
	public static void updateOwner(String from, String to) throws CoreException {
		updateOwner(from, to, getRoot());
	}

	/**
	 * This method changes all validator markers on the IResource and its children. All markers
	 * owned by "from" have their owner reassigned to "to".
	 */
	public static void updateOwner(String from, String to, IResource resource) throws CoreException {
		IMarker[] ownedMarkers = getValidationTasks(resource, from);
		if (ownedMarkers == null) {
			return;
		}

		for (int i = 0; i < ownedMarkers.length; i++) {
			IMarker marker = ownedMarkers[i];
			marker.setAttribute(VALIDATION_MARKER_OWNER, to);
		}
	}
}
