/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.util;

import java.lang.reflect.Modifier;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class ClassUtils {
    
    private ClassUtils() {
        // utility class
    }
    
    /**
     * Zamienia typ prymitywny na odpowiadający mu wrapper, np {@code int}
     * zamienia na {@code Integer}. W pozostałych przypadkach po prostu zwraca
     * przekazany argument bez żadnych zmian. W szczególności metoda może być
     * bezpiecznie wywoływana na rzecz wrapperów:
     * <pre>{@code box(box(int.class));}</pre>
     * @param clazz
     * @return
     */
    @SuppressWarnings("PMD")
    public static Class<?> box(Class<?> clazz) {

        if (byte[].class.equals(clazz)) {
            return Byte[].class;
        } else if (short[].class.equals(clazz)) {
            return Short[].class;
        } else if (int[].class.equals(clazz)) {
            return Integer[].class;
        } else if (long[].class.equals(clazz)) {
            return Long[].class;
        } else if (char[].class.equals(clazz)) {
            return Character[].class;
        } else if (float[].class.equals(clazz)) {
            return Float[].class;
        } else if (double[].class.equals(clazz)) {
            return Double[].class;
        } else if (boolean[].class.equals(clazz)) {
            return Boolean[].class;
        }

        if (byte.class.equals(clazz)) {
            return Byte.class;
        } else if (short.class.equals(clazz)) {
            return Short.class;
        } else if (int.class.equals(clazz)) {
            return Integer.class;
        } else if (long.class.equals(clazz)) {
            return Long.class;
        } else if (char.class.equals(clazz)) {
            return Character.class;
        } else if (float.class.equals(clazz)) {
            return Float.class;
        } else if (double.class.equals(clazz)) {
            return Double.class;
        } else if (boolean.class.equals(clazz)) {
            return Boolean.class;
        } else if (void.class.equals(clazz)) {
            return Void.class;
        } else {
            return clazz;
        }
    }

    @SuppressWarnings("PMD")
    public static Class<?> unbox(Class<?> clazz) {

        if (Byte[].class.equals(clazz)) {
            return byte[].class;
        } else if (Short[].class.equals(clazz)) {
            return short[].class;
        } else if (Integer[].class.equals(clazz)) {
            return int[].class;
        } else if (Long[].class.equals(clazz)) {
            return long[].class;
        } else if (Character[].class.equals(clazz)) {
            return char[].class;
        } else if (Float[].class.equals(clazz)) {
            return float[].class;
        } else if (Double[].class.equals(clazz)) {
            return double[].class;
        } else if (Boolean[].class.equals(clazz)) {
            return boolean[].class;
        }

        if (Byte.class.equals(clazz)) {
            return byte.class;
        } else if (Short.class.equals(clazz)) {
            return short.class;
        } else if (Integer.class.equals(clazz)) {
            return int.class;
        } else if (Long.class.equals(clazz)) {
            return long.class;
        } else if (Character.class.equals(clazz)) {
            return char.class;
        } else if (Float.class.equals(clazz)) {
            return float.class;
        } else if (Double.class.equals(clazz)) {
            return double.class;
        } else if (Boolean.class.equals(clazz)) {
            return boolean.class;
        } else if (Void.class.equals(clazz)) {
            return void.class;
        } else {
            return clazz;
        }
    }

    
    public static boolean isStatic(Class<?> clazz) {
        return Modifier.isStatic(clazz.getModifiers());
    }

    public static boolean isPublic(Class<?> clazz) {
        return Modifier.isPublic(clazz.getModifiers());
    }

    public static boolean isProtected(Class<?> clazz) {
        return Modifier.isProtected(clazz.getModifiers());
    }

    public static boolean isPrivate(Class<?> clazz) {
        return Modifier.isPrivate(clazz.getModifiers());
    }

    public static boolean isInterface(Class<?> clazz) {
        return Modifier.isInterface(clazz.getModifiers());
    }

    public static boolean isFinal(Class<?> clazz) {
        return Modifier.isFinal(clazz.getModifiers());
    }

    public static boolean isAbstract(Class<?> clazz) {
        return Modifier.isAbstract(clazz.getModifiers());
    }
    
    /**
     * Rozszerzenie standardowej metody {@code Class.isAssignableFrom}.
     * Uwzględnia autoboxing argumentów.
     * @param ssuper
     * @param value
     * @return
     */
    @SuppressWarnings("PMD.CyclomaticComplexity")
    public static boolean isAssignableFrom(Class<?> ssuper, Class<?> value) {
        assert ssuper != null;
        assert value != null;
        if (ssuper.equals(boolean.class) || ssuper.equals(Boolean.class)) {
            return value.equals(boolean.class) || value.equals(Boolean.class);
        } else if (ssuper.equals(char.class) || ssuper.equals(Character.class)) {
            return value.equals(char.class) || value.equals(Character.class);
        } else if (ssuper.equals(byte.class) || ssuper.equals(Byte.class)) {
            return value.equals(byte.class) || value.equals(Byte.class);
        } else if (ssuper.equals(short.class) || ssuper.equals(Short.class)) {
            return value.equals(short.class) || value.equals(Short.class);
        } else if (ssuper.equals(int.class) || ssuper.equals(Integer.class)) {
            return value.equals(int.class) || value.equals(Integer.class);
        } else if (ssuper.equals(long.class) || ssuper.equals(Long.class)) {
            return value.equals(long.class) || value.equals(Long.class);
        } else if (ssuper.equals(float.class) || ssuper.equals(Float.class)) {
            return value.equals(float.class) || value.equals(Float.class);
        } else if (ssuper.equals(double.class) || ssuper.equals(Double.class)) {
            return value.equals(double.class) || value.equals(Double.class);
        } else {
            return ssuper.isAssignableFrom(value);
        }
    }
    
    /**
     * Grupowa wersja funkcji {@code Class.isAssignableFrom} - sprawdza, czy odpowiednie
     * elementy z tablicy {@code supers} są assignable względem odpowiednich
     * elementów {@code values}, czyli czy <br>
     * {@code for(i) supers[i].isAssignableFrom( values[i] ) }
     * Uwzględnia autoboxing argumentów.
     * @param supers
     * @param values
     * @return
     */
    public static boolean isAssignableFrom(Class<?>[] supers, Class<?>[] values) {
        assert supers != null;
        assert values != null;
        if(supers.length != values.length) {
            return false;
        }
        for(int i=0; i<supers.length; i++) {
            if( !isAssignableFrom(supers[i], values[i]) ) { return false; }
        }
        return true;
    }

    /**
     * Sprawdza, czy podany obiekt {@code get} jest instancją klasy {@code ssuper}.
     * Uwzględnia autoboxing argumentów.
     * @param ssuper
     * @param value
     * @return
     */
    public static boolean isInstance(Class<?> ssuper, Object value) {
        assert ssuper != null;
        return value==null || ssuper.isInstance(value) || box(ssuper).isInstance(value);
    }

    /**
     * Sprawdza, czy odpowiednie obiekty z tablicy {@code values} są istancjami
     * odpowiednich klas z tablicy {@code supers}. Uwzględnia autoboxing argumentów.
     * @param supers
     * @param values
     * @return
     */
    public static boolean isInstance(Class<?>[] supers, Object[] values) {
        assert supers != null;
        assert values != null;
        if(supers.length != values.length) {
            return false;
        }
        for(int i=0; i<supers.length; i++) {
            if( !isInstance(supers[i], values[i]) ) { return false; }
        }
        return true;
    }
    
}
