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

import java.lang.reflect.Field;
import java.util.List;
import net.ranides.assira.reflection.GenericField;
import net.ranides.assira.reflection.InspectException;
import static net.ranides.assira.reflection.util.MemberUtils.isFinal;
import static net.ranides.assira.reflection.util.MemberUtils.isStatic;
import static net.ranides.assira.reflection.util.ReflectUtils.access;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class FieldInspector {
    
    private FieldInspector() {
        // utility class
    }

    public static GenericField<?> find(Class<?> clazz, String name) {
        try {
            return new GenericField<>(clazz.getDeclaredField(name));
        } catch (NoSuchFieldException | SecurityException ex) {
            throw new InspectException(ex);
        }
    }
    
    public static <T> GenericField<T> check(Class<T> type, GenericField<?> field) {
        if( !type.isAssignableFrom(field.type().rawType()) ) {
            throw new InspectException("field type is incompatible: " + type + " vs " + field.type());
        }
        return (GenericField<T>)field;
    }

    /**
     * <p>
     * Przepisuje wszystkie pola z obiektu źródłowego do obiektu docelowego. 
     * Jeśli w obu obiektach zostanie znalezione pole o tej samej nazwie, i 
     * kompatybilnym typie, to następuje przepisanie jego wartości (niezależnie 
     * od tego, gdzie w hierarchii dziedziczenia zadeklarowano pola). 
     * </p><p>
     * Metoda nie jest rekurencyjna wgłąd, wykonuje płytką kopię, to znaczy, że
     * kopiowane są w istocie referencje. W praktyce większość obiektów nie powinna
     * być w ten sposób kopiowana, dlatego należy używać metody z rozwagą. 
     * Warto zauważyć, że jakkolwiek metoda powinna być bezpieczna względem 
     * obiektów <i>immutable</i>, to nie istnieje żaden powód, aby takie obiekty
     * kopiować. Potencjalne problemy, które mogą zaistnieć podczas kopiowania obiektów
     * o zmiennym stanie sprawadzają się do dwóch głównych kategorii: 
     * </p>
     * <ul>
     *      <li>poprawne działanie, ale utrata gwarancji thread-safe. </li>
     *      <li>niepoprawne zachowanie obiektu, ponieważ zakłada on, że nikt nie 
     *          modyfikuje jego składowych prywatnych poza nim
     *      </li>
     * </ul>
     * @param <T>
     * @param target
     * @param source
     * @return
     */
    public static <T> T copy(T target, Object source) {
        try {
            List<Class<?>> targetList = ClassInspector.getAncestors(target.getClass());
            List<Class<?>> sourceList = ClassInspector.getAncestors(source.getClass());
        
            for (Class<?> sourceClazz : sourceList) {
                for (Field input : sourceClazz.getDeclaredFields()) {
                    Field output = selectFor(targetList, input);
                    if (null != output) {
                        access(output).set(target, access(input).get(source));
                    }
                }
            }
        } catch (IllegalAccessException cause) {
            // not reachable under normal conditions
            throw new AssertionError(cause);
        }
        return target;
    }
    
    private static Field selectFor(List<Class<?>> clazzList, Field source) {
        String name = source.getName();
        for(Class<?> clazz : clazzList ) {
            try {
                Field target = clazz.getDeclaredField(name);
                if( isFinal(target) || isStatic(target)) {
                    continue;
                }
                if( target.getType().isAssignableFrom(source.getType()) ) { 
                    return access(target); 
                }
            }
            catch (NoSuchFieldException | SecurityException _e) {  // NOPMD
                // ignore and continue enumeration
            }
        }
        return null;
    }
    
}
