/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.trace;

import net.ranides.assira.collection.lists.ListUtils;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.generic.Ref;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * This is default implementation of IStackWalker class.
 *
 * It uses Exception class for inspection, which is very slow, but it is compatible with every JVM.
 *
 * See: {@code net.ranides.assira.trace.FStackWalker8}
 * See: {@code net.ranides.assira.trace.FStackWalker11}
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
final class FStackWalker6 implements TraceUtils.IStackWalker {

    final SecurityResolver RESOLVER_SECURITY = getSecurityResolver();

    final ExceptionResolver RESOLVER_EXCEPT = new ExceptionResolver();

    final String NO_SECURE_RESOLVER = "Can't access types because of security restrictions";

    @Override
    public List<StackTraceElement> getFrames() {
        return RESOLVER_EXCEPT.frames();
    }

    @Override
    public List<Class<?>> getTypes() {
        if(RESOLVER_SECURITY == null) {
            throw new UnsupportedOperationException(NO_SECURE_RESOLVER);
        }
        return RESOLVER_SECURITY.types();
    }

    @Override
    public List<String> getNames() {
        return RESOLVER_EXCEPT.names();
    }

    @Override
    public Ref<StackTraceElement> getFrame(Predicate<StackTraceElement> filter) {
        return CQuery.from().list(RESOLVER_EXCEPT.frames()).filter(filter).first();
    }

    @Override
    public Ref<Class<?>> getType(Predicate<Class<?>> filter) {
        if(RESOLVER_SECURITY == null) {
            throw new UnsupportedOperationException(NO_SECURE_RESOLVER);
        }
        return CQuery.from().list(RESOLVER_SECURITY.types()).filter(filter).first();
    }

    @Override
    public Ref<String> getName(Predicate<String> filter) {
        return CQuery.from().list(RESOLVER_EXCEPT.names()).filter(filter).first();
    }

    @Override
    public StackTraceElement getFrame(int index) {
        return RESOLVER_EXCEPT.frame(index);
    }

    @Override
    public Class<?> getType(int index) {
        if(RESOLVER_SECURITY == null) {
            throw new UnsupportedOperationException(NO_SECURE_RESOLVER);
        }
        return RESOLVER_SECURITY.type(index);
    }

    @Override
    public String getName(int index) {
        return RESOLVER_EXCEPT.name(index);
    }

    private static SecurityResolver getSecurityResolver() {
        try {
            return new SecurityResolver();
        } catch(SecurityException se) {
            return null;
        }
    }

    private static <T> List<T> tail(int index, List<T> list) {
        return list.subList(index, list.size());
    }

    private static <T,R> List<R> tail(int index, Function<T,R> map, List<T> list) {
        return ListUtils.map(list, map).subList(index, list.size());
    }

    private static final class SecurityResolver extends SecurityManager {

        private static final int FIRST = 3;

        public Class<?> type(int index) {
            return super.getClassContext()[FIRST+index];
        }

        public String name(int index) {
            return super.getClassContext()[FIRST+index].getName();
        }

        public List<Class<?>> types() {
            return tail(FIRST, Arrays.asList(super.getClassContext()));
        }

        public List<String> names() {
            return tail(FIRST, Class::getName, Arrays.asList(super.getClassContext()));
        }

    }

    private static final class ExceptionResolver {

        private static final int FIRST = 3;

        public List<String> names() {
            return tail(FIRST, StackTraceElement::getClassName, Arrays.asList(new Exception().getStackTrace()));
        }

        public String name(int index) {
            return new Exception().getStackTrace()[FIRST+index].getClassName();
        }

        public List<StackTraceElement> frames() {
            return tail(FIRST, Arrays.asList(new Exception().getStackTrace()));
        }

        public StackTraceElement frame(int index) {
            return new Exception().getStackTrace()[FIRST+index];
        }

    }

}
