public enum Language {
	DUTCH,
	ENGLISH,
	GERMAN,
	SPANISH,
	ITALIAN,
	CHINESE;
    	
	/**
	 * Gets the language from its configuration name
	 * @param name The configuration language name
	 * @return Enum of language. ENGLISH if no matches found
	 */
	public static Language getLanuage(String name) {
		if (name.equalsIgnoreCase("nl-dutch")) {
			return DUTCH;
		} else if (name.equalsIgnoreCase("de-german")) {
		    return GERMAN;
		} else if (name.equalsIgnoreCase("es-spanish")) {
            return SPANISH;
        } else if (name.equalsIgnoreCase("it-italian")) {
        	return ITALIAN;
        } else if (name.equalsIgnoreCase("zh-chinese")) {
            return CHINESE;
        }
		return ENGLISH;
	}
} public class APIHandler {
	
	public static APIHandler instance;
	
	private HPS HPS;
	private SpellManager spellManager;
	
	public APIHandler(HPS HPS) {
		instance = this;
		this.HPS = HPS;
		spellManager = HPS.SpellManager;
	}
	
	HPS getHPS() {
		return HPS;
	}
	
	public static APIHandler getInstance() {
		return instance;
	}
	
	public static SpellManager getSpellManager() {
		return APIHandler.getInstance().spellManager;
	}
} public class SpellBookRecipeAddEvent extends Event implements Cancellable {
    private static final HandlerList handlers = new HandlerList();

    private SpellBookRecipe recipe;
    private boolean cancelled = false;

    public SpellBookRecipeAddEvent(SpellBookRecipe recipe) {
        this.recipe = recipe;
    }

    /**
     * Gets the HandlerList for this event
     *
     * @return the handler list
     */
    @Override
    public HandlerList getHandlers() {
        return handlers;
    }

    /**
     * Gets the cancellation status of this spell
     *
     * @return {@code true} if the spell has been cancelled
     */
    @Override
    public boolean isCancelled() {
        return cancelled;
    }

    /**
     * Sets the cancellation status of this spell
     *
     * @param cancel
     */
    @Override
    public void setCancelled(boolean cancel) {
        cancelled = cancel;
    }

    /**
     * Gets the recipe associated with this event
     *
     * @return the recipe
     */
    public SpellBookRecipe getRecipe() {
        return recipe;
    }

    /**
     * Sets the recipe associated with this event
     *
     * @param recipe
     */
    public void setRecipe(SpellBookRecipe recipe) {
        this.recipe = recipe;
    }

    /*
     * START STATIC METHODS
     */

    public static HandlerList getHandlerList() {
        return handlers;
    }

} public abstract class HCommandExecutor implements CommandExecutor {
    public HPS HPS;

    public HCommandExecutor(HPS instance) {
        this.HPS = instance;
    }

} public class ConfigurationManager {
	
	private static String[] configHeader = {
		"############################ #",
		"## General Configuration  ## #",
		"## http://tiny.cc/hpsconf ## #",
		"############################ #",
		"\n",
		"Only enable if you want to enable debugging mode"
	};
	
    private HPS HPS;
    private Map<ConfigurationType, CustomConfiguration> configurationMap = new HashMap<ConfigurationType, CustomConfiguration>();

    /**
     * Constructs a new {@link ConfigurationManager}
     *
     * @param instance an instance of {@link HPS}
     */
    public ConfigurationManager(HPS instance) {
        this.HPS = instance;
        configurationMap.put(ConfigurationType.COOLDOWN, new CooldownConfig(HPS, new File(HPS.getDataFolder(), "cooldown.yml"), HPS.getResource("cooldown.yml")));
        configurationMap.put(ConfigurationType.PLAYER_SPELL, new PlayerSpellConfig(HPS, new File(HPS.getDataFolder(), "PlayerSpellConfig.yml"), HPS.getResource("PlayerSpellConfig.yml")));
        configurationMap.put(ConfigurationType.SPELL, new SpellConfig(HPS, new File(HPS.getDataFolder(), "spells.yml"), HPS.getResource("spells.yml")));
    }

    /**
     * Gets a {@link CustomConfiguration} that represents the given {@link Type} of configuration
     *
     * @param type what configuration file you want to retrieve
     * @return the configuration file
     */
    public CustomConfiguration getConfig(ConfigurationType type) {
        return configurationMap.get(type);
    }
    
    /**
     * Reloads all configs
     */
    public void reloadConfigAll() {
    	configurationMap.put(ConfigurationType.COOLDOWN, new CooldownConfig(HPS, new File(HPS.getDataFolder(), "cooldown.yml"), HPS.getResource("cooldown.yml")));
    	configurationMap.put(ConfigurationType.PLAYER_SPELL, new PlayerSpellConfig(HPS, new File(HPS.getDataFolder(), "PlayerSpellConfig.yml"), HPS.getResource("PlayerSpellConfig.yml")));
    	configurationMap.put(ConfigurationType.SPELL, new SpellConfig(HPS, new File(HPS.getDataFolder(), "spells.yml"), HPS.getResource("spells.yml")));
    }
    
    /**
     * Loads the default config
     */
    public void loadConfig() {
        File file = new File(HPS.getDataFolder(), "config.yml");
        FileConfiguration config = YamlConfiguration.loadConfiguration(file);
        FileConfiguration resource = HPS.getConfig();
        if (!file.exists()) {
        	HPS.saveDefaultConfig();
        } else {
        	if (configHeader != null) {
            	String header = "";
                for (int i = 0; i < configHeader.length; i++) {
                	header = header + configHeader[i];
                	if (i+2 < configHeader.length) {
                		header = header + "\n";
                	}
                }
                config.options().header(header);
        	}
        	config.setDefaults(resource.getDefaults());
        	if (HPS.getConfig().getBoolean("debug-mode")) HPS.getLogger().info(config.options().header());
        	config.options().copyDefaults(true);
        	config.options().copyHeader(true);
        	try {
				config.save(file);
			} catch (IOException e) {
				e.printStackTrace();
			}
        }
    }

    /**
     * An enum representing all the different types of {@link CustomConfiguration} that exist
     */
    public enum ConfigurationType {
        /**
         * Stores a list of the spells that every player knows
         */
        PLAYER_SPELL,
        /**
         * Stores a list of spell configurations
         */
        SPELL,
        /**
         * Stores a list of cooldowns for spells
         */
        COOLDOWN;
    }

} public enum ConfigurationType {
    /**
     * Stores a list of the spells that every player knows
     */
    PLAYER_SPELL,
    /**
     * Stores a list of spell configurations
     */
    SPELL,
    /**
     * Stores a list of cooldowns for spells
     */
    COOLDOWN;
} public class CustomConfiguration {
    private FileConfiguration fileConfiguration = null;
    private File file = null;
    private HPS HPS;

    /**
     * Constructs a new {@link CustomConfiguration} without copying any defaults
     *
     * @param instance an instance of {@link HPS}
     * @param file     where to store the custom configuration
     */
    public CustomConfiguration(HPS instance, File file) {
        this(instance, file, null, null);
    }

    /**
     * Constructs a new {@link CustomConfiguration}, copying defaults from an {@link InputStream}
     *
     * @param instance an instance of {@link HPS}
     * @param file     where to store the custom configuration
     * @param stream   an input stream to copy default configuration from
     * @param header   String array of text as header
     */
	public CustomConfiguration(HPS instance, File file, InputStream stream, String[] header) {
        this.HPS = instance;
        this.file = file;
        this.fileConfiguration = YamlConfiguration.loadConfiguration(file);
        if (stream != null) {
            YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(stream));
            fileConfiguration.setDefaults(defConfig);
            fileConfiguration.options().copyDefaults(true);
        } 
        if (header != null) {
        	String head = "";
            for (String line : header) {
            	head = head + line + "\n";
            }
            fileConfiguration.options().header(head);
            fileConfiguration.options().copyHeader(true);
        }
        save();
    }
    
    /**
     * Gets the custom configuration file
     *
     * @return the {@link FileConfiguration} represented by this class
     */
    public FileConfiguration get() {
        return fileConfiguration;
    }

    /**
     * Saves change made to this custom configuration file to disk
     */
    public void save() {
        try {
            get().save(file);
        } catch (IOException ex) {
            HPS.PM.log(Level.SEVERE, HPS.Localisation.getTranslation("ccnCouldNotSave", file.getName()));
            HPS.PM.debug(ex);
        }
    }

} public class PlayerSpellConfig extends CustomConfiguration {

    /**
     * Constructs a new {@link PlayerSpellConfig} without copying any defaults
     *
     * @param instance an instance of {@link HPS}
     * @param file     where to store the custom configuration
     */
    public PlayerSpellConfig(HPS instance, File file) {
        super(instance, file);
    }

    /**
     * Constructs a new {@link CustomConfiguration}, copying defaults from an {@link InputStream}
     *
     * @param instance an instance of {@link HPS}
     * @param file     where to store the custom configuration
     * @param stream   an input stream to copy default configuration from
     */
    public PlayerSpellConfig(HPS instance, File file, InputStream stream) {
        super(instance, file, stream, null);
    }

    /**
     * The current version specifying the format of the {@code PlayerSpellConfig.yml}
     */
    public static final double CURRENT_VERSION = 1.1d;

    /**
     * A utility function that is meant to be used instead of {@link FileConfiguration#getStringList(String)}. <br>
     * This is needed because the Bukkit version returns {@code null} when no String list is found.
     *
     * @param string the path to the String list
     * @return the string list at that location or an empty {@link ArrayList}
     */
    public List<String> getStringListOrEmpty(String string) {
        return get().getStringList(string) == null ? new ArrayList<String>() : get().getStringList(string);
    }

} public class ExtensionManager {
    private HPS HPS;
    private ExtensionLoader extensionLoader;
    private final File extensionFolder;
    private final SortedMap<Extension, Extension.State> extensions = new TreeMap<Extension, State>(new Comparator<Extension>() {

        @Override
        public int compare(Extension o1, Extension o2) {
            return o1.getName().compareTo(o2.getName());
        }

    });

    /**
     * Constructs a new {@link ExtensionManager}
     * 
     * @param instance an instance of {@link HPS}
     */
    public ExtensionManager(HPS instance) {
        HPS = instance;
        extensionFolder = new File(HPS.getDataFolder(), "Extensions");

        if (!extensionFolder.exists()) {
            extensionFolder.mkdir();
        }

        extensionLoader = new ExtensionLoader(instance, extensionFolder);
    }

    /**
     * Loads the extensions located in the extensions folder.
     */
    public void loadExtensions() {
        for (File file : extensionFolder.listFiles(new FileExtensionFilter(".jar"))) {
            Extension extension = extensionLoader.loadExtension(file);
            extensions.put(extension, State.LOADED);
        }
    }

    /**
     * Disables all extensions, loads and then enables the extension list.
     */
    public void reloadExtensions() {
        disableExtensions();
        this.extensions.clear();
        this.extensionLoader.clearLoaders();
        loadExtensions();
        enableExtensions();
    }

    /**
     * Gets the folder for storing {@link Extension}s
     * 
     * @return the folder
     */
    public File getExtensionFolder() {
        return extensionFolder;
    }

    /**
     * Enables all {@link Extension}s with the {@link Extension.State#LOADED} state
     */
    public void enableExtensions() {
        for (Extension extension : extensions.keySet()) {
            if (extensions.get(extension) == Extension.State.LOADED) {
                extension.onEnable();
                HPS.getServer().getPluginManager().registerEvents(extension, HPS);
                extensions.put(extension, Extension.State.ENABLED);
            }
        }
    }

    /**
     * Disables all {@link Extension}s with the {@link Extension.State#ENABLED} state
     */
    public void disableExtensions() {
        for (Extension extension : extensions.keySet()) {
            if (extensions.get(extension) == Extension.State.ENABLED) {
                extension.onDisable();
                HandlerList.unregisterAll(extension);
                extensions.put(extension, Extension.State.DISABLED);
            }
        }
    }

    /**
     * Gets the {@link Extension.State} of an {@link Extension}
     * 
     * @param extension the extension
     * @return the extension's state
     */
    public Extension.State getState(Extension extension) {
        return extensions.get(extension);
    }

} public abstract class Spell {
    public HPS HPS;

    public Spell(HPS instance) {
        this.HPS = instance;
    }

    /**
     * Called when a spell is cast.
     *
     * @param p the player who cast the spell
     * @return {@code true} if the spell was a success
     */
    public abstract boolean cast(Player p);

    /**
     * Teaches the spell to a target.
     *
     * @param sender the person who wants to teach
     * @param target the person who will be taught
     */
    public void teach(Player sender, Player target) {
        if (target != null) {
            if (playerKnows(target))
                HPS.PM.warn(sender, HPS.Localisation.getTranslation("cmdTeaKnowsThat", target.getName()));
            else {
                teach(target);
                HPS.PM.tell(sender, HPS.Localisation.getTranslation("cmdTeaTaughtOne", target.getName(), getName()));
            }
        } else
            HPS.PM.warn(sender, HPS.Localisation.getTranslation("cmdPlayerNotFound"));
    }

    /**
     * Teaches the spell to a player.
     *
     * @param p the player
     */
    public void teach(Player p) {
        PlayerSpellConfig psc = (PlayerSpellConfig) HPS.ConfigurationManager.getConfig(ConfigurationType.PLAYER_SPELL);
        List<String> list = psc.getStringListOrEmpty(p.getName());
        list.add(getName());
        psc.get().set(p.getName(), list);
        psc.save();
    }

    /**
     * Gets whether a player knows this spell.
     *
     * @param p the player
     * @return {@code true} if the player knows this spell
     */
    public boolean playerKnows(Player p) {
        PlayerSpellConfig psc = (PlayerSpellConfig) HPS.ConfigurationManager.getConfig(ConfigurationType.PLAYER_SPELL);
        List<String> list = psc.getStringListOrEmpty(p.getName());
        return list.contains(getName());
    }

    /**
     * Makes a player forget the spell.
     *
     * @param p the player
     */
    public void unTeach(Player p) {
        PlayerSpellConfig psc = (PlayerSpellConfig) HPS.ConfigurationManager.getConfig(ConfigurationType.PLAYER_SPELL);
        List<String> list = psc.getStringListOrEmpty(p.getName());
        list.remove(getName());
        psc.get().set(p.getName(), list);
        psc.save();
    }

    /**
     * Gets the permission required to teach the spell and cast the spell.
     * 
     * @return spell permission
     */
    public Permission getPermission() {
        return new Permission("harrypotterspells.spell." + this.getName().toLowerCase().replaceAll(" ", "-"), PermissionDefault.TRUE);
    }

    /**
     * Gets the name of this spell.
     *
     * @return the spell's name
     */
    public String getName() {
        SpellInfo info = this.getClass().getAnnotation(SpellInfo.class);
        return info == null ? this.getClass().getSimpleName() : info.name();
    }

    /**
     * Gets the description of this spell.
     *
     * @return the description
     */
    public String getDescription() {
        SpellInfo info = this.getClass().getAnnotation(SpellInfo.class);
        return info == null ? "" : HPS.Localisation.getTranslation(info.description());
    }

    /**
     * The annotation required for a spell to be used. <br>
     * This annotation contains all the information needed for using a spell.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @interface SpellInfo {
        String name() default ""; // "" defaults to class name

        String description() default "";

        int range() default 25;

        boolean goThroughWalls() default false;

        int cooldown() default 60;

        Material icon() default Material.STICK;
    }

    /**
     * Gets the icon for this spell.
     *
     * @return the icon as a {@link Material}
     */
    public Material getIcon() {
        SpellInfo info = this.getClass().getAnnotation(SpellInfo.class);
        return info == null ? Material.STICK : info.icon();
    }

    /**
     * Gets whether the spell can be cast through walls.
     *
     * @return {@code true} if the spel can be cast through walls.
     */
    public boolean canBeCastThroughWalls() {
        SpellInfo info = this.getClass().getAnnotation(SpellInfo.class);
        return info == null ? false : info.goThroughWalls();
    }

    /**
     * Gets the range of the spell.
     *
     * @return the range
     */
    public int getRange() {
        SpellInfo info = this.getClass().getAnnotation(SpellInfo.class);
        return info == null ? 25 : info.range();
    }

    /**
     * Gets the cool down of the spell for a player.
     *
     * @return the cool down
     */
    public int getCoolDown(Player p) {
        FileConfiguration cdConfig = HPS.ConfigurationManager.getConfig(ConfigurationType.COOLDOWN).get();
        SpellInfo info = this.getClass().getAnnotation(SpellInfo.class);
        if (info == null)
            return 60;
        
        String spellName = info.name().toLowerCase().replace(" ", "-");
        if (p.hasPermission(HPS.SpellManager.NO_COOLDOWN_ALL_1) || p.hasPermission(HPS.SpellManager.NO_COOLDOWN_ALL_2)
                || p.hasPermission("harrypotterspells.nocooldown." + spellName))
            return 0;

        int cooldown;
        if (cdConfig.contains("cooldowns." + spellName))
            cooldown = cdConfig.getInt("cooldowns." + spellName);
        else
            cooldown = info.cooldown();

        return cooldown == -1 ? info.cooldown() : cooldown;
    }

    /**
     * A utility method used to shorten the retrieval of something from the spells configuration
     * file.
     *
     * @param key the key to the value relative to {@code spells.[spell name].}
     * @param defaultt the nullable value to return if nothing was found
     * @return the object found at that location
     */
    public Object getConfig(String key, @Nullable Object defaultt) {
        FileConfiguration spellConfig = HPS.ConfigurationManager.getConfig(ConfigurationType.SPELL).get();
        key = "spells." + getName().toLowerCase().replace(" ", "-") + "." + key;
        return defaultt == null ? spellConfig.get(key) : spellConfig.get(key, defaultt);
    }

    /**
     * Gets a time from the spells configuration as formatted by the following table: <br>
     * Default: seconds <br>
     * {@code endsWith("t")}: ticks
     *
     * @param key the key to the value relative to {@code spells.[spellname].}
     * @param defaultt the nullable ticks to return if nothing was found
     * @return a {@code long} with the amount of ticks the time specified
     */
    public long getTime(String key, @Nullable long defaultt) {
        Object time = getConfig(key, "");
        if (time instanceof String) {
            String durationString = (String) time;
            if (durationString.equals("")) {
                return defaultt;
            }
            int duration = 0;
            if (durationString.endsWith("t")) {
                String ticks = durationString.substring(0, durationString.length() - 1);
                duration = Integer.parseInt(ticks);
            } else {
                duration = Integer.parseInt(durationString) * 20; // convert to ticks
            }
            return duration;
        }
        if (time instanceof Integer) {
            return ((Number) time).longValue() * 20; // convert to ticks
        }
        return defaultt;
    }
    
    /**
     * Will attempt to convert an Object to a double. Logs a warning if not able to.
     * Primarily used for {@link #getConfig(String, Object)}
     * 
     * @param toHandle The value to convert
     * @param defaultt The value to default to on error.
     * @return the double value of toHandle
     */
    public double handleDouble(Object toHandle, @Nullable double defaultt) {
        Double result = defaultt;
        try {
            result = MiscUtilities.handleDouble(toHandle);
        } catch (NumberFormatException e) {
            HPS.PM.log(Level.WARNING, "Error when handling double value for Spell: " + getName());
            HPS.PM.log(Level.WARNING, "Expected double but instead got: " + toHandle.toString());
        }
        return result;
    }

} public class SpellManager {
    private HashMap<String, HashMap<Spell, Long>> cooldowns = new HashMap<String, HashMap<Spell, Long>>();
    private Comparator<Spell> spellComparator = new Comparator<Spell>() {

        @Override
        public int compare(Spell o1, Spell o2) {
            return o1.getName().compareTo(o2.getName());
        }

    };
    private SortedSet<Spell> spellList = new TreeSet<Spell>(spellComparator);
    private Map<String, Integer> currentSpell = new HashMap<String, Integer>();
    private HPS HPS;

    public final Permission NO_COOLDOWN_ALL_1 = new Permission("harrypotterspells.nocooldown", PermissionDefault.OP), NO_COOLDOWN_ALL_2 = new Permission("harrypotterspells.nocooldown.*");

    /**
     * Constructs the {@link SpellManager}, adding all core spells to the Spell List
     *
     * @param plugin an instance of {@link HPS}
     */
    public SpellManager(HPS instance) {
        this.HPS = instance;
        HPS.getServer().getPluginManager().addPermission(NO_COOLDOWN_ALL_1);
        HPS.getServer().getPluginManager().addPermission(NO_COOLDOWN_ALL_2);

        try {
            for (Class<?> clazz : ReflectionsReplacement.getSubtypesOf(Spell.class, "com.hpspells.core.spell", HPS.getHPSClassLoader(), Spell.class)) {
                if (clazz.getAnnotation(SpellInfo.class) == null)
                    continue;

                Spell spell;
                try {
                    spell = (Spell) clazz.getConstructor(HPS.class).newInstance(HPS);

                    if (Listener.class.isAssignableFrom(clazz)) {
                        HPS.getServer().getPluginManager().registerEvents((Listener) spell, HPS);
                    }
                    
                    HPS.getServer().getPluginManager().addPermission(spell.getPermission());

                } catch (Exception e) {
                    HPS.PM.log(Level.WARNING, HPS.Localisation.getTranslation("errSpells", clazz.getSimpleName()));
                    HPS.PM.debug(e);
                    continue;
                }
                spellList.add(spell);
            }
        } catch (Exception e) {
            HPS.PM.log(Level.WARNING, HPS.Localisation.getTranslation("errReflectionsReplacementSpelll"));
            HPS.PM.debug(e);
        }
    }

    /**
     * Gets the current spell position a player is on
     *
     * @param player the player
     * @return the current spell position they are on
     */
    public Integer getCurrentSpellPosition(Player player) {
        PlayerSpellConfig psc = (PlayerSpellConfig) HPS.ConfigurationManager.getConfig(ConfigurationType.PLAYER_SPELL);
        List<String> spellsTheyKnow = psc.getStringListOrEmpty(player.getName());

        if (spellsTheyKnow.isEmpty())
            return null;

        if (!currentSpell.containsKey(player.getName()))
            return 0;

        return currentSpell.get(player.getName());
    }

    /**
     * Gets the current spell a player is on
     *
     * @param player the player
     * @return the current spell they are on
     */
    public Spell getCurrentSpell(Player player) {
        PlayerSpellConfig psc = (PlayerSpellConfig) HPS.ConfigurationManager.getConfig(ConfigurationType.PLAYER_SPELL);
        Integer cur = getCurrentSpellPosition(player);
        List<String> spells = psc.getStringListOrEmpty(player.getName());
        if (spells.isEmpty())
            return null;
        return cur == null ? null : getSpell(Iterables.get(new TreeSet<String>(spells), cur.intValue()));
    }

    /**
     * Sets the current spell position a player is on
     *
     * @param player the player
     * @param id     the new current spell position the player is on
     * @return the spell they have changed to
     * @throws IllegalArgumentException if the id parameter is invalid
     */
    public Spell setCurrentSpellPosition(Player player, int id) throws IllegalArgumentException {
        PlayerSpellConfig psc = (PlayerSpellConfig) HPS.ConfigurationManager.getConfig(ConfigurationType.PLAYER_SPELL);
        List<String> spellsTheyKnow = psc.getStringListOrEmpty(player.getName());
        if (spellsTheyKnow == null || id >= spellsTheyKnow.size() || id < 0)
            throw new IllegalArgumentException("id was invalid");
        currentSpell.put(player.getName(), id);
        return getCurrentSpell(player);
    }

    /**
     * Sets the current spell a player is on
     *
     * @param player the player
     * @param spell  the new spell the player is on
     * @return the spell they have changed to for chaining
     * @throws IllegalArgumentException if the spell parameter is invalid
     */
    public Spell setCurrentSpell(Player player, Spell spell) throws IllegalArgumentException {
        PlayerSpellConfig psc = (PlayerSpellConfig) HPS.ConfigurationManager.getConfig(ConfigurationType.PLAYER_SPELL);
        Integer spellIndex = getIndex(new TreeSet<String>(psc.getStringListOrEmpty(player.getName())), spell.getName());
        if (spellIndex == null)
            throw new IllegalArgumentException("player does not know that spell");
        setCurrentSpellPosition(player, spellIndex);
        return getCurrentSpell(player);
    }

    /**
     * Gets a spell by name
     *
     * @param name the spell to get
     * @return the spell or {@code null} if not found
     */
    public Spell getSpell(String name) {
        for (Spell spell : spellList)
            if (spell.getName().equalsIgnoreCase(name))
                return spell;
        return null;
    }

    /**
     * Adds a spell to the spell list
     *
     * @param spell the spell
     */
    public void addSpell(Spell spell) {
        spellList.add(spell);
    }

    /**
     * Gets a list of all spells <br>
     * <b>Note:</b> this returns a {@link SortedSet}. If you do not need to use
     * a sorted set then cast it as a {@link Set}!
     *
     * @return the list
     */
    public SortedSet<Spell> getSpells() {
        return spellList;
    }

    /**
     * Checks if a string corrosponds to a spell
     *
     * @param name the name to test
     * @return {@code true} if the spell exists
     */
    public boolean isSpell(String name) {
        return getSpell(name) != null;
    }

    /**
     * Casts a spell cleverly. Checks permissions, triggering {@link SpellEvent}'s , sending effects and applying cooldown.
     * 
     * Note: Does not check if player has a wand in their inventory
     *
     * @param player the player who is casting
     * @param spell  the spell that they are casting
     */
    public void cleverCast(Player player, Spell spell) {
        if (!player.hasPermission("harrypotterspells.cast") || !spell.playerKnows(player))
            return;

        PlayerSpellConfig psc = (PlayerSpellConfig) HPS.ConfigurationManager.getConfig(ConfigurationType.PLAYER_SPELL);

        List<String> spellList = psc.getStringListOrEmpty(player.getName());
        if (spellList == null || spellList.isEmpty()) {
            HPS.PM.tell(player, "You don't know any spells.");
            return;
        }

        SpellPreCastEvent sce = new SpellPreCastEvent(spell, player);
        Bukkit.getServer().getPluginManager().callEvent(sce);
        if (!sce.isCancelled()) {
            Boolean cast = true;
            String playerName = player.getName();
            if (needsCooldown(playerName, spell)) {
                HPS.PM.dependantMessagingTell((CommandSender) player, HPS.Localisation.getTranslation("cldWait", spell.getCoolDown(player) - ((System.currentTimeMillis() - cooldowns.get(playerName).get(spell)) / 1000)).toString());
                cast = false;
            }
            boolean successful = false;
            if (cast) {
                successful = spell.cast(player);
                if (HPS.getConfig().getBoolean("spell-particle-toggle")) {
                    Location l = player.getLocation();
                    l.setY(l.getY() + 1);
                    player.getWorld().playEffect(l, Effect.ENDER_SIGNAL, 0);
                }
            }
            Bukkit.getServer().getPluginManager().callEvent(new SpellPostCastEvent(spell, player, successful));

            if (cast && successful && spell.getCoolDown(player) > 0) {
                setCoolDown(playerName, spell);
            }
        }
    }

    /**
     * Gets if a cooldown is needed by a player for a certain spell.
     *
     * @param playerName The name of the player you're checking if needs a cooldown
     * @param spell      The spell you're checking for
     * @return
     */
    public boolean needsCooldown(String playerName, Spell spell) {
        if (cooldowns.containsKey(playerName) && cooldowns.get(playerName).containsKey(spell)) {
            long currentTime = System.currentTimeMillis();
            long lastTime = cooldowns.get(playerName).get(spell);
            long difference = (currentTime - lastTime) / 1000;
            return difference < spell.getCoolDown(Bukkit.getPlayer(playerName));
        }
        return false;
    }

    /**
     * Sets the cooldown of a certain spell for a certain player for right now
     *
     * @param playerName name of the player you're setting a cooldown for
     * @param spell      the spell you're setting the cooldown for
     */
    public void setCoolDown(String playerName, Spell spell) {
        setCooldown(playerName, spell, null);
    }

    /**
     * @param playerName         The name of the player you're setting a cooldown for
     * @param spell              The spell you're setting the cooldown for
     * @param timeInMilliseconds The amount of additional time you wish for the cooldown to last, can be negative.
     */
    public void setCooldown(String playerName, Spell spell, @Nullable Long timeInMilliseconds) {
        if (timeInMilliseconds == null) {
            timeInMilliseconds = 0L;
        }
        if (cooldowns.containsKey(playerName)) {
            cooldowns.get(playerName).put(spell, System.currentTimeMillis() + timeInMilliseconds);
        } else {
            cooldowns.put(playerName, new HashMap<Spell, Long>());
            cooldowns.get(playerName).put(spell, System.currentTimeMillis() + timeInMilliseconds);
        }
    }

	/*
     * START PRIVATE UTILITIES
	 */

    private Integer getIndex(Set<? extends Object> set, Object value) {
        int result = 0;
        for (Object entry : set) {
            if (entry.equals(value))
                return result;
            result++;
        }
        return null;
    }

} public interface Craftable {

    SpellBookRecipe getCraftingRecipe();

} public class MetricStatistics implements Listener {
    private static int spellsCast = 0, successes = 0, failures = 0;
    private static Map<String, Integer> typesOfSpellCast = new HashMap<String, Integer>();

    public static int getSpellsCast() {
        return spellsCast;
    }

    public static int getAmountOfTimesCast(Spell spell) {
        return typesOfSpellCast.get(spell.getName()) == null ? 0 : typesOfSpellCast.get(spell.getName());
    }

    public static int getSuccesses() {
        return successes;
    }

    public static int getFailures() {
        return failures;
    }

    @EventHandler
    public void postSpellCast(SpellPostCastEvent e) {
        spellsCast++;
        typesOfSpellCast.put(e.getSpell().getName(), typesOfSpellCast.get(e.getSpell().getName()) == null ? 1 : typesOfSpellCast.get(e.getSpell().getName()) + 1);
        if (e.wasSuccessful())
            successes++;
        else
            failures++;
    }

} public class ReflectionsReplacement {

    /**
     * Gets a list containing the fully qualified names of the all classes in a package
     *
     * @param packageName the name of the package
     * @param classLoader the {@link ClassLoader} to find the package in
     * @return a list containing the names of all classes in a package
     * @throws IOException if an error occurred whilst getting the package information
     */
    public static List<String> getClassNamesFromPackage(String packageName, ClassLoader classLoader) throws IOException {
        URL packageURL;
        ArrayList<String> names = new ArrayList<String>();
        ;

        packageName = packageName.replace(".", "/");
        packageURL = classLoader.getResource(packageName);

        if (packageURL.getProtocol().equals("jar")) { // Loop through all things in the jar
            String jarFileName;
            JarFile jf;
            Enumeration<JarEntry> jarEntries;
            String entryName;

            jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8");
            jarFileName = jarFileName.substring(5, jarFileName.indexOf("!"));
            jf = new JarFile(jarFileName);
            jarEntries = jf.entries();
            while (jarEntries.hasMoreElements()) { // Get the jar file and loop through everything in it
                entryName = jarEntries.nextElement().getName();
                if (entryName.startsWith(packageName) && entryName.endsWith(".class") && entryName.length() > packageName.length() + 5) {
                    names.add(entryName.replace(".class", "").replace('/', '.')); // Put the stuff back again
                }
            }
            jf.close();
        } else { // Otherwise loop through all things in the classpath
            File folder = new File(packageURL.getFile());
            File[] contenuti = folder.listFiles();
            String entryName;
            for (File actual : contenuti) {
                entryName = actual.getName();
                names.add(entryName.replace(".class", "").replace('/', '.'));
            }
        }

        return names;
    }

    /**
     * Gets all subtypes of a specified class in a package
     *
     * @param clazz       the class to find subtypes of
     * @param packagee    the package to search in
     * @param classLoader the {@link ClassLoader} to find the package in
     * @param ignore      classes to ignore
     * @return a list containing all subtypes of the specified class
     * @throws Exception if an error occurred during the lookup of these classes
     */
    public static <T> List<Class<? extends T>> getSubtypesOf(Class<T> clazz, String packagee, ClassLoader classLoader, Class<?>... ignore) throws Exception {
        List<Class<? extends T>> returnMe = Lists.newArrayList();
        List<Class<?>> ignores = Lists.newArrayList(ignore);
        for (String str : getClassNamesFromPackage(packagee, classLoader)) {
            Class<?> indi = Class.forName(str.replace(".class", ""));
            if (clazz.isAssignableFrom(indi) && !ignores.contains(indi)) {
                @SuppressWarnings("unchecked") Class<? extends T> extended = (Class<? extends T>) indi;
                returnMe.add(extended);
            }
        }
        return returnMe;
    }

    /**
     * Adds a file to a classpath
     *
     * @param classPath the classpath
     * @param file the file
     * @throws Exception if an error occurred while adding the class to the classpath
     */
    public static void addFileToClasspath(ClassLoader classPath, File file) throws Exception {
        Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
        method.setAccessible(true);
        method.invoke(classPath, new Object[]{file.toURI().toURL()});
    }

    /**
     * Adds a file to the system's classpath
     *
     * @param file the file
     * @throws Exception if an error occurred while adding the class to the classpath
     */
    public static void addFileToClasspath(File file) throws Exception {
        addFileToClasspath(ClassLoader.getSystemClassLoader(), file);
    }

} public class SVPBypass {

    /**
     * Gets the current package version used in the SVP. <br>
     * It returns the part in []: org.bukkit.craftbukkit.[1.5.1_R3].CraftServer
     *
     * @return the current package version
     */
    public static String getCurrentPackageVersion() {
        return Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
    }

    /**
     * Gets the current version of the CraftBukkit class, bypassing the Bukkit SVP.
     *
     * @param clazz the class relative to the root of the version. <br>
     *              E.G {@code entity.CraftArrow} will return the current class that contains the CraftArrow.
     * @return the class
     */
    public static Class<?> getCurrentCBClass(String clazz) {
        try {
            return Class.forName("org.bukkit.craftbukkit." + getCurrentPackageVersion() + "." + clazz);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * Gets the current version of the NMS (net.minecraft.server) class, bypassing the Bukkit SVP.
     *
     * @param clazz the class relative to the root of the version. <br>
     *              E.G {@code EnchantmentManager} will return the current class that contains the EnchantmentManager.
     * @return the class
     */
    public static Class<?> getCurrentNMSClass(String clazz) {
        try {
            return Class.forName("net.minecraft.server." + getCurrentPackageVersion() + "." + clazz);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * Sets a value of an {@link Object} via reflection
     *
     * @param instance  instance the class to use
     * @param fieldName the name of the {@link Field} to modify
     * @param value     the value to set
     * @throws Exception if an error occurred during reflections
     */
    public static void setValue(Object instance, String fieldName, Object value) throws Exception {
        Field field = instance.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(instance, value);
    }

    /**
     * Get a value of an {@link Object}'s {@link Field}
     *
     * @param instance  the target {@link Object}
     * @param fieldName name of the {@link Field}
     * @return the value of {@link Object} instance's {@link Field} with the name of fieldName
     * @throws Exception if an error occurred during reflections
     */
    public static Object getValue(Object instance, String fieldName) throws Exception {
        Field field = instance.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(instance);
    }

    /**
     * Utility function to get a {@link Method} from a class.
     *
     * @param cl     the class to get the method from
     * @param method the name of the method
     * @return the method retrieved by the function
     */
    public static Method getMethod(Class<?> cl, String method) {
        for (Method m : cl.getMethods()) {
            if (m.getName().equals(method)) {
                return m;
            }
        }
        return null;
    }

    /**
     * Gets a method by type types
     *
     * @param clazz the class
     * @param type the type
     * @param paramTypes any param types
     *
     * @return the method
     */
    public static Method getMethodByTypeTypes(Class<?> clazz, Class<?> type, Class<?>... paramTypes) {
        w1: for(Method m:clazz.getDeclaredMethods()){
            if(!m.getReturnType().equals(type)) continue;
            if(m.getParameterTypes().length!=paramTypes.length) continue;
            for(int i=0;i<paramTypes.length;i++) {
                if (!m.getParameterTypes()[i].equals(paramTypes[i])) {
                    continue w1;
                }
            }
            return m;
        }
        w1: for(Method m:clazz.getMethods()){
            if(!m.getReturnType().equals(type)) continue;
            if(m.getParameterTypes().length!=paramTypes.length) continue;
            for(int i=0;i<paramTypes.length;i++) {
                if (!m.getParameterTypes()[i].equals(paramTypes[i])) {
                    continue w1;
                }
            }
            return m;
        }
        return null;
    }

    /**
     * Gets a field by the type
     *
     * @param clazz the class
     * @param type the type
     *
     * @return the field
     */
    public static Field getFieldByType(Class<?> clazz,Class<?> type) {
        for(Field f:clazz.getDeclaredFields()){
            if (f.getType().equals(type)) return f;
        }
        for(Field f:clazz.getFields()){
            if (f.getType().equals(type)) return f;
        }
        return null;
    }

}
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.help.GenericCommandHelpTopic;
import org.bukkit.help.HelpTopic;
import org.bukkit.help.IndexHelpTopic;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.java.JavaPlugin;
import com.hpspells.core.Localisation.Language;
import com.hpspells.core.api.APIHandler;
import com.hpspells.core.api.event.SpellBookRecipeAddEvent;
import com.hpspells.core.command.CommandInfo;
import com.hpspells.core.command.HCommandExecutor;
import com.hpspells.core.configuration.ConfigurationManager;
import com.hpspells.core.configuration.ConfigurationManager.ConfigurationType;
import com.hpspells.core.configuration.CustomConfiguration;
import com.hpspells.core.configuration.PlayerSpellConfig;
import com.hpspells.core.extension.ExtensionManager;
import com.hpspells.core.spell.Spell;
import com.hpspells.core.spell.SpellManager;
import com.hpspells.core.spell.interfaces.Craftable;
import com.hpspells.core.util.MetricStatistics;
import com.hpspells.core.util.ReflectionsReplacement;
import com.hpspells.core.util.SVPBypass;
                                    list.add(spellString);
                                }
                            }

                            newConfig.put(cs, list);
                        }

                        for (Entry<String, List<String>> ent : newConfig.entrySet())
                            PSC.get().set(ent.getKey(), ent.getValue());

                        PSC.get().set("VERSION_DO_NOT_EDIT", 1.1d);
                        PSC.save();

                        PM.log(Level.INFO, Localisation.getTranslation("pscUpdated", "1.1"));
                    }
                }
            }

            // Listeners
            getServer().getPluginManager().registerEvents(new Listeners(this), this);
            getServer().getPluginManager().registerEvents(new MetricStatistics(), this);

            // Hacky command map stuff
            try {
                Class<?> craftServer = SVPBypass.getCurrentCBClass("CraftServer");
                if (craftServer == null)
                    throw new Throwable("Computer says no");
                Field f = craftServer.getDeclaredField("commandMap");
                f.setAccessible(true);
                commandMap = (CommandMap) f.get(getServer());
            } catch (Throwable e) {
                PM.log(Level.SEVERE, Localisation.getTranslation("errCommandMap"));
                PM.debug(e);
            }

            // Reflections - Commands
            int commands = 0;
            try {
                for (Class<? extends HCommandExecutor> clazz : ReflectionsReplacement.getSubtypesOf(HCommandExecutor.class, "com.hpspells.core.command", getClassLoader(), HCommandExecutor.class)) {
                    if (addHackyCommand(clazz))
                        commands++;
                }
            } catch (Exception e) {
                PM.log(Level.WARNING, Localisation.getTranslation("errReflectionsReplacementCmd"));
                PM.debug(e);
            }
            
//            HPSTabCompleter completer = new HPSTabCompleter();
//            getCommand("spellinfo").setTabCompleter(completer);
//            getCommand("spellswitch").setTabCompleter(completer);
//            getCommand("teach").setTabCompleter(completer);
//            getCommand("unteach").setTabCompleter(completer);
            
            PM.debug(Localisation.getTranslation("dbgRegisteredCoreCommands", commands));

            Bukkit.getHelpMap().addTopic(new IndexHelpTopic("HarryPotterSpells", Localisation.getTranslation("hlpDescription"), "", helpTopics));
            PM.debug(Localisation.getTranslation("dbgHelpCommandsAdded"));

            // Plugin Metrics
            setupCharts(new Metrics(this));

            // Crafting Changes
            PM.debug(Localisation.getTranslation("dbgCraftingStart"));
            setupCrafting();
            PM.debug(Localisation.getTranslation("dbgCraftingEnd"));

            // Extension manager setup
            PM.debug(Localisation.getTranslation("dbgExtensionStart"));
            ExtensionManager.loadExtensions();
            ExtensionManager.enableExtensions();
            PM.debug(Localisation.getTranslation("dbgExtensionStop"));

            PM.log(Level.INFO, Localisation.getTranslation("genPluginEnabled"));
        }
    }

    @Override
    public void onDisable() {
        ExtensionManager.disableExtensions();
    	if (localeState)
    		PM.log(Level.INFO, Localisation.getTranslation("genPluginDisabled"));
    	else
    		PM.log(Level.INFO, "Plugin disabled.");
    }
    
    public boolean onReload() {
        reloadConfig();
        ConfigurationManager.reloadConfigAll();
        Localisation.load();
        ExtensionManager.reloadExtensions();
        this.WandManager = new WandManager(this);
        if (!setupCrafting()) return false;
        return true;
    }
    
    private void setupCharts(Metrics metrics) {
    	try {
    		// Total Amount of Spells Casted
//            metrics.addCustomChart(new Metrics.MultiLineChart("spells_casted_total", () -> {
//            	Map<String, Integer> spellsCastMap = new HashMap<>();
//				spellsCastMap.put("Total", MetricStatistics.getSpellsCast());
//				spellsCastMap.put("Hits", MetricStatistics.getSuccesses());
//				spellsCastMap.put("Misses", MetricStatistics.getFailures());
//				return spellsCastMap;
//            }));
    		
    		metrics.addCustomChart(new Metrics.SingleLineChart("total_spells_casted", () -> MetricStatistics.getSpellsCast()));
    		metrics.addCustomChart(new Metrics.SingleLineChart("total_spells_successes", () -> MetricStatistics.getSuccesses()));
    		metrics.addCustomChart(new Metrics.SingleLineChart("total_spells_failures", () -> MetricStatistics.getFailures()));
    		
            // Amount of Spells Casted per Spell
            metrics.addCustomChart(new Metrics.SimpleBarChart("spells_casted_single", () -> {
            	Map<String, Integer> spellsCastMap = new HashMap<>();
				for (Spell spell : SpellManager.getSpells()) {
					spellsCastMap.put(spell.getName(), MetricStatistics.getAmountOfTimesCast(spell));
				}
				return spellsCastMap;
            } ));
            
            // Language used
            metrics.addCustomChart(new Metrics.SimplePie("Language", () -> Language.getLanuage(getConfig().getString("language")).toString()));