/*******************************************************************************
 * 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.operations;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.wst.validation.internal.ResourceConstants;
import org.eclipse.wst.validation.internal.ResourceHandler;
import org.eclipse.wst.validation.internal.TaskListUtility;
import org.eclipse.wst.validation.internal.Tracing;
import org.eclipse.wst.validation.internal.ValidationRegistryReader;
import org.eclipse.wst.validation.internal.ValidatorMetaData;
import org.eclipse.wst.validation.internal.plugin.ValidationPlugin;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
import org.eclipse.wst.validation.internal.provisional.core.IValidator;
import org.eclipse.wst.validation.internal.provisional.core.IValidatorJob;

/**
 * IValidator instances will interact with an instance of this class, but should never cast that
 * IReporter instance to WorkbenchReporter.
 * <p>
 * This class handles interaction between the user and the IValidator in the eclipse workbench
 * environment.
 * <p>
 * This class must not be called outside of an IWorkspaceRunnable or IRunnableWithProgress. Many
 * resource deltas can be generated by the methods in this class.
 * <p>
 * Only the validation framework may instantiate or alter instances of this class.
 */

public class WorkbenchReporter implements IReporter {
	public static final String DEFAULT_LOCATION = ""; //$NON-NLS-1$
	public static final int NO_MESSAGE_LIMIT = -1;

	private IProject _project;
	private IProgressMonitor _monitor;

	public static void addMessage(IResource resource, Class messageOwner, IMessage message) {
		addMessage(resource, messageOwner, null, message);
	}
	
	public static void addMessage(IResource resource, Class messageOwner, ClassLoader cl, IMessage message) {
		addMessage(resource, messageOwner, cl, message, null, ""); //$NON-NLS-1$
	}
	
	public static void addMessage(IResource resource, Class clazz, IMessage message, 
		String targetObjectName, String location) {
		addMessage(resource, clazz, null, message, targetObjectName, location);
	}
	
	public static void addMessage(IResource resource, Class clazz, IMessage message, 
		String targetObjectName, String location,String markerId) {
		addMessage(resource, clazz, null, message, targetObjectName, location,markerId);
	}
	
	public static void addMessage(IResource resource, Class clazz, ClassLoader cl, IMessage message, 
		String targetObjectName, String location) {
		if ((clazz == null) || (message == null) || (resource == null))return;

		if (cl == null)cl = clazz.getClassLoader();

		addMessage(resource, getUniqueId(clazz), cl, message, targetObjectName, location);
	}
	
	public static void addMessage(IResource resource, Class clazz, ClassLoader cl, IMessage message, 
		String targetObjectName, String location, String markerId) {
		if ((clazz == null) || (message == null) || (resource == null))return;

		if (cl == null)cl = clazz.getClassLoader();

		addMessage(resource, getUniqueId(clazz), cl, message, targetObjectName, location, markerId);
	}

	public static void addMessage(IResource resource, String messageOwnerId, ClassLoader cl, IMessage message) {
		addMessage(resource, messageOwnerId, cl, message, null, ""); //$NON-NLS-1$
	}

	public static void addMessage(IResource resource, String messageOwnerId, ClassLoader cl, 
		IMessage message, String targetObjectName, String location) {
		int severity = message.getSeverity();
		try {
			TaskListUtility.addTask(messageOwnerId, resource, location, message.getId(), 
				message.getText(cl), severity,targetObjectName, message.getGroupName(), 
				message.getOffset(), message.getLength());
		} catch (CoreException e) {
			ValidationPlugin.getPlugin().handleException(e);
		}
	}
	
	public static void addMessage(IResource resource, String messageOwnerId, ClassLoader cl, 
		IMessage message, String targetObjectName, String location, String markerId) {
		int severity = message.getSeverity();
		try {
			TaskListUtility.addTask(messageOwnerId, resource, location, message.getId(), 
				message.getText(cl), severity,markerId,targetObjectName, message.getGroupName(), 
				message.getOffset(), message.getLength());
		} catch (CoreException e) {
			ValidationPlugin.getPlugin().handleException(e);
		}
	}
	
	public static void removeAllMessages(IResource resource, IValidator validator) {
		if (resource == null)return;

		ValidatorMetaData vmd = ValidationRegistryReader.getReader().getValidatorMetaData(validator);
		if (vmd == null) {
			// log
			return;
		}

		String[] validatorNames = vmd.getValidatorNames();
		try {
			TaskListUtility.removeAllTasks(resource, validatorNames);
		} catch (CoreException e) {
			ValidationPlugin.getPlugin().handleException(e);
		}
	}

	/**
	 * @deprecated Will be removed in Milestone 3. Should not be called outside of the validation
	 *             framework.
	 */
	public static void removeAllMessages(IResource resource, IValidator validator, Object object) {
		if (resource == null) {
			return;
		}

		ValidatorMetaData vmd = ValidationRegistryReader.getReader().getValidatorMetaData(validator);
		if (vmd == null) {
			// log
			return;
		}

		String[] validatorNames = vmd.getValidatorNames();
		try {
			//String targetObjectName = getTargetObjectName(vmd.getHelper(resource.getProject()), object);
			String targetObjectName = getTargetObjectName( getHelper( resource.getProject(), validator), object);			
			removeAllMessages(resource, validatorNames, targetObjectName);
		} catch (InstantiationException e) {
			// Remove the vmd from the reader's list
			ValidationRegistryReader.getReader().disableValidator(vmd);
			ValidationPlugin.getPlugin().handleException(e);
		}
	}

	public static void removeAllMessages(IResource resource, String[] validatorNames, String targetObjectName) {
		try {
			TaskListUtility.removeAllTasks(resource, validatorNames, targetObjectName);
		} catch (CoreException e) {
			ValidationPlugin.getPlugin().handleException(e);
		}
	}

	public static void removeMessageSubset(IResource resource, Class messageOwner, String groupName) {
		// Since the addMessage(Class, IMessage) is defaulted to the IProject,
		// remove the message subsets from the IProject
		removeMessageSubset(resource, new String[]{getUniqueId(messageOwner)}, null, groupName);
	}

	public static void removeMessageSubset(IResource resource, String messageOwnerId, String groupName) {
		// Since the addMessage(Class, IMessage) is defaulted to the IProject,
		// remove the message subsets from the IProject
		removeMessageSubset(resource, new String[]{messageOwnerId}, null, groupName);
	}

	public static void removeMessageSubset(IResource resource, String[] ownerId, String targetObjectName, String groupName) {
		try {
			TaskListUtility.removeTaskSubset(resource, ownerId, targetObjectName, groupName);
		} catch (CoreException e) {
			ValidationPlugin.getPlugin().handleException(e);
		}
	}

	/**
	 * Given a Class instance, return the id that uniquely identifies this instance. (Used as the
	 * value for the message owner.)
	 */
	public static String getUniqueId(Class clazz) {
		if (clazz == null) {
			return ""; //$NON-NLS-1$
		}

		// It is safe to load this String into the constants space because
		// the Class name will not change during WSAD's session.
		return clazz.getName().intern();
	}

	public static String getUniqueId(IValidator validator) {
		if (validator == null) {
			return ""; //$NON-NLS-1$
		}
		return getUniqueId(validator.getClass());
	}

	public static String getLocation(IWorkbenchContext helper, IMessage message) {
		if (message == null) {
			return getLocationText(helper, null);
		}
		int lineNo = message.getLineNumber();
		if (lineNo == IMessage.LINENO_UNSET) {
			return getLocationText(helper, message.getTargetObject());
		}
		return String.valueOf(lineNo);
	}

	public static String getLocationText(IWorkbenchContext helper, Object targetObject) {
		String location = null;
		try {
			location = helper.getLocation(targetObject);
		} catch (Exception e) {
			ValidationPlugin.getPlugin().handleException(e);
		}
		if ((location == null) || (location.trim().equals(""))) { //$NON-NLS-1$
			location = DEFAULT_LOCATION;
		}
		return location;
	}

	// TODO This method was made protected for the SaberReporter. Make this method private again
	// once the framework supports IMarker.PRIORITY.
	protected static String getTargetObjectName(IWorkbenchContext helper, IMessage message) {
		if (message == null) {
			return getTargetObjectName(helper, null);
		}
		return getTargetObjectName(helper, message.getTargetObject());
	}

	private static String getTargetObjectName(IWorkbenchContext helper, Object targetObject) {
		String targetObjectName = null;
		try {
			targetObjectName = helper.getTargetObjectName(targetObject);
		} catch (Exception e) {
			ValidationPlugin.getPlugin().handleException(e);
		}
		return targetObjectName;
	}

	// TODO This method was made "protected" for the SaberReporter. Do not call this method! This
	// method will be made private once support for marker PRIORITY attributes is in the framework.
	protected static ValidatorMetaData getVMD(IValidator validator) throws IllegalArgumentException {
		ValidatorMetaData vmd = ValidationRegistryReader.getReader().getValidatorMetaData(validator);
		if (vmd == null) {
			throw new IllegalArgumentException(); // already logged in the ValidationRegistryReader
		}
		return vmd;
	}

	/**
	 * @deprecated. The IValidationContext will be ignored.
	 */
	public WorkbenchReporter(IWorkbenchContext helper, IProgressMonitor monitor, IProject project) {
		this(project, monitor);
	}

	/**
	 * An IReporter instance must exist for each Runnable in order for the validator to report
	 * status messages. IProject and IProgressMonitor must not be null.
	 */
	public WorkbenchReporter(IProject project, IProgressMonitor monitor) {
		super();
		_project = project;
		_monitor = monitor;
	}

	// TODO This method was made protected for the SaberReporter. Make it private again once the
	// framework support IMarker.PRIORITY.
	protected IWorkbenchContext getHelper(IValidator validator) throws InstantiationException, IllegalArgumentException {
		ValidatorMetaData vmd = getVMD(validator);
		IWorkbenchContext helper = vmd.getHelper( getProject(), validator );
		return helper;
	}
	
	protected static IWorkbenchContext getHelper(IProject project, IValidator validator) throws InstantiationException, IllegalArgumentException {
		ValidatorMetaData vmd = getVMD(validator);
		IWorkbenchContext helper = vmd.getHelper( project, validator );
		return helper;
	}
	
	public IProject getProject() {
		return _project;
	}

	public IProgressMonitor getProgressMonitor() {
		return _monitor;
	}

	/**
	 * This method will never return null.
	 */
	private IResource getDefaultResource() {
		IResource prj = getProject();
		if (prj != null) {
			return prj;
		}
		// return the workspace root
		return ResourcesPlugin.getWorkspace().getRoot();
	}

	/**
	 * When an IMessage is created, the user has the option of creating it with a target object.
	 * That target object is used to identify which object has the problem identified by the
	 * IMessage. This method, given the target object, returns the IResource which represents that
	 * object in the workbench.
	 */
	public IResource getMessageResource(IValidator validator, Object object) {
		if (validator == null) {
			return null;
		}

		IResource resource = null;
		if (object != null) {
			if (object instanceof IResource) {
				resource = (IResource) object;
			} else if (object instanceof WorkbenchFileDelta) {
				// resource may be null if the WorkbenchFileDelta was constructed from an Object
				// instead of an IResource
				resource = ((WorkbenchFileDelta) object).getResource();
			}

			if (resource == null) {
				try {
					IWorkbenchContext helper = getHelper(validator);
					resource = helper.getResource(object);
				} catch (InstantiationException e) {
					try {
						// Unlikely that an exception will be thrown, because this method is
						// invoked by the validator, and if the validator is invoked, it's likely
						// that the helper has been loaded too.
						ValidatorMetaData vmd = getVMD(validator);

						// Remove the vmd from the reader's list
						ValidationRegistryReader.getReader().disableValidator(vmd);

						ValidationPlugin.getPlugin().handleException(e);
					} catch (IllegalArgumentException e2) {
						// Even the IValidator is invalid. Unfortunately, can't disable the
						// validator because it can't be found by the registry reader.
						// Log the reason for the disabled validator
						ValidationPlugin.getPlugin().handleException(e2);
					}
				} catch (IllegalArgumentException e) {
					// Even the IValidator is invalid. Unfortunately, can't disable the
					// validator because it can't be found by the registry reader.
					// Log the reason for the disabled validator
					ValidationPlugin.getPlugin().handleException(e);
				} catch (Exception e) {
					ValidationPlugin.getPlugin().handleException(e);
				}
			}
		}

		if (resource == null) {
			resource = getDefaultResource();
		}

		if (!resource.exists()) {
			resource = getDefaultResource();
		}

		return resource;
	}

	/**
	 * Show a message to the user indicating which subtask is currently being processed. <br>
	 * <br>
	 * <code>message</code> may not be null or the empty string (""). <br>
	 */
	public void displaySubtask(String message) {
		if ((message == null) || (message.equals(""))) { //$NON-NLS-1$
			return;
		}

		if (getProgressMonitor() != null) {
			getProgressMonitor().subTask(message);
		} else {
			System.out.println(message);
		}
	}

	/**
	 * @see org.eclipse.wst.validation.internal.provisional.core.core.IReporter#getMessages()
	 */
	public List getMessages() {
		return null;
	}

	/**
	 * @see org.eclipse.wst.validation.internal.provisional.core.core.IReporter#isCancelled()
	 */
	public boolean isCancelled() {
		if (getProgressMonitor() != null) {
			return getProgressMonitor().isCanceled();
		}
		return false;
	}

	/**
	 * @see org.eclipse.wst.validation.internal.provisional.core.core.IReporter#addMessage(IValidator, IMessage)
	 */
	public void addMessage(IValidator validator, IMessage message)  {
		IResource resource = getMessageResource(validator, message.getTargetObject());
		IWorkbenchContext helper = null;
		ValidatorMetaData vmd = getVMD(validator);
		try {
			helper = getHelper(validator);
		} catch (InstantiationException e) {
			try {
				// Unlikely that an exception will be thrown, because this method is
				// invoked by the validator, and if the validator is invoked, it's likely
				// that the helper has been loaded too
								
				// Remove the vmd from the reader's list
				ValidationRegistryReader.getReader().disableValidator(vmd);
				ValidationPlugin.getPlugin().handleException(e);
				return;
			} catch (IllegalArgumentException exc2) {
				logDisabledValidator(exc2);
				return;
			}
		} catch (IllegalArgumentException exc) {
			logDisabledValidator(exc);
			return;
		}

		if (resource == null) {
			if (Tracing.isLogging()) {
				String result = MessageFormat.format(ResourceHandler.getExternalizedMessage(
					ResourceConstants.VBF_EXC_INVALID_RESOURCE), 
					new Object[]{message.getText(), getTargetObjectName(helper, message)});
				Tracing.log("WorkbenchReporter-01: ", result); //$NON-NLS-1$
			}
			return;
		}

		if( validator instanceof IValidatorJob ){
			cacheMessage( (IValidatorJob)validator, resource, validator.getClass(), message, 
				getTargetObjectName(helper, message), getLocation(helper, message), getMarkerId(vmd, message) );
		}else
			addMessage(resource, validator.getClass(), message, getTargetObjectName(helper, message), 
				getLocation(helper, message),getMarkerId(vmd,message));
	}

	private String getMarkerId(ValidatorMetaData vmd, IMessage message) {
		if (vmd.getMarkerIds() != null) {
			if (vmd.getMarkerIds().length == 1)
				return vmd.getMarkerIds()[0];
			else if (vmd.getMarkerIds().length > 1) {
				String messageMarkerId = message.getMarkerId();
				if (messageMarkerId != null && messageMarkerId.length() > 0) {
					String firstMarkerId = vmd.getMarkerIds()[0];
					String pluginQualifier = firstMarkerId.substring(0, firstMarkerId.lastIndexOf(".")); //$NON-NLS-1$
					String qualifiedMessageMarkerId = null;
					if (messageMarkerId.indexOf(pluginQualifier) == -1)
						qualifiedMessageMarkerId = pluginQualifier + "." + messageMarkerId; //$NON-NLS-1$
					else
						qualifiedMessageMarkerId = messageMarkerId;
					if (Arrays.asList(vmd.getMarkerIds()).contains(qualifiedMessageMarkerId))
						return qualifiedMessageMarkerId;
				}
			}
		}
		return null;
	}

	private void logDisabledValidator(IllegalArgumentException e) {
		// Even the IValidator is invalid. Unfortunately, can't disable the
		// validator because it can't be found by the registry reader.
		// Log the reason for the disabled validator
		ValidationPlugin.getPlugin().handleException(e);
		return;
	}

	/**
	 * @see org.eclipse.wst.validation.internal.provisional.core.core.IReporter#displaySubtask(IValidator, IMessage)
	 */
	public void displaySubtask(IValidator validator, IMessage message) {
		if ((message == null) || (message.equals(""))) { //$NON-NLS-1$
			return;
		}

		displaySubtask(message.getText(validator.getClass().getClassLoader()));
	}

	/**
	 * @see org.eclipse.wst.validation.internal.provisional.core.core.IReporter#removeAllMessages(IValidator)
	 */
	public void removeAllMessages(IValidator validator) {
		if (validator == null) { // getHelper could be null if the user cancelled before something
			// was fully initialized
			return;
		}

		removeAllMessages(validator, null);
	}

	/**
	 * @see org.eclipse.wst.validation.internal.provisional.core.core.IReporter#removeAllMessages(IValidator, Object)
	 */
	public void removeAllMessages(IValidator validator, Object object) {
		IResource resource = getMessageResource(validator, object);
		if (resource == null) {
			return;
		}

		ValidatorMetaData vmd = ValidationRegistryReader.getReader().getValidatorMetaData(validator);
		if (vmd == null) {
			return;
		}

		IWorkbenchContext helper = null;
		try {
			//helper = vmd.getHelper(resource.getProject());
			helper = getHelper(validator);
		} catch (InstantiationException e) {
			// Unlikely that an exception will be thrown, because this method is
			// invoked by the validator, and if the validator is invoked, it's likely
			// that the helper has been loaded too.

			// Remove the vmd from the reader's list
			ValidationRegistryReader.getReader().disableValidator(vmd);
			ValidationPlugin.getPlugin().handleException(e);

			return;
		}

		String[] validatorNames = vmd.getValidatorNames();
		String targetObjectName = getTargetObjectName(helper, object);
		removeAllMessages(resource, validatorNames, targetObjectName);
	}

	/**
	 * @see org.eclipse.wst.validation.internal.provisional.core.core.IReporter#removeMessageSubset(IValidator, Object, String)
	 */
	public void removeMessageSubset(IValidator validator, Object obj, String groupName) {
		IResource resource = getMessageResource(validator, obj);
		if (resource == null) {
			return;
		}

		ValidatorMetaData vmd = ValidationRegistryReader.getReader().getValidatorMetaData(validator);
		if (vmd == null) {
			return;
		}


		IWorkbenchContext helper = null;
		try {
			//helper = vmd.getHelper(resource.getProject());
			helper = getHelper( resource.getProject(), validator );
		} 
		catch (InstantiationException e) {
			// Unlikely that an exception will be thrown, because this method is
			// invoked by the validator, and if the validator is invoked, it's likely
			// that the helper has been loaded too.

			// Remove the vmd from the reader's list
			ValidationRegistryReader.getReader().disableValidator(vmd);
			ValidationPlugin.getPlugin().handleException(e);

			return;
		}

		String[] validatorNames = vmd.getValidatorNames();
		String targetObjectName = getTargetObjectName(helper, obj);
		removeMessageSubset(resource, validatorNames, targetObjectName, groupName);
	}
	
	private static void cacheMessage( IValidatorJob validator,
		IResource resource,	Class clazz, IMessage message,
		String targetObjectName, String location, String markerId ){
		
		if ((clazz == null) || (message == null) || (resource == null))return;
		
		ClassLoader cl = clazz.getClassLoader();
		String text = message.getText(cl);
		MessageInfo msgInfo = new MessageInfo(getUniqueId(clazz), resource, location,
				text, targetObjectName, markerId, message );

		ValidatorManager.getManager().cacheMessage( validator, msgInfo );
	}
}
