GUIManager.java

package it.fulminazzo.yagl;

import it.fulminazzo.fulmicollection.objects.Refl;
import it.fulminazzo.fulmicollection.structures.tuples.Tuple;
import it.fulminazzo.fulmicollection.utils.ReflectionUtils;
import it.fulminazzo.yagl.contents.GUIContent;
import it.fulminazzo.yagl.guis.GUI;
import it.fulminazzo.yagl.viewers.Viewer;
import org.bukkit.Bukkit;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * A general manager class that controls most {@link GUI} features.
 * It is completely independent and not required from the end user to be loaded or registered.
 */
public class GUIManager extends SingleInstance implements Listener {
    private final List<Viewer> viewers;

    /**
     * Instantiates a new GUI manager.
     */
    public GUIManager() {
        initialize();
        this.viewers = new ArrayList<>();
    }

    @EventHandler
    void on(final @NotNull InventoryClickEvent event) {
        getOpenGUIViewer(event.getWhoClicked()).ifPresent((v, g) -> {
            int slot = event.getRawSlot();
            if (slot < 0) g.clickOutsideAction().ifPresent(a -> a.execute(v, g));
            else if (slot < g.size()) {
                if (!g.isMovable(slot)) event.setCancelled(true);
                @Nullable GUIContent content = g.getContent(v, slot);
                if (content != null) content.clickItemAction().ifPresent(a -> a.execute(v, g, content));
            }
        });
    }

    @EventHandler
    void on(final @NotNull InventoryDragEvent event) {
        getOpenGUIViewer(event.getWhoClicked()).ifPresent((v, g) -> event.setCancelled(true));
    }

    @EventHandler
    void on(final @NotNull InventoryCloseEvent event) {
        GUIAdapter.closeGUI(getViewer(event.getPlayer()));
    }

    @EventHandler
    void on(final @NotNull PluginDisableEvent event) {
        JavaPlugin plugin = getProvidingPlugin();
        Plugin disablingPlugin = event.getPlugin();
        if (plugin.equals(disablingPlugin)) {
            this.viewers.stream()
                    .filter(Viewer::hasOpenGUI)
                    .map(Viewer::getUniqueId)
                    .map(Bukkit::getPlayer)
                    .filter(Objects::nonNull)
                    .forEach(HumanEntity::closeInventory);
            terminate();
        }
    }

    /**
     * Gets a {@link Tuple} with the corresponding {@link Viewer} and open {@link GUI} if present.
     *
     * @param player the player
     * @return the Tuple
     */
    public static @NotNull Tuple<Viewer, GUI> getOpenGUIViewer(final @NotNull HumanEntity player) {
        Viewer viewer = getViewer(player);
        if (viewer.hasOpenGUI()) return new Tuple<>(viewer, viewer.getOpenGUI());
        else return new Tuple<>();
    }

    /**
     * Gets a {@link Tuple} with the corresponding {@link Viewer} and open {@link GUI} if present.
     *
     * @param uuid the uuid
     * @return the Tuple
     */
    public static @NotNull Tuple<Viewer, GUI> getOpenGUIViewer(final @NotNull UUID uuid) {
        Viewer viewer = getViewer(uuid);
        if (viewer != null && viewer.hasOpenGUI()) return new Tuple<>(viewer, viewer.getOpenGUI());
        else return new Tuple<>();
    }

    /**
     * Gets the corresponding {@link Viewer} to the provided player.
     * If it is not present, it will be created.
     *
     * @param player the player
     * @return the viewer
     */
    public static @NotNull Viewer getViewer(final @NotNull HumanEntity player) {
        Viewer viewer = getViewer(player.getUniqueId());
        if (viewer == null) {
            viewer = newViewer(player);
            getInstance().viewers.add(viewer);
        }
        return viewer;
    }

    private static @NotNull Viewer newViewer(final @NotNull HumanEntity player) {
        final String packageName = Viewer.class.getPackage().getName();
        final Class<?> bukkitViewer = ReflectionUtils.getClass(packageName + ".BukkitViewer");
        Viewer viewer = new Refl<>(bukkitViewer).invokeMethod("newViewer", player);
        return Objects.requireNonNull(viewer);
    }

    /**
     * Gets the corresponding {@link Viewer} to the provided player.
     * If it is not present, it will be returned null.
     *
     * @param uuid the uuid
     * @return the viewer
     */
    public static @Nullable Viewer getViewer(final @NotNull UUID uuid) {
        return getInstance().viewers.stream()
                .filter(v -> v.getUniqueId().equals(uuid))
                .findFirst().orElse(null);
    }

    /**
     * Gets an instance of {@link GUIManager}.
     * If none is currently loaded, it will be created.
     *
     * @return the instance
     */
    public static GUIManager getInstance() {
        try {
            return getInstance(GUIManager.class);
        } catch (InstanceNotInitializedException e) {
            GUIManager manager = new GUIManager();
            Bukkit.getPluginManager().registerEvents(manager, getProvidingPlugin());
            return manager;
        }
    }

    private static @NotNull JavaPlugin getProvidingPlugin() {
        return JavaPlugin.getProvidingPlugin(GUIManager.class);
    }
}