/*
 * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
 *
 * Copyright (c) JCThePants (www.jcwhatever.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


package com.jcwhatever.nucleus.internal.managed.scripting.api;

import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.collections.observer.subscriber.SubscriberLinkedList;
import com.jcwhatever.nucleus.mixins.IDisposable;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.observer.ISubscriber;
import com.jcwhatever.nucleus.utils.observer.event.EventSubscriberPriority;
import com.jcwhatever.nucleus.utils.observer.script.IScriptEventSubscriber;
import com.jcwhatever.nucleus.utils.observer.script.ScriptEventSubscriber;
import com.jcwhatever.nucleus.utils.text.TextUtils;

import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

/**
 * Provide scripts with an event registration API
 */
public class SAPI_Events implements IDisposable {

    private final Plugin _plugin;
    private final List<RegisteredBukkitEvent> _registeredBukkit = new ArrayList<>(10);
    private final Deque<ISubscriber> _subscribers = new SubscriberLinkedList<>();
    private boolean _isDisposed;

    public SAPI_Events(Plugin plugin) {
        _plugin = plugin;
    }

    @Override
    public boolean isDisposed() {
        return _isDisposed;
    }

    @Override
    public void dispose() {

        // unregister Bukkit event handlers
        for (RegisteredBukkitEvent registered : _registeredBukkit) {
            registered._handlerList.unregister(registered._registeredListener);
        }
        _registeredBukkit.clear();

        // unregister nucleus event handlers
        while (!_subscribers.isEmpty()) {
            ISubscriber subscriber = _subscribers.remove();
            subscriber.dispose();
        }

        _isDisposed = true;
    }

    /**
     * Registers an event handler.
     *
     * @param eventName  The event class name.
     * @param priority   The event priority as a string. Can specify "invokeForCancelled".
     *                   ie. "NORMAL:invokeForCancelled". By default, handlers are not invoked if
     *                   event is cancelled by another handler.
     * @param handler    The event handler.
     *
     * @return True if successfully registered.
     */
    public boolean on(String eventName, String priority, final IScriptEventSubscriber handler) {
        PreCon.notNullOrEmpty(eventName);
        PreCon.notNullOrEmpty(priority);
        PreCon.notNull(handler);

        String[] priorityComp = TextUtils.PATTERN_COLON.split(priority);
        boolean ignoreCancelled = true;

        if (priorityComp.length == 2) {
            if (priorityComp[1].equalsIgnoreCase("invokeForCancelled")) {
                ignoreCancelled = false;
                priority = priorityComp[0];
            }
        }

        Class<?> eventClass;

        try {
            eventClass = Class.forName(eventName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return false;
        }

        // check for and register bukkit events
        if (Event.class.isAssignableFrom(eventClass)) {
            Class<? extends Event> bukkitEventClass = eventClass.asSubclass(Event.class);
            return registerBukkitEvent(bukkitEventClass, priority, ignoreCancelled, handler);
        }
        else {
            return registerNucleusEvent(eventClass, priority, ignoreCancelled, handler);
        }
    }

    /*
     * Register NucleusFramework event.
     */
    private boolean registerNucleusEvent(Class<?> event, String priority, boolean ignoreCancelled,
                                         final IScriptEventSubscriber<?> handler) {

        EventSubscriberPriority eventPriority = EventSubscriberPriority.NORMAL;

        try {
            eventPriority = EventSubscriberPriority.valueOf(priority.toUpperCase());
        } catch (Exception e) {
            e.printStackTrace();
        }

        ScriptEventSubscriber subscriber = new ScriptEventSubscriber<>(handler);
        subscriber.setPriority(eventPriority);
        subscriber.setInvokedForCancelled(!ignoreCancelled);

        //noinspection unchecked
        Nucleus.getEventManager().register(_plugin, event, subscriber);

        _subscribers.add(subscriber);

        return true;
    }

    /*
     * Register Bukkit event
     */
    private boolean registerBukkitEvent(Class<? extends Event> event, String priority,
                                        boolean ignoreCancelled,
                                        final IScriptEventSubscriber handler) {
        EventPriority eventPriority = EventPriority.NORMAL;

        try {
            eventPriority = EventPriority.valueOf(priority.toUpperCase());
        } catch (Exception e) {
            e.printStackTrace();
        }

        Object result;

        try {
            result = event.getMethod("getHandlerList").invoke(null);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
            return false;
        }

        if (!(result instanceof HandlerList))
            return false;

        final HandlerList handlerList = (HandlerList)result;
        final Listener dummyListener = new Listener() {};

        final EventExecutor eventExecutor = new EventExecutor() {

            boolean isDisposed = false;

            @Override
            public void execute(Listener listener, Event event) throws EventException {

                try {
                    //noinspection unchecked
                    handler.onEvent(event, new IDisposable() {

                        @Override
                        public boolean isDisposed() {
                            return isDisposed;
                        }

                        @Override
                        public void dispose() {
                            isDisposed = true;
                            handlerList.unregister(dummyListener);
                        }
                    });

                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        };

        RegisteredListener registeredListener = new RegisteredListener(dummyListener,
                eventExecutor, eventPriority, _plugin, ignoreCancelled);

        handlerList.register(registeredListener);

        _registeredBukkit.add(new RegisteredBukkitEvent(handlerList, registeredListener));

        return true;
    }



    private static class RegisteredBukkitEvent {
        HandlerList _handlerList;
        RegisteredListener _registeredListener;

        RegisteredBukkitEvent(HandlerList handlerList, RegisteredListener listener) {
            _handlerList = handlerList;
            _registeredListener = listener;
        }
    }
}

@@ -4222,52 +4222,8 @@
 se;%0A
-                priority = priorityComp%5B0%5D;%0A
     
@@ -4704,32 +4704,39 @@
 tClass, priority
+Comp%5B0%5D
 , ignoreCancelle
@@ -4833,16 +4833,23 @@
 priority
+Comp%5B0%5D
 , ignore

fix script events ignore cancelled parsing