package org.mvel2.util;

public class CompatibilityStrategy {

    private CompatibilityStrategy() { }

    public interface CompatibilityEvaluator {
        boolean areEqualityCompatible(Class<?> c1, Class<?> c2);
        boolean areComparisonCompatible(Class<?> c1, Class<?> c2);
    }

    public static CompatibilityEvaluator compatibilityEvaluator = new DefaultCompatibilityEvaluator();

    public static boolean areEqualityCompatible(Class<?> c1, Class<?> c2) {
        return compatibilityEvaluator.areEqualityCompatible(c1, c2);
    }

    public static boolean areComparisonCompatible(Class<?> c1, Class<?> c2) {
        return compatibilityEvaluator.areComparisonCompatible(c1, c2);
    }

    public static void setCompatibilityEvaluator(CompatibilityEvaluator compatibilityEvaluator) {
        CompatibilityStrategy.compatibilityEvaluator = compatibilityEvaluator;
    }

    public static class DefaultCompatibilityEvaluator implements CompatibilityEvaluator {

        public boolean areEqualityCompatible(Class<?> c1, Class<?> c2) {
            if (c1 == NullType.class || c2 == NullType.class) return true;
            if (c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1)) return true;
            if (isBoxedNumber(c1, false) && isBoxedNumber(c2, true)) return true;
            if (c1.isPrimitive()) return c2.isPrimitive() || arePrimitiveCompatible(c1, c2, true);
            if (c2.isPrimitive()) return arePrimitiveCompatible(c2, c1, false);
            return false;
        }

        public boolean areComparisonCompatible(Class<?> c1, Class<?> c2) {
            return areEqualityCompatible(c1, c2);
        }

        private boolean arePrimitiveCompatible(Class<?> primitive, Class<?> boxed, boolean leftFirst) {
            if (primitive == Boolean.TYPE) return boxed == Boolean.class;
            if (primitive == Integer.TYPE) return isBoxedNumber(boxed, leftFirst);
            if (primitive == Long.TYPE) return isBoxedNumber(boxed, leftFirst);
            if (primitive == Double.TYPE) return isBoxedNumber(boxed, leftFirst);
            if (primitive == Float.TYPE) return isBoxedNumber(boxed, leftFirst);
            if (primitive == Character.TYPE) return boxed == Character.class;
            if (primitive == Byte.TYPE) return boxed == Byte.class;
            if (primitive == Short.TYPE) return boxed == Short.class;
            return false;
        }

        private boolean isBoxedNumber(Class<?> c, boolean allowString) {
            return Number.class.isAssignableFrom(c) || (allowString && c == String.class);
        }
    }
}
