/*******************************************************************************
 * Copyright (c) 2010-2014 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.text.xml.contentassist;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext;
import org.eclipse.wst.sse.ui.internal.contentassist.CustomCompletionProposal;
import org.eclipse.wst.sse.ui.internal.contentassist.IRelevanceCompletionProposal;
import org.eclipse.wst.sse.ui.internal.util.Sorter;
import org.jboss.tools.common.text.ITextProposalProvider;
import org.jboss.tools.common.text.TextProposal;

/**
 * The utility helper class which purpose is to filter out and sort 
 * Content Assist proposals
 * 
 * @author Victor V. Rubezhny
 */
@SuppressWarnings("restriction")
public class ProposalSorter {
	
	/**
	 * Utility that helps to ProposalSorter object to filter out the proposals that aren't valid for the 
	 * given context
	 */
	public static interface IProposalFilter {
		boolean isValidProposal(CompletionProposalInvocationContext context, ICompletionProposal proposal);
	}
	
	/**
	 * Filters and sorts the Content Assist proposals, using the external filter
	 */
	public static List<ICompletionProposal> filterAndSortProposals(List<ICompletionProposal> proposals,
			IProgressMonitor monitor, CompletionProposalInvocationContext context, IProposalFilter filter) {
		if (proposals == null)
			return null;
		ICompletionProposal[] resultArray = (ICompletionProposal[])proposals.toArray(new ICompletionProposal[proposals.size()]);
		resultArray = makeUnique(resultArray);
		resultArray = filterProposals(resultArray, context, filter);
		Object[] sorted = createSorter().sort(resultArray);
		System.arraycopy(sorted, 0, resultArray, 0, sorted.length);
		return Arrays.asList(resultArray);
	}
	
	/**
	 * Filters and sorts the Content Assist proposals
	 */
	public static List<ICompletionProposal> filterAndSortProposals(List<ICompletionProposal> proposals,
			IProgressMonitor monitor, CompletionProposalInvocationContext context) {
		return filterAndSortProposals(proposals, monitor, context, null);
	}
	
	private static Sorter createSorter() {
		return new Sorter() {
			public boolean compare(Object proposal1, Object proposal2) {

				int pr1 = Integer.MIN_VALUE;
				int pr2 = Integer.MIN_VALUE;
				
				ICompletionProposal p1 = (ICompletionProposal)proposal1;
				ICompletionProposal p2 = (ICompletionProposal)proposal2;
				
				if (p1 instanceof IRelevanceCompletionProposal)
					pr1 = ((IRelevanceCompletionProposal)p1).getRelevance();

				if (p2 instanceof IRelevanceCompletionProposal)
					pr2 = ((IRelevanceCompletionProposal)p2).getRelevance();
				
				
				if (pr1 == pr2) {
					String str1 = (p1.getDisplayString() == null ? "" : p1.getDisplayString()); //$NON-NLS-1$
					String str2 = (p2.getDisplayString() == null ? "" : p2.getDisplayString()); //$NON-NLS-1$
					return str2.compareTo(str1) > 0;
				}

				return (pr1 > pr2);
			}
		};
	}

	/**
	 * Removes duplicates of completion strings
	 *
	 * @param suggestions a list of suggestions ({@link String}).
	 * @return a list of unique completion suggestions.
	 */
	public static ICompletionProposal[] makeUnique(ICompletionProposal[] proposals) {
		if (proposals == null)
			return null;
		
		Map <String, ICompletionProposal> existingProposals = new HashMap<String, ICompletionProposal>(proposals.length);
		ArrayList<ICompletionProposal> unique = new ArrayList<ICompletionProposal>(proposals.length);
		
		for (ICompletionProposal proposal : proposals) {
			if (proposal == null)
				continue;

			String replString = null;
			String dispString = null;

			if (proposal instanceof CustomCompletionProposal) {
				replString = ((CustomCompletionProposal) proposal)
						.getReplacementString();
			}
			dispString = unQuote(proposal.getDisplayString());
			replString = getReplacementWord(replString == null ? dispString
					: replString);
			
			boolean isFilterable = true;
			if (proposal instanceof ITextProposalProvider) { 
				TextProposal textProposal = ((ITextProposalProvider)proposal).getTextProposal();
				isFilterable = (textProposal == null || textProposal.isFilterable());
			}

			if (isFilterable) {
				ICompletionProposal existingProposal = existingProposals.get(replString);
				if (existingProposal == null) {
					existingProposals.put(replString, proposal);
					unique.add(proposal);
				} else {
					if (existingProposal instanceof IRelevanceCompletionProposal && proposal instanceof IRelevanceCompletionProposal) {
						if (((IRelevanceCompletionProposal)existingProposal).getRelevance() <
								((IRelevanceCompletionProposal)proposal).getRelevance()) {
							existingProposals.put(replString, proposal);
							unique.remove(existingProposal);
							unique.add(proposal);
						}
					}
				}
			} else {
				unique.add(proposal);
			}
		}
		return unique.toArray(new ICompletionProposal[unique.size()]);
	}

	public static ICompletionProposal[] filterProposals(ICompletionProposal[] proposals,
			CompletionProposalInvocationContext context, IProposalFilter filter) {
		if (filter == null)
			return proposals;

		ArrayList<ICompletionProposal> filtered = new ArrayList<ICompletionProposal>(proposals.length);
		for (ICompletionProposal proposal : proposals) {
			if (filter.isValidProposal(context, proposal)) {
				filtered.add(proposal);
			}
		}
		return filtered.toArray(new ICompletionProposal[filtered.size()]);
	}
	
	private static String getReplacementWord(String replacement) {
		replacement = (replacement == null ? "" : //$NON-NLS-1$
				replacement);
		int index = replacement.indexOf('>');
		if (index != -1) {
			replacement = replacement.substring(0, index).trim();
			if (replacement.endsWith("/")) //$NON-NLS-1$
				replacement = replacement
						.substring(0, replacement.length() - 1).trim();
		}
		index = replacement.indexOf("="); //$NON-NLS-1$
		if (index != -1) {
			replacement = replacement.substring(0, index).trim();
		}
		index = replacement.indexOf(" "); //$NON-NLS-1$
		if (index != -1) {
			replacement = replacement.substring(0, index).trim();
		}
		return replacement;
	}

	private static String unQuote(String str) {
		str = (str == null ?
				"" :  //$NON-NLS-1$
				str.toLowerCase());
		if (str.startsWith("\"")) //$NON-NLS-1$
			str = str.substring(1);
		if (str.endsWith("\"")) //$NON-NLS-1$
			str = str.substring(0, str.length() - 1);
		str = str.trim().toLowerCase();
		
		return str;
	}
}
