package com.btk5h.skriptmirror.skript.reflect;

import ch.njol.skript.Skript;
import ch.njol.skript.SkriptConfig;
import ch.njol.skript.lang.Literal;
import ch.njol.skript.lang.SkriptEvent;
import ch.njol.skript.lang.SkriptParser;
import com.btk5h.skriptmirror.LibraryLoader;
import com.btk5h.skriptmirror.SkriptMirror;
import com.btk5h.skriptmirror.WrappedEvent;
import org.bukkit.Bukkit;
import org.bukkit.event.*;
import org.bukkit.plugin.EventExecutor;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class EvtByReflection extends SkriptEvent {
  static {
    Skript.registerEvent("Bukkit Event", EvtByReflection.class, BukkitEvent.class,
        "[(1¦all)] %strings% [(at|on|with) priority <.+>]");
  }

  private static class PriorityListener implements Listener {
    private EventPriority priority;
    private Set<Class<? extends Event>> events = new HashSet<>();

    public PriorityListener(int priority) {
      this.priority = EventPriority.values()[priority];
    }

    public EventPriority getPriority() {
      return priority;
    }

    public Set<Class<? extends Event>> getEvents() {
      return events;
    }
  }

  private static EventExecutor executor =
      (listener, event) -> Bukkit.getPluginManager()
          .callEvent(new BukkitEvent(event, ((PriorityListener) listener).getPriority()));

  private static PriorityListener[] listeners;

  static {
    listeners = Arrays.stream(EventPriority.values())
        .mapToInt(EventPriority::ordinal)
        .mapToObj(PriorityListener::new)
        .toArray(PriorityListener[]::new);
  }

  private static class BukkitEvent extends WrappedEvent implements Cancellable {
    private final static HandlerList handlers = new HandlerList();

    private final EventPriority priority;

    public BukkitEvent(Event event, EventPriority priority) {
      super(event);
      this.priority = priority;
    }

    public EventPriority getPriority() {
      return priority;
    }

    public static HandlerList getHandlerList() {
      return handlers;
    }

    @Override
    public HandlerList getHandlers() {
      return handlers;
    }

    @Override
    public boolean isCancelled() {
      Event event = getEvent();
      return getEvent() instanceof Cancellable && ((Cancellable) event).isCancelled();
    }

    @Override
    public void setCancelled(boolean cancel) {
      Event event = getEvent();
      if (event instanceof Cancellable) {
        ((Cancellable) event).setCancelled(cancel);
      }
    }
  }

  private static void registerEvent(Class<? extends Event> event, EventPriority priority, boolean ignoreCancelled) {
    PriorityListener listener = listeners[priority.ordinal()];
    Set<Class<? extends Event>> events = listener.getEvents();

    if (!events.contains(event)) {
      events.add(event);
      Bukkit.getPluginManager()
          .registerEvent(event, listener, priority, executor, SkriptMirror.getInstance(), ignoreCancelled);
    }
  }

  private Class<? extends Event>[] classes;
  private EventPriority priority;

  @SuppressWarnings("unchecked")
  @Override
  public boolean init(Literal<?>[] args, int matchedPattern, SkriptParser.ParseResult parseResult) {
    String[] events = ((Literal<String>) args[0]).getArray();

    classes = (Class<? extends Event>[]) Array.newInstance(Class.class, events.length);

    for (int i = 0; i < events.length; i++) {
      String event = events[i];

      try {
        Class<?> eventClass = LibraryLoader.getClassLoader().loadClass(event);

        if (!Event.class.isAssignableFrom(eventClass)) {
          Skript.error(event + " is not an event.");
          return false;
        }

        classes[i] = (Class<? extends Event>) eventClass;
      } catch (ClassNotFoundException e) {
        Skript.error(event + " refers to a non-existent class.");
        return false;
      }
    }

    if (parseResult.regexes.size() > 0) {
      String priorityName = parseResult.regexes.get(0).group().toUpperCase();
      try {
        priority = EventPriority.valueOf(priorityName);
      } catch (IllegalArgumentException ex) {
        Skript.error(priorityName + " is not a valid priority level.");
        return false;
      }
    } else {
      priority = SkriptConfig.defaultEventPriority.value();
    }

    boolean ignoreCancelled = (parseResult.mark & 1) == 1;

    for (Class<? extends Event> cls : classes) {
      registerEvent(cls, priority, ignoreCancelled);
    }

    return true;
  }

  @Override
  public boolean check(Event e) {
    Class<? extends Event> eventClass = ((BukkitEvent) e).getEvent().getClass();
    if (priority == ((BukkitEvent) e).getPriority()) {
      for (Class<? extends Event> cls : classes) {
        if (cls == eventClass) {
          return true;
        }
      }
    }
    return false;
  }

  @Override
  public String toString(Event e, boolean debug) {
    return Arrays.toString(classes) + " priority " + priority;
  }
}

@@ -254,29 +254,24 @@
 tmirror.
-LibraryLoader
+JavaType
 ;%0Aimport
@@ -450,40 +450,8 @@
 r;%0A%0A
-import java.lang.reflect.Array;%0A
 impo
@@ -689,14 +689,16 @@
 )%5D %25
-string
+javatype
 s%25 %5B
@@ -3207,26 +3207,32 @@
     
-String%5B%5D events = 
+classes = Arrays.stream(
 ((Li
@@ -3237,22 +3237,24 @@
 Literal%3C
-String
+JavaType
 %3E) args%5B
@@ -3271,327 +3271,46 @@
 ay()
-;%0A
+)
 %0A    
-classes = (Class%3C? extends Event%3E%5B%5D) Array.newInstance(Class.class, events.length);%0A%0A    for (int i = 0; i %3C events.length; i++) %7B%0A      String event = events%5Bi%5D;%0A%0A      try %7B%0A        Class%3C?%3E eventClass = LibraryLoader.getClassLoader().loadClass(event);%0A%0A        if (!Event.class.isAssignableFrom(event
+    .map(JavaType::getJava
 Class)
-) %7B
 %0A   
@@ -3318,290 +3318,31 @@
     
-  Skript.error(event + %22 is not an event.%22);%0A          return false;%0A        %7D%0A%0A        classes%5Bi%5D = (Class%3C? extends Event%3E) eventClass;%0A      %7D catch (ClassNotFoundException e) %7B%0A        Skript.error(event + %22 refers to a non-existent class.%22);%0A        return false;%0A      %7D%0A    %7D
+.toArray(Class%5B%5D::new);
 %0A%0A  

Change custom events to use imported classes instead of strings