package core.config;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Optional;

/**
 * Factory実装
 *
 * @author Tadashi Nakayama
 */
public class FactoryImpl extends Factory {

	/**
	 * @see core.config.Factory#getClassInstance(java.lang.Class, java.lang.Class)
	 */
	@Override
	protected <T, U> T getClassInstance(final Class<T> cls, final Class<U> caller) {
		final var name = Factory.getFromEnv(cls, caller);
		final Class<T> c;
		if (name.isEmpty()) {
			if (cls.isInterface() || Modifier.isAbstract(cls.getModifiers())) {
				c = Factory.loadClass(addImpl(cls.getName()));
				if (c == null && cls.isInterface()) {
					return proxyInstance(cls);
				}
			} else {
				c = cls;
			}
		} else {
			c = Factory.loadClass(name);
		}
		final T obj = Factory.invoke(null, Factory.getMethod(c, "getInstance"));
		return Optional.ofNullable(obj).orElse(Factory.toInstance(c));
	}

	/**
	 * Proxyインスタンス作成
	 *
	 * @param <T> ジェネリックス
	 * @param cls クラス
	 * @return インスタンス
	 */
	private static <T> T proxyInstance(final Class<T> cls) {
		return Factory.cast(Proxy.newProxyInstance(
			Factory.getClassLoader(), new Class<?>[]{cls}, (p, m, a) -> {
				if (Modifier.isStatic(m.getModifiers())) {
					return m.invoke(null, a);
				} else if (!Modifier.isAbstract(m.getModifiers())) {
					final var lookup = MethodHandles.privateLookupIn(cls, MethodHandles.lookup());
					final var handle = lookup.unreflectSpecial(m, cls);
					return handle.bindTo(p).invokeWithArguments(a);
				} else if ("iterator".equals(m.getName()) && m.getParameterCount() == 0) {
					return Arrays.stream(a).iterator();
				}
				return null;
			}
		));
	}
}
