/*
 *   Copyright 2016 the original author or authors.
 *
 *   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 org.powermock.reflect.internal.comparator;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Comparator;

/**
 *  This comparator factory is used to create Comparators for
 *  {@link org.powermock.reflect.Whitebox} which are used to find best candidates
 *  for constructor and method invocation.
 *  @see org.powermock.reflect.internal.WhiteboxImpl#getBestMethodCandidate(Class, String, Class[], boolean)
 *  @see org.powermock.reflect.internal.WhiteboxImpl#getBestCandidateConstructor(Class, Class[], Object[])
 *  @see org.powermock.reflect.internal.CandidateConstructorSearcher
 */
public class ComparatorFactory {

    private ComparatorFactory() {
    }

    public static Comparator<Constructor> createConstructorComparator(){
        return new ConstructorComparator(new ParametersComparator());
    }

    public static Comparator<Method> createMethodComparator(){
        return new MethodComparator(new ParametersComparator());
    }


    public static class ConstructorComparator implements Comparator<Constructor> {
        private final ParametersComparator parametersComparator;

        private ConstructorComparator(ParametersComparator parametersComparator) {

            this.parametersComparator = parametersComparator;
        }

        @Override
        public int compare(Constructor constructor1, Constructor constructor2) {
            final Class<?>[] parameters1 = constructor1.getParameterTypes();
            final Class<?>[] parameters2 = constructor2.getParameterTypes();
            return parametersComparator.compare(parameters1,parameters2);
        }
    }

    /**
     *
     */
    public static class MethodComparator implements Comparator<Method> {
        private final ParametersComparator parametersComparator;

        private MethodComparator(ParametersComparator parametersComparator) {

            this.parametersComparator = parametersComparator;
        }

        @Override
        public int compare(Method m1, Method m2) {
            final Class<?>[] typesMethod1 = m1.getParameterTypes();
            final Class<?>[] typesMethod2 = m2.getParameterTypes();
            return parametersComparator.compare(typesMethod1, typesMethod2);

        }
    }

    private static class ParametersComparator implements Comparator<Class[]>{

        @Override
        public int compare(Class[] params1, Class[] params2) {
            final int size = params1.length;
            for (int i = 0; i < size; i++) {
                Class<?> type1 = params1[i];
                Class<?> type2 = params2[i];
                if (!type1.equals(type2)) {
                    if (type1.isAssignableFrom(type2)) {
                        if (!type1.isArray() && type2.isArray()) {
                            return -1;
                        }
                        return 1;
                    } else {
                        if (type1.isArray() && !type2.isArray()) {
                            return 1;
                        }
                        return -1;
                    }
                }
            }
            return 0;
        }
    }
}
