package com.jcwhatever.bukkit;

import com.jcwhatever.bukkit.v1_8_R1.MockServer;

import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_8_R1.scheduler.CraftScheduler;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Static utilities for testing Bukkit plugins.
 */
public class BukkitTest {

    public static final String NMS_TEST_VERSION = "v1_8_R1";

    private static boolean _isInit;
    private static long _nextHeartBeat;
    private static int _currentTick = 0;

    /**
     * Initialize the Bukkit server. This needs to be called
     * before running any tests that may potentially use Bukkit.
     *
     * @return  True if initialized, false if already initialized.
     */
    public static boolean init() {
        if (_isInit) {
            return false;
        }

        _isInit = true;

        try {
            Bukkit.setServer(new MockServer());
        }
        catch (UnsupportedOperationException ignore) {
            return false;
        }

        return true;
    }

    /**
     * Invoke from within loops to send a heart beat signal to the scheduler service.
     *
     * <p>Required when testing components that use the Bukkit scheduler.</p>
     */
    public static void heartBeat() {
        if (!_isInit || System.currentTimeMillis() < _nextHeartBeat)
            return;

        ((CraftScheduler)Bukkit.getServer().getScheduler()).mainThreadHeartbeat(_currentTick);
        _currentTick++;

        _nextHeartBeat = System.currentTimeMillis() + 50;
    }

    /**
     * Invoke to pause for the specified number of ticks.
     * A heart beat is sent to the Bukkit scheduler during this time.
     */
    public static void pause(int ticks) {
        long end = System.currentTimeMillis() + (ticks * 50);
        while (System.currentTimeMillis() < end) {

            heartBeat();

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                return;
            }
        }
    }

    /**
     * Create a new instance of a mock plugin. The plugin
     * returned is already enabled.
     *
     * <p>The plugins version is "v0".</p>
     *
     * @param name  The name of the plugin.
     *
     * @return  The {@code MockPlugin} instance.
     */
    public static MockPlugin mockPlugin(String name) {
        return new MockPlugin(name).enable();
    }

    /**
     * Instantiate and init a non-mock Bukkit plugin.
     *
     * <p>The plugin is initialized without commands. The returned instance is already
     * enabled.</p>
     *
     * @param name         The name to initialize the plugin with.
     * @param version      The version to initialize the plugin with.
     * @param pluginClass  The plugin class.
     *
     * @param <T>  The plugin type.
     *
     * @return  The plugin instance.
     */
    public static <T extends JavaPlugin> T initPlugin(String name, String version, Class<T> pluginClass) {

        init();

        PluginDescriptionFile descriptionFile = new PluginDescriptionFile(name, version, "");

        Map<String, Object> descriptionMap = new HashMap<>(10);
        descriptionMap.put("version", version);
        descriptionMap.put("name", name);
        descriptionMap.put("main", pluginClass.getName());
        descriptionMap.put("commands", new HashMap(0));

        try {
            Method method = descriptionFile.getClass().getDeclaredMethod("loadMap", Map.class);
            method.setAccessible(true);

            method.invoke(descriptionFile, descriptionMap);

        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        T plugin;

        // instantiate plugin
        try {
            plugin = pluginClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        // init plugin
        try {
            Method method = JavaPlugin.class.getDeclaredMethod("init", PluginLoader.class, Server.class,
                    PluginDescriptionFile.class, File.class, File.class, ClassLoader.class);
            method.setAccessible(true);

            method.invoke(plugin, new JavaPluginLoader(Bukkit.getServer()), Bukkit.getServer(),
                    descriptionFile, new File(""), new File(""), BukkitTest.class.getClassLoader());


        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        // enable plugin
        try {
            Method method = JavaPlugin.class.getDeclaredMethod("setEnabled", boolean.class);
            method.setAccessible(true);

            method.invoke(plugin, true);

        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        return plugin;
    }
}

@@ -105,34 +105,8 @@
 it;%0A
-import org.bukkit.Server;%0A
 impo
@@ -161,24 +161,24 @@
 tScheduler;%0A
+
 import org.b
@@ -217,47 +217,8 @@
 le;%0A
-import org.bukkit.plugin.PluginLoader;%0A
 impo
@@ -300,24 +300,24 @@
 ginLoader;%0A%0A
-
 import java.
@@ -321,24 +321,62 @@
 va.io.File;%0A
+import java.lang.reflect.Constructor;%0A
 import java.
@@ -4010,32 +4010,33 @@
 n%0A        try %7B%0A
+%0A
             plug
@@ -4035,156 +4035,72 @@
     
-plugin = pluginClass.newInstance();%0A        %7D catch (InstantiationException %7C IllegalAccessException e) %7B%0A            throw new RuntimeException(e);
+Constructor%3CT%3E constructor = pluginClass.getDeclaredConstructor(
 %0A   
@@ -4104,27 +4104,24 @@
         
-%7D%0A%0A
         
 // init 
@@ -4116,161 +4116,35 @@
     
-// init plugin%0A        try %7B%0A            Method method = JavaPlugin.class.getDeclaredMethod(%22init%22, PluginLoader.class, Server.class,%0A                   
+    JavaPluginLoader.class,
  Plu
@@ -4195,27 +4195,8 @@
 lass
-, ClassLoader.class
 );%0A 
@@ -4198,38 +4198,43 @@
 s);%0A            
-method
+constructor
 .setAccessible(t
@@ -4252,38 +4252,49 @@
         
-method.invoke(plugin, 
+plugin = constructor.newInstance(
 new Java
@@ -4330,28 +4330,8 @@
 ()),
- Bukkit.getServer(),
 %0A   
@@ -4394,56 +4394,107 @@
 (%22%22)
-, BukkitTest.class.getClassLoader
+);%0A%0A            try %7B%0A                plugin = pluginClass.newInstance
 ()
-)
 ;%0A
-%0A%0A
+            %7D%0A   
         
 %7D ca
@@ -4485,33 +4485,32 @@
 %0A           
-%7D
  catch (
 NoSuchMethod
@@ -4489,32 +4489,149 @@
          catch (
+IllegalStateException ignore) %7B%7D%0A%0A        %7D catch (InstantiationException %7C IllegalAccessException %7C%0A                
 NoSuchMethodExce
@@ -4658,33 +4658,8 @@
 rget
-Exception %7C IllegalAccess
 Exce

fix #initPlugin