/*
 * Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package freemarker.ext.jsp;

import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.StringUtil;

final class TaglibMethodUtil {

    private TaglibMethodUtil() {
        // Not meant to be instantiated
    }

    private static final Pattern FUNCTION_SIGNATURE_PATTERN = 
            Pattern.compile("^([\\w\\.]+(\\s*\\[\\s*\\])?)\\s+([\\w]+)\\s*\\((.*)\\)$");
    private static final Pattern FUNCTION_PARAMETER_PATTERN = 
            Pattern.compile("^([\\w\\.]+)(\\s*\\[\\s*\\])?$");

    /**
     * Finds method by function signature string which is compliant with
     * Tag Library function signature in Java Server Page (TM) Specification.
     * A function signature example is as follows: {@code java.lang.String nickName( java.lang.String, int)}
     * 
     * @param clazz Class having the method.
     * @param signature Java Server Page (TM) Specification compliant function signature string.
     * @return method if found.
     */
    public static Method getMethodByFunctionSignature(Class clazz, String signature)
            throws SecurityException, NoSuchMethodException, ClassNotFoundException {
        Matcher m1 = FUNCTION_SIGNATURE_PATTERN.matcher(signature);

        if (!m1.matches()) {
            throw new IllegalArgumentException("Invalid function signature.");
        }

            String methodName = m1.group(3);
            String params = m1.group(4).trim();
            Class [] paramTypes = null;

            if ("".equals(params)) {
                paramTypes = new Class[0];
            }
            else {
                String [] paramsArray = StringUtil.split(params, ',');
                paramTypes = new Class[paramsArray.length];
                String token = null;
                String paramType = null;
                boolean isPrimitive = false;
                boolean isArrayType = false;
                Matcher m2 = null;

                for (int i = 0; i < paramsArray.length; i++) {
                    token = paramsArray[i].trim();
                    m2 = FUNCTION_PARAMETER_PATTERN.matcher(token);

                    if (!m2.matches()) {
                        throw new IllegalArgumentException("Invalid argument signature: '" + token + "'.");
                    }

                    paramType = m2.group(1);
                    isPrimitive = (paramType.indexOf('.') == -1);
                    isArrayType = (m2.group(2) != null);

                    if (isPrimitive) {
                        if ("byte".equals(paramType)) {
                            paramTypes[i] = (isArrayType ? byte[].class : byte.class);
                        }
                        else if ("short".equals(paramType)) {
                            paramTypes[i] = (isArrayType ? short[].class : short.class);
                        }
                        else if ("int".equals(paramType)) {
                            paramTypes[i] = (isArrayType ? int[].class : int.class);
                        }
                        else if ("long".equals(paramType)) {
                            paramTypes[i] = (isArrayType ? long[].class : long.class);
                        }
                        else if ("float".equals(paramType)) {
                            paramTypes[i] = (isArrayType ? float[].class : float.class);
                        }
                        else if ("double".equals(paramType)) {
                            paramTypes[i] = (isArrayType ? double[].class : double.class);
                        }
                        else if ("boolean".equals(paramType)) {
                            paramTypes[i] = (isArrayType ? boolean[].class : boolean.class);
                        }
                        else if ("char".equals(paramType)) {
                            paramTypes[i] = (isArrayType ? char[].class : char.class);
                        }
                        else {
                            throw new IllegalArgumentException("Invalid primitive type: '" + paramType + "'.");
                        }
                    }
                    else {
                        if (isArrayType) {
                            paramTypes[i] = ClassUtil.forName("[L" + paramType + ";");
                        }
                        else {
                            paramTypes[i] = ClassUtil.forName(paramType);
                        }
                    }
                }
            }

            return clazz.getMethod(methodName, paramTypes);
    }
    
}
