package de.renew.formalism.java;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import de.renew.util.StringUtil;

/**
 * A suggestion for a method that can be used, based on given types and name.
 */
public class MethodSuggestion extends Suggestion {

    /**
     * Logger for writing debug messages.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(MethodSuggestion.class);
    private final Class[] _types;
    private final Method _method;
    private final String[] _parameters;
    private final String _callWithParameters;
    private final String _attemptedMethod;

    /**
     * Finds matching method suggestions for a given class and method name.
     * @param clazz the class to search methods in
     * @param name the name of the method to match
     * @param types the parameter types to match
     * @param modifier required modifiers
     * @return a list of matching method suggestions
     */
    public static List<MethodSuggestion> suggest(
        Class<?> clazz, String name, Class<?>[] types, int modifier)
    {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(JavaHelper.class.getName() + ": Class = " + clazz.toString());
        }

        List<Method> resultMethodList = new ArrayList<Method>();

        Method[] methods = clazz.getMethods();
        if (methods.length != 0) {
            String methodPattern = name;

            boolean filter = false;

            // do filter if method name suffixes with "_"
            if (methodPattern.endsWith("_")) {
                methodPattern = methodPattern.substring(0, methodPattern.length() - 1);
                filter = true;
            }

            // do filtering when number of methods is large
            if (methods.length > 20) {
                filter = true;
            }

            // force no filtering by typing only "_"
            if (name.equals("_")) {
                filter = false;
            }

            for (Method method : methods) {
                int mod = method.getModifiers();
                String methodName = method.getName();
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Name " + methodName + " pattern: " + methodPattern);
                    LOGGER.debug(
                        JavaHelper.class.getName() + ": modifier for " + method.getName() + "= "
                            + mod + " " + !filter + methodName.startsWith(methodPattern));
                }
                if (!filter || methodName.startsWith(methodPattern)) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(
                            JavaHelper.class.getName() + ": passed filter "
                                + ((modifier & mod) != 0));
                    }
                    if ((modifier & mod) != 0) {
                        resultMethodList.add(method);
                    }
                }
            }
        }

        List<MethodSuggestion> result = new ArrayList<MethodSuggestion>();
        for (Method method : resultMethodList) {
            result.add(new MethodSuggestion(method, types, name));
        }
        Collections.sort(result);

        return result;
    }

    /**
     * Creates a new method suggestion using the method, input types, and method name.
     *  @param method the method that was found
     *  @param types the types of the parameters used to find it
     *  @param attemptedMethod the method name that was searched for
     */
    public MethodSuggestion(Method method, Class[] types, String attemptedMethod) {
        super(method.getName(), method.getReturnType().getSimpleName());
        this._method = method;
        this._types = types;
        this._attemptedMethod = attemptedMethod;

        Class<?>[] parameterTypes = method.getParameterTypes();
        this._parameters = new String[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            this._parameters[i] = parameterTypes[i].getSimpleName();
        }

        this._callWithParameters = this._name + "(" + StringUtil.join(_parameters, ", ") + ")";
    }

    @Override
    public String toString() {
        return "<html>" + getCallWithParameters() + " : " + getTypeName() + " <font color=gray>- "
            + _method.getDeclaringClass().getSimpleName() + "</font></html>";
    }

    /**
     * Returns the method call as text with parameter names.
     * @return the method call as a string
     */
    public String getCallWithParameters() {
        return _callWithParameters;
    }

    /**
     * Returns the method that is suggested.
     * @return the matched Method object
     */
    public Method getMethod() {
        return _method;
    }

    /**
     * Returns the types that were used to search for the method.
     * @return an array of Class objects representing the types
     */
    public Class<?>[] getAttemptedTypes() {
        return _types;
    }

    /**
     * Returns the parameter type names of the method.
     * @return an array of Strings with the parameter type names
     */
    public String[] getParameters() {
        return _parameters;
    }

    /**
     * Returns the name of the method that was tried.
     * @return the attempted method name
     */
    public String getAttemptedMethod() {
        return _attemptedMethod;
    }
}