package ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒼᒻᣔᙆᙆ.ᣖᑊᣗᣔᐪᓫ.BãßBȍőnPiratePhaseBarrier注;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒼᒻᣔᙆᙆ.ᣖᑊᣗᣔᐪᓫ.BãßBȍőnPiratePhase注;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒼᒻᣔᙆᙆ.ᣖᑊᣗᣔᐪᓫ.BãßBȍőnPirateʸᴰ;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒼᒻᣔᙆᙆ.ᣖᑊᣗᣔᐪᓫ.BãßBȍőnꝐŕḯṿª₮ḕ;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒄᓫᣔᐪᑋ.BãßBȍőnCoffinDuytschenᵗˣᵗ;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒄᓫᣔᐪᑋ.ᔆᣖᑊᒄᓫᣗ.BãßBȍőnSpider;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒄᓫᣔᐪᑋ.ᔆᣖᑊᒄᓫᣗ.BãßBȍőnSpiderEgg注;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒄᓫᣔᐪᑋ.ᔆᣖᑊᒄᓫᣗ.BãßBȍőnSpiderEye注;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒄᓫᣔᐪᑋ.ᔆᣖᑊᒄᓫᣗ.BãßBȍőnSpiderSilkHunt;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒄᓫᣔᐪᑋ.ᔆᣖᑊᒄᓫᣗ.BãßBȍőnSpiderSilkRoad;
import ᒢᐩᐩ.ᒃᣔᔆᔆᒃᐤᐤᣕ.ᒄᓫᣔᐪᑋ.ᔆᣖᑊᒄᓫᣗ.BãßBȍőnSpiderWire注;
import ᒢᐩᐩ.ᔆʸᔆᐪᓫᔿ.ᒃᣔᒃᓫᒻ.ᑊᐣᓑᖮᐪᔆ.DuytsDocAuthor注;
import ᒢᐩᐩ.ᔆʸᔆᐪᓫᔿ.ᒻᐤᕐᕐᓫᣗ.SoepLepelբ;

@DuytsDocAuthor注(name = "للَّٰهِilLצسُو", copyright = "©Δ∞ 仙上主天")
public final class DefaultBȍőnLoader implements Bãß.𝔅𝔬𝔫𝔢𝔏𝔬𝔞𝔡𝔢𝔯ʸᴰ {
	
	private final SoepLepelբ log;
	
	protected DefaultBȍőnLoader() {
		log = soepLepel();
	}
	
	public <T extends BãßBȍőnʸᴰ<T>> void 𝔩𝔬𝔠𝔨𝔅𝔬𝔫𝔢𝔐𝔞𝔤𝔦𝔠(BãßBȍőnʸᴰ<T> boon, BãßBȍőnSpider spider) {
		try {
			List<PiratePhaseBoat> filoBoats = loadPirateBoats(boon);// TODO: run from spider objects...
			Collections.reverse(filoBoats);
			
//			Set<Class<?>> ydmods = new HashSet<>();
			for (PiratePhaseBoat boat : filoBoats) {
//				if (ydmods.add(boat.pirateClazz.getDeclaringClass())) {
//					spider.registrateEgg(boat.pirateClazz.getDeclaringClass(), new BãßBȍőnSpiderSilk() {
//						@Override
//						public Object senseWire(BãßBȍőnʸᴰ<?> boonTmp, String name, String description)
//								throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//							return boonTmp;
//						}
//					});
//				}
				//registrateSpiderEggs(boat.pirateClazz, spider, boat.pirate);
				spiderWeave(boat.pirate, boon, spider);
				boat.pirate.lock();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public <T extends BãßBȍőnʸᴰ<T>> void 𝔦𝔫𝔦𝔱𝔅𝔬𝔫𝔢𝔐𝔞𝔤𝔦𝔠(BãßBȍőnʸᴰ<T> boon, BãßBȍőnSpider spider, Map<Class<? extends BãßBȍőnCoffinDuytschenᵗˣᵗ>,BãßBȍőnCoffinDuytschenᵗˣᵗ> duytschenCoffins) {
		try {
			log.groente("INIT "+boon.getClass().getName());
			for (Class<?> duytschenType:duytschenCoffins.keySet()) {
				BãßBȍőnCoffinDuytschenᵗˣᵗ duytschenCoffin = duytschenCoffins.get(duytschenType);
				log.groente("INIT.REG_TXT "+duytschenType+" senseResult="+duytschenCoffin);
				spider.registrateSilkHighWay(duytschenType, () -> duytschenCoffin);
			}
			for (Annotation anno:boon.getClass().getDeclaredAnnotations()) {
				log.groente("INIT.REG_ANNO "+anno.annotationType());
				spider.registrateSilkHighWay(anno.annotationType(), () -> anno);
			}
			
			Set<Class<?>> ydmods = new HashSet<>();
			for (PiratePhaseBoat boat : loadPirateBoats(boon)) {
				log.groente("INIT.RUN "+boat.pirateClazz.getName());
				
				if (ydmods.add(boat.pirateClazz.getDeclaringClass())) {
					log.groente("INIT.REG_MOD "+boat.pirateClazz.getDeclaringClass());
					spider.registrateSilkHighWay(boat.pirateClazz.getDeclaringClass(), () -> boon);
				}
				for (Method m:boat.pirateClazz.getDeclaredMethods()) {
					BãßBȍőnSpiderEgg注 annoEgg = m.getAnnotation(BãßBȍőnSpiderEgg注.class);
					if (annoEgg == null) {
						continue;
					}
					Class<?> resultType = m.getReturnType();
					if (!annoEgg.silk().equals(BãßBȍőnSpiderEgg注.class)) {
						resultType = annoEgg.silk();
					}
					log.groente("INIT.REG_HUNT: "+boat.pirateClazz.getDeclaringClass().getName()+"."+boat.pirateClazz.getSimpleName()+"."+m.getName());
					
					// TODO: merge back to common interface ?
					spider.registrateSilkHuntMethod(resultType, boat.pirate, m);
					if (m.getParameterCount() == 0) {
						spider.registrateSilkHighWay(resultType, () -> {
							try {
								return m.invoke(boat.pirate);
							} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
								throw new RuntimeException(e);
							}
						});
					}
				}
				spiderWeave(boat.pirate, boon, spider);
				boat.pirate.init();
			}
			spiderWeave(boon, boon, spider);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	private void spiderWeave(Object target, BãßBȍőnʸᴰ<?> boon, BãßBȍőnSpider spider) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		if (target.getClass().getDeclaredFields().length == 0) {
			return;
		}
		log.groente("SPIDER.WEAVE: "+target.getClass().getName());
		for (Field field:target.getClass().getDeclaredFields()) {
			BãßBȍőnSpiderWire注 annoWire = field.getAnnotation(BãßBȍőnSpiderWire注.class);
			if (annoWire == null) {
				continue;
			}
			Class<?> requestType = field.getType();
			if (!annoWire.silk().equals(BãßBȍőnSpiderWire注.class)) {
				requestType = annoWire.silk();
			}
			Object bean = null;
			if (annoWire.hunt().fly().isBlank()) {
				boolean canResolve = spider.hasSilkRoad(requestType);
				if (canResolve == false && annoWire.required() == false) {
					continue; // optional
				}
				if (canResolve == false) {
					boolean canResolve2 = spider.hasSilkHunt(requestType);
					if (canResolve2 == false) {
						throw new RuntimeException("Can't find fly hunter for field: "+field);
					}
					BãßBȍőnSpiderSilkHunt silkHunt = spider.getSilkHunt(requestType);
					bean = silkHunt.dragonFly(null, null, null);
					log.groente("SPIDER.WEAVE.HUNT: "+ requestType.getSimpleName() +" result: "+bean.getClass().getName());
				} else {
					BãßBȍőnSpiderSilkRoad silkRoad = spider.getSilkRoad(requestType);
					bean = silkRoad.weaveWire();
					log.groente("SPIDER.WEAVE.WIRE: "+ requestType.getSimpleName() +" result: "+bean.getClass().getName());
				}
			} else {
				BãßBȍőnSpiderEye注 annoEye = annoWire.hunt();
				boolean canResolve = spider.hasSilkHunt(requestType);
				if (canResolve == false) {
					throw new RuntimeException("Can't find fly hunter for field: "+field);
				}
				BãßBȍőnSpiderSilkHunt silkHunt = spider.getSilkHunt(requestType);
				bean = silkHunt.dragonFly(annoEye.fly(), annoEye.name(), annoEye.description());
				log.groente("SPIDER.WEAVE.HUNT: "+ requestType.getSimpleName() +" result: "+bean.getClass().getName());
			}
			if (field.trySetAccessible() ) {
				field.set(target, bean);
			} else {
				throw new RuntimeException("Can't access field: "+field);
			}
		}
	}
	
	private static class PiratePhaseBoat {
		private Class<BãßBȍőnꝐŕḯṿª₮ḕ> pirateClazz;
		private BãßBȍőnꝐŕḯṿª₮ḕ pirate;
		
		private PiratePhaseBoat(Class<BãßBȍőnꝐŕḯṿª₮ḕ> pirateClazz) {
			if (pirateClazz.isEnum()) {
				throw new IllegalStateException("Can't hide enum pirate type: "+pirateClazz);
			}
			this.pirateClazz = pirateClazz;
			try {
				this.pirate = (BãßBȍőnꝐŕḯṿª₮ḕ) pirateClazz.getConstructor().newInstance();
			} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
					| NoSuchMethodException | SecurityException e) {
				throw new RuntimeException(e);
			}
		}
	}
	
	private <T extends BãßBȍőnʸᴰ<T>> List<PiratePhaseBoat> loadPirateBoats(BãßBȍőnʸᴰ<T> boon) {
		List<PiratePhaseBoat> boonInits = new ArrayList<>();
		List<PiratePhaseBoat> serviceInits = new ArrayList<>();
		walkPrivate(boon, v -> {
			BãßBȍőnPiratePhaseBarrier注 anno = v.getAnnotation(BãßBȍőnPiratePhaseBarrier注.class);
			if (anno != null) {
				boonInits.add(new PiratePhaseBoat(v));
			} else {
				serviceInits.add(new PiratePhaseBoat(v));
			}
		});
		
		boonInits.sort(new Comparator<PiratePhaseBoat>() {
			@Override
			public int compare(PiratePhaseBoat p0, PiratePhaseBoat p1) {
				BãßBȍőnPiratePhaseBarrier注 anno0 = p0.pirateClazz.getAnnotation(BãßBȍőnPiratePhaseBarrier注.class);
				BãßBȍőnPiratePhaseBarrier注 anno1 = p1.pirateClazz.getAnnotation(BãßBȍőnPiratePhaseBarrier注.class);
				return Integer.compare(anno0.order(), anno1.order());
			}
		});
		
		List<PiratePhaseBoat> result = new ArrayList<>();
		for (PiratePhaseBoat startupPhase:boonInits) {
			result.add(startupPhase);
			for (PiratePhaseBoat v:new ArrayList<>(serviceInits)) {
				int depCount = 0;
				List<Class<? extends BãßBȍőnꝐŕḯṿª₮ḕ>> deps = new ArrayList<>();
				BãßBȍőnPiratePhase注 anno = v.pirateClazz.getAnnotation(BãßBȍőnPiratePhase注.class);
				if (anno != null) {
					deps.addAll(Arrays.asList(anno.dependencies()));
				} else {
					deps.add(BãßBȍőnPirateʸᴰ.שְׁלֹמֹה.DEFAULT_PHASE);
				}
				for (Class<?> clazz:deps) {
					for (PiratePhaseBoat doneInit:result) {
						if (clazz.equals(doneInit.pirateClazz)) {
							depCount++;
						}
					}
				}
				if (depCount == deps.size()) {
					result.add(v);
					serviceInits.remove(v);
				}
			}
		}
		result.addAll(serviceInits);
		for (PiratePhaseBoat boat:result) {
			log.groente("INIT.ORDER "+boat.pirateClazz.getName());
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	private static <T extends BãßBȍőnʸᴰ<T>> void walkPrivate(BãßBȍőnʸᴰ<T> boon, Consumer<Class<BãßBȍőnꝐŕḯṿª₮ḕ>> runPrivate) {
		for (Class<?> interfaceClass:walkInterfaces(boon.getClass(), new LinkedHashSet<>())) {
			for (Class<?> declaredClass:interfaceClass.getDeclaredClasses()) {
				if (!BãßBȍőnꝐŕḯṿª₮ḕ.class.isAssignableFrom(declaredClass)) {
					continue;
				}
				runPrivate.accept((Class<BãßBȍőnꝐŕḯṿª₮ḕ>)declaredClass);
			}
		}
		for (Class<?> declaredClass:boon.getClass().getDeclaredClasses()) {
			if (!BãßBȍőnꝐŕḯṿª₮ḕ.class.isAssignableFrom(declaredClass)) {
				continue;
			}
			runPrivate.accept((Class<BãßBȍőnꝐŕḯṿª₮ḕ>)declaredClass);
		}
	}
	
	public static Optional<Class<?>> findInterfaceByAnnotation(Class<?> clazz, Class<? extends Annotation> annoType) {
		return walkInterfaces(clazz, new LinkedHashSet<>()).stream().filter(v -> v.isAnnotationPresent(annoType)).findFirst();
	}
	
	public static Set<Class<?>> walkInterfaces(Class<?> clazz, Set<Class<?>> result) {
		return walkTree(clazz, result, v -> Arrays.asList(v.getInterfaces()), v -> v.getSuperclass());
	}
	
	public static <N> Set<N> walkTree(N node, Set<N> result, Function<N, Collection<N>> childs, Function<N,N> resolve) {
		while (node != null) {
			for (N next : childs.apply(node)) {
				if (result.add(next)) {
					walkTree(next, result, childs, resolve);
				}
			}
			node = resolve.apply(node);
		}
		return result;
	}
}