package dev.crucifixbeta.api.events.eventbus;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;

@Environment(EnvType.CLIENT)
public class EventBus implements IEventBus {
   public final Map<Class<?>, List<IListener>> listenerMap = new ConcurrentHashMap<>();
   private final Map<Object, List<IListener>> listenerCache = new ConcurrentHashMap<>();
   private final Map<Class<?>, List<IListener>> staticListenerCache = new ConcurrentHashMap<>();
   private final List<EventBus.LambdaFactoryInfo> lambdaFactoryInfos = new ArrayList<>();

   @Override
   public void registerLambdaFactory(LambdaListener.Factory factory) {
      List var2 = this.lambdaFactoryInfos;
      synchronized (var2) {
         this.lambdaFactoryInfos.add(new EventBus.LambdaFactoryInfo(factory));
      }
   }

   @Override
   public <T> T post(T event) {
      List<IListener> var2 = this.listenerMap.get(event.getClass());
      if (var2 != null) {
         for (IListener var4 : var2) {
            var4.call(event);
         }
      }

      return (T)event;
   }

   @Override
   public <T extends ICancellable> T post(T event) {
      List<IListener> var2 = this.listenerMap.get(event.getClass());
      if (var2 != null) {
         event.setCancelled(false);

         for (IListener var4 : var2) {
            var4.call(event);
            if (event.isCancelled()) {
               break;
            }
         }
      }

      return (T)event;
   }

   @Override
   public void subscribe(Object object) {
      this.subscribe(this.getListeners(object.getClass(), object), false);
   }

   @Override
   public void subscribe(Class<?> klass) {
      this.subscribe(this.getListeners(klass, null), true);
   }

   @Override
   public void subscribe(IListener listener) {
      this.subscribe(listener, false);
   }

   private void subscribe(List<IListener> listeners, boolean onlyStatic) {
      for (IListener var4 : listeners) {
         this.subscribe(var4, onlyStatic);
      }
   }

   private void subscribe(IListener listener, boolean onlyStatic) {
      if (onlyStatic) {
         if (listener.isStatic()) {
            this.insert(this.listenerMap.computeIfAbsent(listener.getTarget(), aClass -> new CopyOnWriteArrayList<>()), listener);
         }
      } else {
         this.insert(this.listenerMap.computeIfAbsent(listener.getTarget(), aClass -> new CopyOnWriteArrayList<>()), listener);
      }
   }

   private void insert(List<IListener> listeners, IListener listener) {
      int var3 = 0;

      while (var3 < listeners.size() && listener.getPriority() <= ((IListener)listeners.get(var3)).getPriority()) {
         var3++;
      }

      listeners.add(var3, listener);
   }

   @Override
   public void unsubscribe(Object object) {
      this.unsubscribe(this.getListeners(object.getClass(), object), false);
   }

   @Override
   public void unsubscribe(Class<?> klass) {
      this.unsubscribe(this.getListeners(klass, null), true);
   }

   @Override
   public void unsubscribe(IListener listener) {
      this.unsubscribe(listener, false);
   }

   private void unsubscribe(List<IListener> listeners, boolean staticOnly) {
      for (IListener var4 : listeners) {
         this.unsubscribe(var4, staticOnly);
      }
   }

   private void unsubscribe(IListener listener, boolean staticOnly) {
      List var3 = this.listenerMap.get(listener.getTarget());
      if (var3 != null) {
         if (staticOnly) {
            if (listener.isStatic()) {
               var3.remove(listener);
            }
         } else {
            var3.remove(listener);
         }
      }
   }

   private List<IListener> getListeners(Class<?> klass, Object object) {
      Function var3 = o -> {
         CopyOnWriteArrayList var4 = new CopyOnWriteArrayList();
         this.getListeners(var4, klass, object);
         return var4;
      };
      if (object == null) {
         return this.staticListenerCache.computeIfAbsent(klass, k -> {
            CopyOnWriteArrayList var3x = new CopyOnWriteArrayList();
            this.getListeners(var3x, klass, null);
            return var3x;
         });
      } else {
         for (Object var5 : this.listenerCache.keySet()) {
            if (var5 == object) {
               return this.listenerCache.get(object);
            }
         }

         List var6 = (List)var3.apply(object);
         this.listenerCache.put(object, var6);
         return var6;
      }
   }

   private void getListeners(List<IListener> listeners, Class<?> klass, Object object) {
      for (Method var7 : klass.getDeclaredMethods()) {
         if (this.isValid(var7)) {
            listeners.add(new LambdaListener(this.getLambdaFactory(klass), klass, object, var7));
         }
      }

      if (klass.getSuperclass() != null) {
         this.getListeners(listeners, klass.getSuperclass(), object);
      }
   }

   private boolean isValid(Method method) {
      if (!method.isAnnotationPresent(EventListener.class)) {
         return false;
      } else if (method.getReturnType() != void.class) {
         return false;
      } else {
         return method.getParameterCount() != 1 ? false : !method.getParameters()[0].getType().isPrimitive();
      }
   }

   private LambdaListener.Factory getLambdaFactory(Class<?> klass) {
      List var2 = this.lambdaFactoryInfos;
      synchronized (var2) {
         Iterator var4 = this.lambdaFactoryInfos.iterator();
         if (var4.hasNext()) {
            EventBus.LambdaFactoryInfo var5 = (EventBus.LambdaFactoryInfo)var4.next();
            return var5.factory;
         }
      }

      throw new NoLambdaFactoryException(klass);
   }

   @Environment(EnvType.CLIENT)
   private static record LambdaFactoryInfo(LambdaListener.Factory factory) {
   }
}
