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

import net.ranides.assira.collection.lists.ListUtils;
import net.ranides.assira.reflection.util.ClassTraits;
import net.ranides.assira.reflection.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class RCompareUtils {
    
    private RCompareUtils() {
        /* utility class */
    }
    
    public static int hashcode(IClass<?> iclass) {
        return iclass.name().hashCode();
    }
        
    public static int compare(IClass<?> a, IClass<?> b) {
        if(a == AClass.NULL) {
            return (AClass.NULL==b) ? 0 : -1;
        }
        if(b == AClass.NULL) {
            return +1;
        }
        if(isSuper(a, b)) {
            return isSuper(b, a) ? a.name().compareTo(b.name()) : -1;
        }
        if(isSuper(b, a)) {
            return +1;
        }
        return a.name().compareTo(b.name());
    }
    
    public static boolean equals(IClass<?> a, IClass<?> b) {
        return (a==b) || (isSubclass(a, b) && isSuper(a, b)); // NOPMD
    }
    
    public static boolean isEquivalent(IClass<?> a, IClass<?> b) {
        return (a==b) || (isSuper(true, false, b, a) && isSuper(true, false, a, b)); // NOPMD
    }
    
    public static boolean isSubclass(IClass<?> iclass, IClass<?> isuper) {
        return isSuper(false, false, isuper, iclass);
    }
    
    public static boolean isSuper(IClass<?> isuper, IClass<?> iclass) {
        return isSuper(false, false, isuper, iclass);
    }
    
    public static boolean isRawSubclass(IClass<?> iclass, IClass<?> isuper) {
        return isSuper(false, true, isuper, iclass);
    }
    
    public static boolean isRawSuper(IClass<?> isuper, IClass<?> iclass) {
        return isSuper(false, true, isuper, iclass);
    }
    
    public static boolean isInstance(IClass<?> type, Object object) {
        return isSuper(true, false, type, IClass.typefor(object));
    }
    
    // isuper.params().equals(iclass.params())
    //
    // to nam załatwia "wildcards:inside" bo tak naprawdę "inside" dla wildcardów to
    // jednoczesne super & subclass. O ile tylko dobrze je zaimplementujemy to MUSI 
    // działać prawidłowo. Jeśli nie działa, to znaczy, że mamy zaimplementowane źle.
    //
    // a to oznacza też, że subclass mamy z głowy, bo dobrze zrobione "super" MUSI
    // działać prawidłowo: anty-symetrycznie.
    //
    // pamiętać: RClass zachowouje się jak RWClass z upper-boundem
    // to nam da gratis dwie metody: rw-ac oraz ac-rw
    // wystarczy, że rw-rw prawidłowo zaimplementujemy.
    
    private static boolean isSuper(boolean box, boolean raw, IClass<?> isuper, IClass<?> iclass) {
        if(isuper == AClass.NULL) {
            return !iclass.attributes().contains(IAttribute.PRIMITIVE);
        }
        if(iclass == AClass.NULL) {
            return !isuper.attributes().contains(IAttribute.PRIMITIVE);
        }
        Bound sw = new Bound(isuper);
        Bound cw = new Bound(iclass);
        
        if(sw.wildcard || cw.wildcard) {
            return isSuperBound(sw, cw) && (raw||matchParams(isuper, iclass));
        }
        Class<?> sr = isuper.raw();
        Class<?> cr = iclass.raw();
        
        return isSuperRaw(box, sr, cr) && (raw||matchParams(isuper, iclass));
    }
    
    private static boolean isSuperRaw(boolean box, Class<?> isuper, Class<?> iclass) {
        return box ? ClassTraits.isSuper(isuper, iclass) : isuper.isAssignableFrom(iclass);
    }
    
    private static boolean isSuperBound(Bound isuper, Bound iclass) {
        if(isuper.lower.length > 0) {
            for(IClass<?> sc : isuper.lower) {
                if( isanysubclass(sc, iclass.upper) ) {
                    return true;
                }
            }
            return false;
        }
        if(iclass.lower.length > 0) {
            return isallobject(isuper.upper);
        }
        if( !isuper.wildcard ) {
            return isanysubclass(isuper.upper[0], iclass.upper);
        }
        for(IClass<?> ec : isuper.upper) {
            if( !isanysubclass(ec, iclass.lower)) {
                return false;
            }
        }
        return true;
    }
    
    private static boolean match(IClass<?> isuper, IClass<?> iclass) {
        Bound sw = new Bound(isuper);
        Bound cw = new Bound(iclass);
        if(sw.wildcard || cw.wildcard) {
            return matchBound(sw, cw);
        }
        Class<?> sr = isuper.raw();
        Class<?> cr = iclass.raw();
        return matchRaw(sr, cr) && matchParams(isuper, iclass);
    }
    
    private static boolean matchBound(Bound isuper, Bound iclass) {
        if(isuper.lower.length > 0) {
            if(iclass.wildcard) {
                return (iclass.lower.length > 0) && isSuper(iclass.lower[0], isuper.lower[0]);
            } 
            return isSuper(iclass.upper[0], isuper.lower[0]);
        }
        if(!isuper.wildcard) {
            return !iclass.wildcard && equals(isuper.upper[0], iclass.upper[0]);
        }
        for(IClass<?> sc : isuper.upper) {
            if( !isanysubclass(sc, iclass.upper) ) {
                return false;
            }
        }
        return true;
    }
    
    private static boolean matchRaw(Class<?> isuper, Class<?> iclass) {
        return isuper.isAssignableFrom(iclass);
    }
    
    private static boolean matchParams(IClass<?> isuper, IClass<?> iclass) {
        return ListUtils.equals(isuper.params(), iclass.params(), RCompareUtils::match);
    }
    
    private static boolean isanysubclass(IClass<?> isuper, IClass<?>[] list) {
        for(IClass<?> s : list) {
            if( isSubclass(s, isuper)) {
                return true;
            }
        }
        return false;
    }
    
    private static boolean isallobject(IClass<?>[] list) {
        for(IClass<?> s : list) {
            if( !RClass.OBJECT.equals(s) ) {
                return false;
            }
        }
        return true;
    }
    
    private static class Bound {

        private static final IClass<?>[] EMPTY = new IClass<?>[0];

        final IClass<?>[] lower;
        final IClass<?>[] upper;
        final boolean wildcard;

        public Bound(IClass<?> type) {
            if(type instanceof RFClass) {
                type = ((RFClass)type).$itype();
            }
            if(type instanceof RWClass) {
                this.wildcard = true;
                this.lower = ((RWClass)type).lower();
                this.upper = ((RWClass)type).upper();
            } else {
                this.wildcard = false;
                this.lower = EMPTY;
                this.upper = new IClass<?>[]{type};
            }
        }

    }
    
}
