package wtf.opal.event.registry;

import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import wtf.opal.event.listener.ListenerMethod;
import wtf.opal.event.subscriber.IEventSubscriber;
import wtf.opal.event.subscriber.Subscribe;

@Environment(EnvType.CLIENT)
public final class EventRegistry {
   private final Map<Class<?>, List<ListenerMethod>> subscriberMap = new HashMap<>();
   private static final Lookup LOOKUP = MethodHandles.lookup();

   private void subscribe(Object instance, Class<?> clazzOwner) {
      IEventSubscriber var3 = (IEventSubscriber)instance;

      for (Method var7 : clazzOwner.getDeclaredMethods()) {
         Subscribe var8 = var7.getDeclaredAnnotation(Subscribe.class);
         if (var8 != null && !Modifier.isStatic(var7.getModifiers())) {
            Class var9 = var7.getParameterTypes()[0];
            MethodType var10 = MethodType.methodType(void.class, var9);

            try {
               Lookup var11 = Modifier.isPrivate(var7.getModifiers()) ? MethodHandles.privateLookupIn(clazzOwner, LOOKUP) : LOOKUP;
               MethodHandle var12 = var11.findVirtual(clazzOwner, var7.getName(), var10);
               ConstantCallSite var13 = new ConstantCallSite(var12);
               ListenerMethod var14 = new ListenerMethod(var8.priority(), var13, var3);
               this.subscriberMap.computeIfAbsent(var9, x -> new ArrayList<>()).add(var14);
            } catch (NoSuchMethodException | IllegalAccessException var15) {
               throw new RuntimeException("Error subscribing event: " + var7.getName(), var15);
            }
         }
      }
   }

   public void subscribe(Object subscriber) {
      this.subscribe(subscriber, subscriber.getClass());

      for (Class var2 = subscriber.getClass().getSuperclass(); var2 != Object.class; var2 = var2.getSuperclass()) {
         this.subscribe(subscriber, var2);
      }

      this.sortSubscribers();
   }

   private void sortSubscribers() {
      for (List var2 : this.subscriberMap.values()) {
         var2.sort(Comparator.comparingInt(ListenerMethod::getPriority));
      }
   }

   public void dispatch(Object event) {
      List var2 = this.subscriberMap.get(event.getClass());
      if (var2 != null) {
         for (int var3 = 0; var3 < var2.size(); var3++) {
            ListenerMethod var4 = (ListenerMethod)var2.get(var3);
            if (var4.invoke(event)) {
               break;
            }
         }
      }
   }
}
