/*
 * Copyright (c) 2006 JMockit developers
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */

package org.apache.doris.common.jmockit;

import java.util.regex.Pattern;

/**
 * Modify from mockit.internal.util.ParameterReflection JMockit v1.13
 * Util class to verify parameter of methods.
 * <p>
 * Copied from Apache Doris
 */
public final class ParameterReflection {
    public static final Class<?>[] NO_PARAMETERS = new Class[0];

    public static final Pattern JAVA_LANG = Pattern.compile("java.lang.", 16);

    private ParameterReflection() {
    }

    /**
     * check if every member in {@declaredTypes} is completely equal to the corresponding member {@specifiedTypes}.
     */
    static boolean matchesParameterTypes(Class<?>[] declaredTypes, Class<?>[] specifiedTypes) {
        if (declaredTypes == null || specifiedTypes == null) {
            throw new IllegalArgumentException();
        }
        for (int i = 0; i < declaredTypes.length; ++i) {
            Class<?> declaredType = declaredTypes[i];
            Class<?> specifiedType = specifiedTypes[i];
            if (!isSameType(declaredType, specifiedType)) {
                return false;
            }
        }

        return true;
    }

    /**
     * check if every member in {@paramTypes} is acceptable to the corresponding member in {@argTypes}.
     */
    static boolean acceptsArgumentTypes(Class<?>[] paramTypes, Class<?>[] argTypes) {
        if (paramTypes == null || argTypes == null) {
            throw new IllegalArgumentException();
        }
        for (int i = 0; i < paramTypes.length; ++i) {
            Class<?> parType = paramTypes[i];
            Class<?> argType = argTypes[i];
            if (!isSameType(parType, argType) && !parType.isAssignableFrom(argType)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get all types from objects {@args}.
     */
    static Class<?>[] getArgumentTypesFromArgumentValues(Object... args) {
        if (args == null) {
            throw new IllegalArgumentException();
        }
        if (args.length == 0) {
            return NO_PARAMETERS;
        } else {
            Class<?>[] argTypes = new Class[args.length];

            for (int i = 0; i < args.length; ++i) {
                argTypes[i] = getArgumentTypeFromArgumentValue(i, args);
            }

            return argTypes;
        }
    }

    /**
     * Get type from {@args} by index.
     */
    static Class<?> getArgumentTypeFromArgumentValue(int i, Object[] args) {
        Object arg = args[i];
        if (arg == null) {
            throw new IllegalArgumentException("Invalid null value passed as argument " + i);
        } else {
            Class argType;
            if (arg instanceof Class) {
                argType = (Class) arg;
                args[i] = null;
            } else {
                argType = GeneratedClasses.getMockedClass(arg);
            }

            return argType;
        }
    }

    /**
     * return true if {@currentTypes} is more specific than {@previousTypes}.
     */
    static boolean hasMoreSpecificTypes(Class<?>[] currentTypes, Class<?>[] previousTypes) {
        if (currentTypes == null || previousTypes == null) {
            throw new IllegalArgumentException();
        }
        for (int i = 0; i < currentTypes.length; ++i) {
            Class<?> current = wrappedIfPrimitive(currentTypes[i]);
            Class<?> previous = wrappedIfPrimitive(previousTypes[i]);
            if (current != previous && previous.isAssignableFrom(current)) {
                return true;
            }
        }

        return false;
    }

    /**
     * return the type names of {@paramTypes} wrapped in brackets.
     */
    static String getParameterTypesDescription(Class<?>[] paramTypes) {
        if (paramTypes == null) {
            throw new IllegalArgumentException();
        }
        StringBuilder paramTypesDesc = new StringBuilder(200);
        paramTypesDesc.append('(');
        String sep = "";

        for (Class paramType : paramTypes) {
            String typeName = JAVA_LANG.matcher(paramType.getCanonicalName()).replaceAll("");
            paramTypesDesc.append(sep).append(typeName);
            sep = ", ";
        }

        paramTypesDesc.append(')');
        return paramTypesDesc.toString();
    }

    /**
     * return real parameters array of inner-class belong to the outer-class instance {@firstValue Object}.
     * the parameter[0] of a inner-class constructor is always the instance of its outer-class.
     */
    static Object[] argumentsWithExtraFirstValue(Object[] args, Object firstValue) {
        Object[] args2 = new Object[1 + args.length];
        args2[0] = firstValue;
        System.arraycopy(args, 0, args2, 1, args.length);
        return args2;
    }

    // return wrapped type if its type is primitive.
    private static Class<?> wrappedIfPrimitive(Class<?> parameterType) {
        if (parameterType.isPrimitive()) {
            Class<?> wrapperType = AutoType.getWrapperType(parameterType);

            assert wrapperType != null;

            return wrapperType;
        } else {
            return parameterType;
        }
    }

    // return true if the two types are same type.
    private static boolean isSameType(Class<?> firstType, Class<?> secondType) {
        return firstType == secondType
                || firstType.isPrimitive() && firstType == AutoType.getPrimitiveType(secondType)
                || secondType.isPrimitive() && secondType == AutoType.getPrimitiveType(firstType);
    }
}
