package com.github.phantomthief.util;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

import javax.annotation.Nullable;

import org.apache.commons.lang3.StringUtils;

/**
 * @author w.vela
 * Created on 2019-08-01.
 */
class StackTraceProviderJdk8 implements StackTraceProvider {

    static final String[] REFLECTION_PREFIXES = {"sun.reflect.", "java.lang.reflect.", "jdk.internal.reflect."};

    @Nullable
    @Override
    public StackTraceElement getCallerPlace(Predicate<String> locationAwareClassChecker, Predicate<String> ignore) {
        boolean afterSelf = false;
        boolean afterDeprecated = false;
        String deprecatedClass = null;
        // 之所以用异常获取而不是Thread.currentThread().getStackTrace(),是因为它内部实现其实也是判断当前线程了
        for (StackTraceElement stack : (new Exception()).getStackTrace()) {
            String stackClassName = stack.getClassName();
            if (isReflection(stackClassName)) {
                continue;
            }
            if (locationAwareClassChecker.test(stackClassName)) {
                afterSelf = true;
                continue;
            }
            if (afterSelf) {
                if (deprecatedClass == null) {
                    deprecatedClass = stackClassName;
                }
            }
            if (stackClassName.equals(deprecatedClass)) {
                afterDeprecated = true;
                continue;
            }
            if (afterDeprecated) {
                if (ignore.test(stackClassName)) {
                    continue;
                }
                return stack;
            }
        }
        return null;
    }

    /**
     * at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     * at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
     * at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
     * at java.lang.reflect.Method.invoke(Method.java:498)
     */
    private boolean isReflection(String stackClassName) {
        return StringUtils.startsWithAny(stackClassName, REFLECTION_PREFIXES);
    }

    private Set<String> toString(Class<?>[] locationAwareClasses) {
        int length = locationAwareClasses.length;
        if (length == 1) { // optimize
            return Collections.singleton(locationAwareClasses[0].getName());
        }
        Set<String> result = new HashSet<>(length);
        for (Class<?> locationAwareClass : locationAwareClasses) {
            result.add(locationAwareClass.getName());
        }
        return result;
    }
}