package net.ranides.assira.reflection.util;

import net.ranides.assira.trace.FStackWalker11;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;

public class FReflectSupport11 implements ReflectUtils.IReflectSupport {

    private final FStackWalker11 walker = new FStackWalker11();

    @Override
    public boolean isStable() {
        return true;
    }

    @Override
    public boolean isAccessible(Member object) {
        return object != null && checkCanSetAccessible(object, walker.getType(0), object.getDeclaringClass());
    }

    @Override
    public <T extends AccessibleObject> T access(T object) {
        object.trySetAccessible();
        return object;
    }

    private boolean checkCanSetAccessible(Member that, Class<?> caller, Class<?> declaringClass) {
        // semantic from JDK 11
        //      java.lang.reflect.AccessibleObject.checkCanSetAccessible
        //      java.lang.reflect.AccessibleObject.logIfOpenedForIllegalAccess

        final Module callerModule = caller.getModule();
        final Module declaringModule = declaringClass.getModule();

        if (callerModule == declaringModule) {
            return true;
        }
        if (callerModule == Object.class.getModule()) {
            return true;
        }
        if (!declaringModule.isNamed()) {
            return true;
        }


        String pn = declaringClass.getPackageName();
        final boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());

        // class is public and package is exported to caller
        if (isClassPublic && declaringModule.isExported(pn, callerModule)) {
            final int modifiers = that.getModifiers();

            // member is public
            if (Modifier.isPublic(modifiers)) {
                return true;
            }

            // member is protected-static
            if (Modifier.isProtected(modifiers)
                && Modifier.isStatic(modifiers)
                && isSubclassOf(caller, declaringClass)) {
                return true;
            }
        }

        // package is <<legally>> open to caller
        if (declaringModule.isOpen(pn, callerModule) && callerModule.isNamed()) {
            return true;
        }

        return false;
    }

    private boolean isSubclassOf(Class<?> queryClass, Class<?> ofClass) {
        while (queryClass != null) {
            if (queryClass == ofClass) {
                return true;
            }
            queryClass = queryClass.getSuperclass();
        }
        return false;
    }
}
