/*******************************************************************************
 * Copyright (c) 2000, 2011 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.jdt.internal.ui.search;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.StringTokenizer;

import org.eclipse.search.ui.text.Match;
import org.eclipse.search.ui.text.MatchFilter;

import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;

import org.eclipse.jdt.internal.corext.util.JdtFlags;

import org.eclipse.jdt.ui.search.ElementQuerySpecification;
import org.eclipse.jdt.ui.search.PatternQuerySpecification;
import org.eclipse.jdt.ui.search.QuerySpecification;

import org.eclipse.jdt.internal.ui.JavaPlugin;

abstract class JavaMatchFilter extends MatchFilter {

	public abstract boolean filters(JavaElementMatch match);

	/**
	 * Returns whether this filter is applicable for this query.
	 * 
	 * @param query the query
	 * @return <code>true</code> if this match filter is applicable for the given query
	 */
	public abstract boolean isApplicable(JavaSearchQuery query);

	/* (non-Javadoc)
	 * @see org.eclipse.search.ui.text.MatchFilter#filters(org.eclipse.search.ui.text.Match)
	 */
	@Override
	public boolean filters(Match match) {
		if (match instanceof JavaElementMatch) {
			return filters((JavaElementMatch) match);
		}
		return false;
	}

	private static final String SETTINGS_LAST_USED_FILTERS= "filters_last_used";  //$NON-NLS-1$

	public static MatchFilter[] getLastUsedFilters() {
		String string= JavaPlugin.getDefault().getDialogSettings().get(SETTINGS_LAST_USED_FILTERS);
		if (string != null) {
			return decodeFiltersString(string);
		}
		return getDefaultFilters();
	}

	public static void setLastUsedFilters(MatchFilter[] filters) {
		String encoded= encodeFilters(filters);
		JavaPlugin.getDefault().getDialogSettings().put(SETTINGS_LAST_USED_FILTERS, encoded);
	}

	public static MatchFilter[] getDefaultFilters() {
		return new MatchFilter[] { IMPORT_FILTER };
	}

	private static String encodeFilters(MatchFilter[] enabledFilters) {
		StringBuffer buf= new StringBuffer();
		for (int i= 0; i < enabledFilters.length; i++) {
			MatchFilter matchFilter= enabledFilters[i];
			buf.append(matchFilter.getID());
			buf.append(';');
		}
		return buf.toString();
	}

	private static JavaMatchFilter[] decodeFiltersString(String encodedString) {
		StringTokenizer tokenizer= new StringTokenizer(encodedString, String.valueOf(';'));
		HashSet<JavaMatchFilter> result= new HashSet<JavaMatchFilter>();
		while (tokenizer.hasMoreTokens()) {
			JavaMatchFilter curr= findMatchFilter(tokenizer.nextToken());
			if (curr != null) {
				result.add(curr);
			}
		}
		return result.toArray(new JavaMatchFilter[result.size()]);
	}

	private static final JavaMatchFilter POTENTIAL_FILTER= new PotentialFilter();
	private static final JavaMatchFilter IMPORT_FILTER= new ImportFilter();
	private static final JavaMatchFilter JAVADOC_FILTER= new JavadocFilter();
	private static final JavaMatchFilter READ_FILTER= new ReadFilter();
	private static final JavaMatchFilter WRITE_FILTER= new WriteFilter();

	private static final JavaMatchFilter POLYMORPHIC_FILTER= new PolymorphicFilter();
	private static final JavaMatchFilter INEXACT_FILTER= new InexactMatchFilter();
	private static final JavaMatchFilter ERASURE_FILTER= new ErasureMatchFilter();

	private static final JavaMatchFilter NON_PUBLIC_FILTER= new NonPublicFilter();
	private static final JavaMatchFilter STATIC_FILTER= new StaticFilter();
	private static final JavaMatchFilter NON_STATIC_FILTER= new NonStaticFilter();
	private static final JavaMatchFilter DEPRECATED_FILTER= new DeprecatedFilter();
	private static final JavaMatchFilter NON_DEPRECATED_FILTER= new NonDeprecatedFilter();

	private static final JavaMatchFilter[] ALL_FILTERS= new JavaMatchFilter[] {
			POTENTIAL_FILTER,
			IMPORT_FILTER,
			JAVADOC_FILTER,
			READ_FILTER,
			WRITE_FILTER,

            POLYMORPHIC_FILTER,
			INEXACT_FILTER,
			ERASURE_FILTER,

			NON_PUBLIC_FILTER,
			STATIC_FILTER,
			NON_STATIC_FILTER,
			DEPRECATED_FILTER,
			NON_DEPRECATED_FILTER
	};

	public static JavaMatchFilter[] allFilters() {
		return ALL_FILTERS;
	}

	public static JavaMatchFilter[] allFilters(JavaSearchQuery query) {
		ArrayList<JavaMatchFilter> res= new ArrayList<JavaMatchFilter>();
		for (int i= 0; i < ALL_FILTERS.length; i++) {
			JavaMatchFilter curr= ALL_FILTERS[i];
			if (curr.isApplicable(query)) {
				res.add(curr);
			}
		}
		return res.toArray(new JavaMatchFilter[res.size()]);
	}

	private static JavaMatchFilter findMatchFilter(String id) {
		for (int i= 0; i < ALL_FILTERS.length; i++) {
			JavaMatchFilter matchFilter= ALL_FILTERS[i];
			if (matchFilter.getID().equals(id))
				return matchFilter;
		}
		return null;
	}
}

class PotentialFilter extends JavaMatchFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		return match.getAccuracy() == SearchMatch.A_INACCURATE;
	}

	@Override
	public String getName() {
		return SearchMessages.MatchFilter_PotentialFilter_name;
	}

	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_PotentialFilter_actionLabel;
	}

	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_PotentialFilter_description;
	}

	@Override
	public boolean isApplicable(JavaSearchQuery query) {
		return true;
	}

	@Override
	public String getID() {
		return "filter_potential"; //$NON-NLS-1$
	}
}

class ImportFilter extends JavaMatchFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		return match.getElement() instanceof IImportDeclaration;
	}

	@Override
	public String getName() {
		return SearchMessages.MatchFilter_ImportFilter_name;
	}

	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_ImportFilter_actionLabel;
	}

	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_ImportFilter_description;
	}

	@Override
	public boolean isApplicable(JavaSearchQuery query) {
		QuerySpecification spec= query.getSpecification();
		if (spec instanceof ElementQuerySpecification) {
			ElementQuerySpecification elementSpec= (ElementQuerySpecification) spec;
			IJavaElement element= elementSpec.getElement();
			switch (element.getElementType()) {
				case IJavaElement.TYPE:
				case IJavaElement.METHOD:
				case IJavaElement.FIELD:
				case IJavaElement.PACKAGE_FRAGMENT:
					return true;
				default:
					return false;
			}
		} else if (spec instanceof PatternQuerySpecification) {
			return true;
		}
		return false;
	}

	@Override
	public String getID() {
		return "filter_imports"; //$NON-NLS-1$
	}
}

abstract class VariableFilter extends JavaMatchFilter {
	@Override
	public boolean isApplicable(JavaSearchQuery query) {
		QuerySpecification spec= query.getSpecification();
		if (spec instanceof ElementQuerySpecification) {
			ElementQuerySpecification elementSpec= (ElementQuerySpecification) spec;
			IJavaElement element= elementSpec.getElement();
			return element instanceof IField || element instanceof ILocalVariable;
		} else if (spec instanceof PatternQuerySpecification) {
			PatternQuerySpecification patternSpec= (PatternQuerySpecification) spec;
			return patternSpec.getSearchFor() == IJavaSearchConstants.FIELD;
		}
		return false;
	}

}

class WriteFilter extends VariableFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		return match.isWriteAccess() && !match.isReadAccess();
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_WriteFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_WriteFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_WriteFilter_description;
	}
	@Override
	public String getID() {
		return "filter_writes"; //$NON-NLS-1$
	}
}

class ReadFilter extends VariableFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		return match.isReadAccess() && !match.isWriteAccess();
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_ReadFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_ReadFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_ReadFilter_description;
	}
	@Override
	public String getID() {
		return "filter_reads"; //$NON-NLS-1$
	}
}

class JavadocFilter extends JavaMatchFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		return match.isJavadoc();
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_JavadocFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_JavadocFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_JavadocFilter_description;
	}
	@Override
	public boolean isApplicable(JavaSearchQuery query) {
		return true;
	}
	@Override
	public String getID() {
		return "filter_javadoc"; //$NON-NLS-1$
	}
}

class PolymorphicFilter extends JavaMatchFilter {
    @Override
	public boolean filters(JavaElementMatch match) {
        return match.isSuperInvocation();
    }

    @Override
	public String getName() {
        return SearchMessages.MatchFilter_PolymorphicFilter_name;
    }

    @Override
	public String getActionLabel() {
        return SearchMessages.MatchFilter_PolymorphicFilter_actionLabel;
    }

    @Override
	public String getDescription() {
        return SearchMessages.MatchFilter_PolymorphicFilter_description;
    }

    @Override
	public boolean isApplicable(JavaSearchQuery query) {
        QuerySpecification spec= query.getSpecification();
        switch (spec.getLimitTo()) {
			case IJavaSearchConstants.REFERENCES:
			case IJavaSearchConstants.ALL_OCCURRENCES:
                if (spec instanceof ElementQuerySpecification) {
                    ElementQuerySpecification elementSpec= (ElementQuerySpecification) spec;
                    return elementSpec.getElement() instanceof IMethod;
                } else if (spec instanceof PatternQuerySpecification) {
                    PatternQuerySpecification patternSpec= (PatternQuerySpecification) spec;
                    return patternSpec.getSearchFor() == IJavaSearchConstants.METHOD;
                }
        }
        return false;
    }

    @Override
	public String getID() {
        return "filter_polymorphic"; //$NON-NLS-1$
    }
}

abstract class GenericTypeFilter extends JavaMatchFilter {
	@Override
	public boolean isApplicable(JavaSearchQuery query) {
		QuerySpecification spec= query.getSpecification();
		if (spec instanceof ElementQuerySpecification) {
			ElementQuerySpecification elementSpec= (ElementQuerySpecification) spec;
			IJavaElement element= elementSpec.getElement();
			return isParameterizedElement(element);
		}
		return false;
	}

	private static boolean isParameterizedElement(IJavaElement element) {
		while (element != null) {
			ITypeParameter[] typeParameters= null;
			try {
				if (element instanceof IType) {
					typeParameters= ((IType)element).getTypeParameters();
				} else if (element instanceof IMethod) {
					typeParameters= ((IMethod)element).getTypeParameters();
				}
			} catch (JavaModelException e) {
				return false;
			}
			if (typeParameters == null)
				return false;
			
			if (typeParameters.length > 0)
				return true;
			
			element= element.getParent();
		}
		return false;
	}
}

class ErasureMatchFilter extends GenericTypeFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		return (match.getMatchRule() & (SearchPattern.R_FULL_MATCH | SearchPattern.R_EQUIVALENT_MATCH)) == 0;
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_ErasureFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_ErasureFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_ErasureFilter_description;
	}
	@Override
	public String getID() {
		return "filter_erasure"; //$NON-NLS-1$
	}
}

class InexactMatchFilter extends GenericTypeFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		return (match.getMatchRule() & (SearchPattern.R_FULL_MATCH)) == 0;
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_InexactFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_InexactFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_InexactFilter_description;
	}
	@Override
	public String getID() {
		return "filter_inexact"; //$NON-NLS-1$
	}
}

abstract class ModifierFilter extends JavaMatchFilter {
	@Override
	public boolean isApplicable(JavaSearchQuery query) {
		return true;
	}
}

class NonPublicFilter extends ModifierFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		Object element= match.getElement();
		if (element instanceof IMember) {
			try {
				return ! JdtFlags.isPublic((IMember) element);
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
			}
		}
		return false;
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_NonPublicFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_NonPublicFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_NonPublicFilter_description;
	}
	@Override
	public String getID() {
		return "filter_non_public"; //$NON-NLS-1$
	}
}

class StaticFilter extends ModifierFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		Object element= match.getElement();
		if (element instanceof IMember) {
			try {
				return JdtFlags.isStatic((IMember) element);
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
			}
		}
		return false;
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_StaticFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_StaticFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_StaticFilter_description;
	}
	@Override
	public String getID() {
		return 	"filter_static"; //$NON-NLS-1$
	}
}

class NonStaticFilter extends ModifierFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		Object element= match.getElement();
		if (element instanceof IMember) {
			try {
				return ! JdtFlags.isStatic((IMember) element);
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
			}
		}
		return false;
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_NonStaticFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_NonStaticFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_NonStaticFilter_description;
	}
	@Override
	public String getID() {
		return 	"filter_non_static"; //$NON-NLS-1$
	}
}

class DeprecatedFilter extends ModifierFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		Object element= match.getElement();
		if (element instanceof IMember) {
			try {
				return JdtFlags.isDeprecated((IMember) element);
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
			}
		}
		return false;
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_DeprecatedFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_DeprecatedFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_DeprecatedFilter_description;
	}
	@Override
	public String getID() {
		return 	"filter_deprecated"; //$NON-NLS-1$
	}
}

class NonDeprecatedFilter extends ModifierFilter {
	@Override
	public boolean filters(JavaElementMatch match) {
		Object element= match.getElement();
		if (element instanceof IMember) {
			try {
				return !JdtFlags.isDeprecated((IMember) element);
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
			}
		}
		return false;
	}
	@Override
	public String getName() {
		return SearchMessages.MatchFilter_NonDeprecatedFilter_name;
	}
	@Override
	public String getActionLabel() {
		return SearchMessages.MatchFilter_NonDeprecatedFilter_actionLabel;
	}
	@Override
	public String getDescription() {
		return SearchMessages.MatchFilter_NonDeprecatedFilter_description;
	}
	@Override
	public String getID() {
		return 	"filter_non_deprecated"; //$NON-NLS-1$
	}
}
