/*
 *  Copyright 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 therian;

import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Comparator;
import java.util.Map;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.reflect.TypeUtils;
import therian.util.Types;

/**
 * Utility methods for Operators.
 */
public class Operators {

    private static final Comparator<Operator<?>> COMPARATOR = new Comparator<Operator<?>>() {
        private final TypeVariable<?> opVar = Operator.class.getTypeParameters()[0];

        @Override
        public int compare(Operator<?> o1, Operator<?> o2) {
            final Type opType1 =
                TypeUtils.unrollVariables(TypeUtils.getTypeArguments(o1.getClass(), Operator.class), opVar);
            final Type opType2 =
                TypeUtils.unrollVariables(TypeUtils.getTypeArguments(o2.getClass(), Operator.class), opVar);

            return compareTypes(opType1, opType2);
        }

        private int compareTypes(Type t1, Type t2) {
            if (TypeUtils.equals(t1, t2)) {
                return 0;
            }
            if (TypeUtils.isAssignable(t1, t2)) {
                return -1;
            }
            if (TypeUtils.isAssignable(t2, t1)) {
                return 1;
            }
            final Class<?> raw1 = raw(t1);
            final Class<?> raw2 = raw(t2);

            if (ObjectUtils.equals(raw1, raw2)) {
                if (raw1.getTypeParameters().length == 0) {
                    return 0;
                }
                final Map<TypeVariable<?>, Type> typeArgs1 = TypeUtils.getTypeArguments(t1, raw1);
                final Map<TypeVariable<?>, Type> typeArgs2 = TypeUtils.getTypeArguments(t2, raw2);
                for (TypeVariable<?> var : raw1.getTypeParameters()) {
                    final int recurse =
                        compareTypes(TypeUtils.unrollVariables(typeArgs1, var), TypeUtils.unrollVariables(typeArgs2, var));
                    if (recurse != 0) {
                        return recurse;
                    }
                }
                return 0;
            }
            return Types.toString(t1).compareTo(Types.toString(t2));
        }

        private Class<?> raw(Type type) {
            if (type instanceof WildcardType) {
                final Type upper = TypeUtils.getImplicitUpperBounds((WildcardType) type)[0];
                return upper instanceof Class<?> ? (Class<?>) upper : raw(upper);
            }
            return TypeUtils.getRawType(type, null);
        }

    };

    /**
     * Get standard operators.
     * 
     * @return Operator[]
     * @see Therian#standard()
     */
    public static Operator<?>[] standard() {
        return StandardOperators.STANDARD_OPERATORS.toArray(new Operator[0]);
    }

    /**
     * Validate an {@link Operator} implementation.
     * 
     * @param operator
     * @param <OPERATOR>
     * @return {@code operator}
     * @throws OperatorDefinitionException on invalid operator
     */
    public static <OPERATOR extends Operator<?>> OPERATOR validateImplementation(OPERATOR operator) {
        for (TypeVariable<?> var : Validate.notNull(operator, "operator").getClass().getTypeParameters()) {
            if (Types.resolveAt(operator, var) == null) {
                throw new OperatorDefinitionException(operator, "Could not resolve %s against operator %s",
                    TypeUtils.toLongString(var), operator);
            }
        }
        return operator;
    }

    /**
     * Get a comparator that compares {@link Operator}s by {@link Operation} type/type parameter assignability.
     * 
     * @return a Comparator that does not handle {@code null} values
     */
    public static Comparator<Operator<?>> comparator() {
        return COMPARATOR;
    }
}

@@ -791,16 +791,17 @@
 il.Map;%0A
+%0A
 import o
@@ -824,22 +824,22 @@
 s.lang3.
-Object
+String
 Utils;%0Ai
@@ -926,24 +926,25 @@
 .TypeUtils;%0A
+%0A
 import theri
@@ -1694,32 +1694,44 @@
             if (
+t1 == t2 %7C%7C 
 TypeUtils.equals
@@ -2002,24 +2002,105 @@
  = raw(t1);%0A
+            Validate.validState(raw1 != null, %22Cannot get raw type for %25s%22, t1);%0A
             
@@ -2130,17 +2130,16 @@
 aw(t2);%0A
-%0A
         
@@ -2146,31 +2146,106 @@
     
-if (ObjectUtils
+Validate.validState(raw2 != null, %22Cannot get raw type for %25s%22, t2);%0A%0A            if (raw1
 .equals(
 raw1
@@ -2244,14 +2244,8 @@
 als(
-raw1, 
 raw2
@@ -2745,16 +2745,44 @@
 1, var),
+%0A                           
  TypeUti
@@ -2994,63 +2994,306 @@
     
-return Types.toString(t1).compareTo(Types.toString(t2))
+final int steps =%0A                Integer.compare(StringUtils.countMatches(raw1.getName(), %22.%22),%0A                    StringUtils.countMatches(raw2.getName(), %22.%22));%0A            if (steps == 0) %7B%0A                return raw1.getName().compareTo(raw2.getName());%0A            %7D%0A            return steps
 ;%0A  

work on Operators comparator