package cz.moneta.test.harness.support.reflect;

import cz.moneta.test.harness.exception.HarnessException;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

@SuppressWarnings("unchecked")
public final class ReflectionUtils {

    private ReflectionUtils() {
    }

    public static <T> T invokeDefaultMethod(Object proxy, Method method, Function<String, String> messageFormatSupplier, Object... args) {
        Class<?> declaringClass = method.getDeclaringClass();
        try {
            Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
            constructor.setAccessible(true);
            return (T) constructor.newInstance(declaringClass)
                    .in(declaringClass)
                    .unreflectSpecial(method, declaringClass)
                    .bindTo(proxy)
                    .invokeWithArguments(args);
        } catch (Throwable e) {
            throw new HarnessException(messageFormatSupplier.apply(e.getMessage()), e);
        }
    }

    public static <T> Optional<T> invokeDefaultMethodByName(String methodName, Object proxy, Function<String, String> messageSupplier) {
        return getInterfaces(proxy.getClass()).stream()
                .flatMap(i -> Arrays.stream(i.getMethods()))
                .filter(m -> methodName.equals(m.getName()))
                .filter(Method::isDefault)
                .findFirst()
                .map(m -> invokeDefaultMethod(proxy, m, messageSupplier));
    }

    public static List<Class<?>> getInterfaces(Class<?> clazz) {
        return doGetInterfaces(clazz, new ArrayList<>());
    }

    private static List<Class<?>> doGetInterfaces(Class<?> clazz, List<Class<?>> accumulator) {
        if (clazz.isInterface()) {
            accumulator.add(clazz);
        }
        Arrays.stream(clazz.getInterfaces()).forEach(c -> {
            accumulator.add(c);
            getInterfaces(c);
        });
        return accumulator;
    }
}
