/*******************************************************************************
 * Copyright (c) 2013, 2014 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.html.ui.internal.text.correction;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension2;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.wst.html.core.internal.HTMLCoreMessages;
import org.eclipse.wst.html.core.internal.HTMLCorePlugin;
import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames;
import org.eclipse.wst.html.core.internal.validate.StringMatcher;
import org.eclipse.wst.html.ui.internal.HTMLUIMessages;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils;
import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;

public class HTMLAttributeValidationQuickFixProcessor implements IQuickAssistProcessor {

	private StringMatcher UNDEFINED_ATTRIBUTE_NAME_MATCHER;
	private IPreferencesService fPreferenceService;

	public HTMLAttributeValidationQuickFixProcessor() {
		UNDEFINED_ATTRIBUTE_NAME_MATCHER = new StringMatcher(NLS.bind(HTMLCoreMessages.Undefined_attribute_name___ERROR_, "*")); //$NON-NLS-1$
		fPreferenceService = Platform.getPreferencesService();
	}
	
	/*
	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#getErrorMessage()
	 */
	public String getErrorMessage() {
		return null;
	}

	/*
	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#canFix(org.eclipse.jface.text.source.Annotation)
	 */
	public boolean canFix(Annotation annotation) {
		boolean result = false;

		String text = null;
		if (annotation instanceof TemporaryAnnotation) {
			TemporaryAnnotation tempAnnotation = (TemporaryAnnotation) annotation;
			int problemID = tempAnnotation.getProblemID();
			text = tempAnnotation.getText();

			if (problemID == 0 && text != null)
				result = true;
		} else if (annotation instanceof MarkerAnnotation) {
			MarkerAnnotation markerAnnotation = (MarkerAnnotation) annotation;
			text = markerAnnotation.getText();
			IMarker marker = markerAnnotation.getMarker();
			IResource resource = marker == null ? null : marker.getResource();
			if (resource != null && resource.exists() && resource.isAccessible() && text != null) {
				result = true;
			}
		}
		
		result = (result && UNDEFINED_ATTRIBUTE_NAME_MATCHER.match(text));

		return result;
	}

	/*
	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#canAssist(org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext)
	 */
	public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
		return true;
	}

	/*
	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#computeQuickAssistProposals(org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext)
	 */
	public ICompletionProposal[] computeQuickAssistProposals(
			IQuickAssistInvocationContext invocationContext) {
		ISourceViewer viewer = invocationContext.getSourceViewer();
		int documentOffset = invocationContext.getOffset();
		int length = viewer != null ? viewer.getSelectedRange().y : 0;

		IAnnotationModel model = viewer.getAnnotationModel();
		if (model == null)
			return null;

		List proposals = new ArrayList();
		if (model instanceof IAnnotationModelExtension2) {
			Iterator iter = ((IAnnotationModelExtension2) model).getAnnotationIterator(documentOffset, length, true, true);
			while (iter.hasNext()) {
				Annotation anno = (Annotation) iter.next();
				if (canFix(anno)) {
					int offset = -1;
					
					if (anno instanceof TemporaryAnnotation) {
						offset = ((TemporaryAnnotation)anno).getPosition().getOffset();
					} else if (anno instanceof MarkerAnnotation) {
						offset = ((MarkerAnnotation)anno).getMarker().getAttribute(IMarker.CHAR_START, -1);
					}
					if (offset == -1)
						continue;

					IDOMNode node = (IDOMNode) ContentAssistUtils.getNodeAt(viewer, offset);
					if (!(node instanceof Element))
						continue;

					Object adapter = (node instanceof IAdaptable ? ((IAdaptable)node).getAdapter(IResource.class) : null);
					IProject project = (adapter instanceof IResource ? ((IResource)adapter).getProject() : null);

					IScopeContext[] fLookupOrder = new IScopeContext[] {new InstanceScope(), new DefaultScope()};
					if (project != null) {
						ProjectScope projectScope = new ProjectScope(project);
						if(projectScope.getNode(getPreferenceNodeQualifier())
								.getBoolean(getProjectSettingsKey(), false))
							fLookupOrder = new IScopeContext[] {projectScope, new InstanceScope(), new DefaultScope()};
					}
					
					boolean ignore = fPreferenceService.getBoolean(
							getPreferenceNodeQualifier(), HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, 
							HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT, fLookupOrder);

					String ignoreList = fPreferenceService.getString(
							getPreferenceNodeQualifier(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, 
							HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, fLookupOrder);

					Set result = new HashSet();
					if (ignoreList.trim().length() > 0) {
						String[] names = ignoreList.split(","); //$NON-NLS-1$
						for (int i = 0; names != null && i < names.length; i++) {
							String name = names[i] == null ? null : names[i].trim();
							if (name != null && name.length() > 0) 
								result.add(name.toLowerCase());
						}
					}
					
					String name = getAttributeName(node, offset);
					if (name == null) continue;
					
					// If ignore == false. then show a quick fix anyway (due to allow to turn 'ignore' option on)
					if (!ignore || shouldShowQuickFix(result, name.toLowerCase())) {
						IgnoreAttributeNameCompletionProposal p = new IgnoreAttributeNameCompletionProposal(
								name.toLowerCase(), offset, NLS.bind(HTMLUIMessages.DoNotValidateAttribute, name), 
								HTMLUIMessages.DoNotValidateAttributeAddInfo, node);
						if (!proposals.contains(p))
							proposals.add(p);  
					}
					
					int dashIndex = name.indexOf('-');
					while (dashIndex != -1) {
						StringBuffer namePattern = new StringBuffer(name.substring(0, dashIndex + 1)).append('*');
						
						// Do not continue creating proposals for the rest of patterns if 
						// a more common pattern is already created
						if (ignore && result.contains(namePattern.toString().toLowerCase()))
							break;
						
						IgnoreAttributeNameCompletionProposal p = new IgnoreAttributeNameCompletionProposal(
								namePattern.toString().toLowerCase(), offset, NLS.bind(HTMLUIMessages.DoNotValidateAllAttributes, namePattern.toString()), 
								HTMLUIMessages.DoNotValidateAllAttributesAddInfo, node); 
						if (!proposals.contains(p))
							proposals.add(p);  

						dashIndex = name.indexOf('-', dashIndex + 1);
					}
				}
			}
		}

		if (proposals.isEmpty())
			return null;

		return (ICompletionProposal[]) proposals.toArray(new ICompletionProposal[proposals.size()]);

	}

	private String getAttributeName(IDOMNode node, int offset) {
		IStructuredDocumentRegion startStructuredDocumentRegion = node == null ? null : node.getStartStructuredDocumentRegion();

		// Only a tag start can have attributes
		if ((startStructuredDocumentRegion != null) && startStructuredDocumentRegion.containsOffset(offset)) {
			NamedNodeMap attributes = node.getAttributes();
			if (attributes == null) return null;
			
			for (int i = 0; i < attributes.getLength(); i++) {
				IDOMAttr attr = (IDOMAttr)attributes.item(i);
				
				if (attr.getNameRegionStartOffset() <= offset && attr.getNameRegionEndOffset() >= offset) {
					return attr.getName();
				}
			}
		}
		
		return null;
	}
	
	private boolean shouldShowQuickFix(Set lcIgnoredPatterns, String attrName) {
		// Check the attribute name absence in ignore list
		String [] lcPatterns = (String[])lcIgnoredPatterns.toArray(new String[0]);
		for (int i = 0; i < lcPatterns.length; i++) {
			StringMatcher strMatcher = new StringMatcher(lcPatterns[i]);
			if (strMatcher.match(attrName.toLowerCase())) {
				return false; // The attribute name is already ignored, no need to show a quickfix
			}
		}

		// The attribute name is not ignored yet, need to show a quickfix
		return true;
	}
	
	private String getPreferenceNodeQualifier() {
		return HTMLCorePlugin.getDefault().getBundle().getSymbolicName();
	}

	private String getProjectSettingsKey() {
		return HTMLCorePreferenceNames.USE_PROJECT_SETTINGS;
	}
}
