public class JailMain extends JavaPlugin {
    private CommandHandler cmdHand;
    private HandCuffManager hcm;
    private JailHandler jh;
    private JailIO io;
    private JailManager jm;
    private IJailPayManager jpm;
    private IJailStickManager jsm;
    private JailTimer jt;
    private JailVoteManager jvm;
    private PrisonerManager pm;
    private ScoreBoardManager sbm;
    private MoveProtectionListener mpl;
    private Update update;
    private boolean debug = false;
    private int updateCheckTask = -1;

    public void onEnable() {
        long st = System.currentTimeMillis();
        loadConfig();

        debug = getConfig().getBoolean(Settings.DEBUG.getPath());
        if(debug) getLogger().info("Debugging enabled.");

        hcm = new HandCuffManager();
        jm = new JailManager(this);
        io = new JailIO(this);
        io.loadLanguage();

        //If the prepareStorage returns false, we need to disable the plugin
        if(!io.prepareStorage(true)) {
            this.getLogger().severe("An error occured while preparing the connection to the storage, please see the error above for more information.");
            this.getServer().getPluginManager().disablePlugin(this);
            return;
        }

        io.loadJails();

        cmdHand = new CommandHandler(this);
        jh = new JailHandler(this);
        pm = new PrisonerManager(this);
        
        try {
            jvm = new JailVoteManager(this);
        }catch(Exception e) {
            e.printStackTrace();
            getLogger().severe("Failed to load the Jail Vote system, please see the stacktrace above (you probably misconfigured the time).");
        }

        PluginManager plm = this.getServer().getPluginManager();
        plm.registerEvents(new CacheListener(this), this);
        plm.registerEvents(new CellSignListener(this), this);
        plm.registerEvents(new EntityListener(this), this);
        plm.registerEvents(new HandCuffListener(this), this);
        plm.registerEvents(new JailingListener(this), this);
        plm.registerEvents(new PlayerListener(this), this);
        plm.registerEvents(new ProtectionListener(this), this);
        plm.registerEvents(new WorldListener(this), this);

        //Only register the move protection listener if this is enabled in the
        //config when we first start the plugin. The reason for this change is
        //that the move event is called a ton of times per single move and so
        //not registering this event listener will hopefully safe some performance.
        //But doing this also forces people to restart their server if they to
        //enable it after disabling it.
        this.reloadMoveProtection();

        jt = new JailTimer(this);
        sbm = new ScoreBoardManager(this);
        reloadJailPayManager();
        reloadJailSticks();
        reloadUpdateCheck();

        new JailsAPI(this);
        debug("Took " + (System.currentTimeMillis() - st) + " to enable the plugin.");
        getLogger().info("Completed enablement.");
    }

    public void onDisable() {
        if(jm != null)
            for(Jail j : jm.getJails())
                io.saveJail(j);

        if(jt != null)
            if(jt.getTimer() != null)
                jt.getTimer().stop();

        if(io != null)
            io.closeConnection();

        getServer().getScheduler().cancelTasks(this);

        update = null;
        jvm = null;
        jt = null;
        sbm = null;
        jpm = null;
        cmdHand = null;
        pm = null;
        jm = null;
        jsm = null;
        io = null;
        hcm = null;
    }

    private void loadConfig() {
        //Only create the default config if it doesn't exist
        saveDefaultConfig();

        //Append new key-value pairs to the config
        getConfig().options().copyDefaults(true);

        // Set the header and save
        getConfig().options().header(getHeader());
        saveConfig();
    }

    private String getHeader() {
        String sep = System.getProperty("line.separator");

        return "###################" + sep
                + "Jail v" + this.getDescription().getVersion() + " config file" + sep
                + "Note: You -must- use spaces instead of tabs!" + sep +
                "###################";
    }

    /* Majority of the new command system was heavily influenced by the MobArena.
     * Thank you garbagemule for the great system you have in place there.
     *
     * Send the command off to the CommandHandler class, that way this main class doesn't get clogged up.
     */
    public boolean onCommand(CommandSender sender, Command command, String commandLabel, String[] args) {
    	if(jh == null || cmdHand == null) {
    		sender.sendMessage(Lang.PLUGINNOTLOADED.get());
    		getServer().getConsoleSender().sendMessage(Lang.PLUGINNOTLOADED.get());
    	}else {
            if(commandLabel.equalsIgnoreCase("jail") || commandLabel.equalsIgnoreCase("j")) {
            	jh.parseCommand(jm, sender, args);
            }else {
                cmdHand.handleCommand(jm, sender, command.getName().toLowerCase(), args);
            }
    	}

        return true;//Always return true here, that way we can handle the help and command usage ourself.
    }
    
    public List<String> onTabComplete(CommandSender sender, Command command, String commandLabel, String[] args) {
        if(jh == null || cmdHand == null) {
            sender.sendMessage(Lang.PLUGINNOTLOADED.get());
            getServer().getConsoleSender().sendMessage(Lang.PLUGINNOTLOADED.get());
        }else {
            debug("Tab Complete Args (" + args.length + ") for '" + commandLabel + "': " + Util.getStringFromArray(", ", args));
            try {
                if(commandLabel.equalsIgnoreCase("jail") || commandLabel.equalsIgnoreCase("j")) {
                    return jh.parseTabComplete(jm, sender, args);
                }else {
                    return cmdHand.parseTabComplete(jm, sender, command.getName().toLowerCase(), args);
                }
            }catch(Exception e) {
                if(this.debug)
                    e.printStackTrace();
                
                debug(e.getClass().getSimpleName() + " occured while providing tab complete: " + e.getMessage());
                return Collections.emptyList();
            }
        }
        
        return Collections.emptyList();
    }
    
    public void reloadEverything() throws Exception {
        //Reload the configuration file
        reloadConfig();
        //Reload the language
        getJailIO().loadLanguage();
        //Reload the storage settings and set them up if they don't exist.
        //We don't touch any of the data currently being stored in cache,
        //this way you can transfer from flatfile to mysql or flip flopped.
        getJailIO().prepareStorage(true);
        reloadMoveProtection();
        //Reload all that has to do with the scoreboard, name and settings
        reloadScoreBoardManager();
        //Reload the jail sticks
        reloadJailSticks();
        //Reload the jail pay information and settings
        reloadJailPayManager();
        //Reload the jail vote information and settings
        reloadJailVoteManager();
        //Reload the update checking, to turn it on/off and/or change the channel
        reloadUpdateCheck();
        //Let the rest of the plugin(s) know we have reloaded so they can do stuff
        //if they rely on any of the configuration settings (such as signs)
        getServer().getPluginManager().callEvent(new JailPluginReloadedEvent(this));
    }
    
    /** Reloads/Loads the move protection listener based upon it's previous status and the config. */
    private void reloadMoveProtection() {
        boolean moveProtection = getConfig().getBoolean(Settings.MOVEPROTECTION.getPath());
        
        //If move protection is not enabled and it used to be,
        //unregister it.
        if(!moveProtection && this.mpl != null) {
            HandlerList.unregisterAll(this.mpl);
            this.mpl = null;
            this.debug("The move protection listener is now disabled.");
        }
        
        //If it is enabled and it used to not be, then enable it.
        if(moveProtection && this.mpl == null) {
            this.mpl = new MoveProtectionListener(this);
            getServer().getPluginManager().registerEvents(mpl, this);
            this.debug("The move protection listener is now enabled.");
        }
    }

    /** Reloads the scoreboard manager class, useful when something is changed in the config about it. */
    private void reloadScoreBoardManager() {
        this.sbm.removeAllScoreboards();
        this.sbm = null;
        this.sbm = new ScoreBoardManager(this);

        if(getConfig().getBoolean(Settings.SCOREBOARDENABLED.getPath())) {
            for(Jail j : jm.getJails()) {
                for(Prisoner p : j.getAllPrisoners().values()) {
                    if(getServer().getPlayer(p.getUUID()) != null) {
                        this.sbm.addScoreBoard(getServer().getPlayer(p.getUUID()), p);
                    }
                }
            }
        }
    }

    /** Reloads the Jail Sticks, so the new ones can be loaded from the config. */
    private void reloadJailSticks() {
        if(getConfig().getBoolean(Settings.JAILSTICKENABLED.getPath())) {
            if(this.jsm != null) {
                this.jsm.removeAllStickUsers();
                this.jsm = null;
            }

            this.jsm = new JailStickManager(this);
        }
    }

    /** Reloads the {@link JailPayManager}. */
    private void reloadJailPayManager() {
        this.jpm = null;

        if(getConfig().getBoolean(Settings.JAILPAYENABLED.getPath())) {
            if(getServer().getPluginManager().isPluginEnabled("Vault")) {
                this.jpm = new JailPayManager(this);
            }else {
                getConfig().set(Settings.JAILPAYENABLED.getPath(), false);
                getLogger().severe("Jail Pay couldn't find an economy, disabling Jail Pay.");
            }
        }
    }
    
    /** Reloads the {@link JailVoteManager}. */
    private void reloadJailVoteManager() throws Exception {
        if(this.jvm != null) {
            for(Integer i : this.jvm.getRunningTasks().values()) {
                this.getServer().getScheduler().cancelTask(i);
            }
            
            this.jvm.getRunningTasks().clear();
            this.jvm.getVotes().clear();
        }
        
        this.jvm = null;
        this.jvm = new JailVoteManager(this);
    }

    /** Reloads the update checker, in case they changed a setting about it. */
    private void reloadUpdateCheck() {
        getServer().getScheduler().cancelTask(updateCheckTask);
        update = new Update(this);
        if(getConfig().getBoolean(Settings.UPDATENOTIFICATIONS.getPath())) {
            try {
                updateCheckTask = getServer().getScheduler().runTaskTimerAsynchronously(this, new Runnable() {
                    public void run() {
                        update.query();
                    }
                }, 80L, Util.getTime(getConfig().getString(Settings.UPDATETIME.getPath()), TimeUnit.SECONDS) * 20).getTaskId();
            } catch (Exception e) {
                e.printStackTrace();
                getLogger().severe("Was unable to schedule the update checking, please check your time format is correct.");
            }
        }
    }

    /**
     * Gets the {@link HandCuffManager} instance.
     * 
     * @return {@link HandCuffManager} instance
     */
    public HandCuffManager getHandCuffManager() {
        return this.hcm;
    }

    /**
     * Gets the {@link JailIO} instance.
     * 
     * @return {@link JailIO} instance
     */
    public JailIO getJailIO() {
        return this.io;
    }

    /**
     * Gets the {@link JailManager} instance.
     * 
     * @return {@link JailManager} instance
     */
    public JailManager getJailManager() {
        return this.jm;
    }

    /**
     * Gets an instance of the {@link IJailPayManager}.
     * 
     * @return {@link IJailPayManager}
     */
    public IJailPayManager getJailPayManager() {
        return this.jpm;
    }

    /**
     * Gets the {@link PrisonerManager} instance.
     * 
     * @return {@link PrisonerManager} instance
     */
    public PrisonerManager getPrisonerManager() {
        return this.pm;
    }

    /**
     * Gets an instance of the {@link JailStickManager}.
     * 
     * @return {@link JailStickManager}
     */
    public IJailStickManager getJailStickManager() {
        return this.jsm;
    }

    /**
     * Gets the {@link ScoreBoardManager} instance.
     * 
     * @return {@link ScoreBoardManager} instance
     */
    public ScoreBoardManager getScoreBoardManager() {
        return this.sbm;
    }
    
    /**
     * Gets the {@link JailVoteManager} instance.
     * 
     * @return {@link JailVoteManager} instance
     */
    public JailVoteManager getJailVoteManager() {
        return this.jvm;
    }

    /**
     * Gets the {@link Update} instance.
     * 
     * @return {@link Update} instance
     */
    public Update getUpdate() {
        return this.update;
    }

    /**
     * Sets whether the plugin is in debugging or not.
     * 
     * @param debug whether to be in debuggin or not
     * @return Whether we we debugging or not.
     */
    public boolean setDebugging(boolean debug) {
        this.debug = debug;

        //Save whether we are debugging when we disable the plugin
        getConfig().set(Settings.DEBUG.getPath(), this.debug);
        saveConfig();

        return this.debug;
    }

    /**
     * Returns if the plugin is in debug state or not.
     * 
     * @return Whether we are debugging or not.
     */
    public boolean inDebug() {
        return this.debug;
    }

    /**
     * Logs a debugging message to the console if debugging is enabled.
     * 
     * @param message the item to log as debugging
     */
    public void debug(String message) {
        if(inDebug()) getLogger().info("[Debug]: " + message);
    }

    /**
     * This method is only for testing, there is no need to use this.
     * 
     * @return the move protection listener
     * @deprecated
     */
    public MoveProtectionListener getPlayerMoveListener() {
        return this.mpl;
    }
} public class JailManager {
    private JailMain plugin;
    private HashMap<String, Jail> jails;
    private HashMap<String, CreationPlayer> jailCreators;
    private HashMap<String, CreationPlayer> cellCreators;
    private HashMap<String, ConfirmPlayer> confirms;
    private HashMap<UUID, CachePrisoner> cache;
    private JailCreationSteps jcs;
    private CellCreationSteps ccs;

    protected JailManager(JailMain plugin) {
        this.plugin = plugin;
        this.jails = new HashMap<String, Jail>();
        this.jailCreators = new HashMap<String, CreationPlayer>();
        this.cellCreators = new HashMap<String, CreationPlayer>();
        this.confirms = new HashMap<String, ConfirmPlayer>();
        this.cache = new HashMap<UUID, CachePrisoner>();
        this.jcs = new JailCreationSteps();
        this.ccs = new CellCreationSteps();
    }

    /**
     * Returns the instance of the plugin main class.
     * 
     * @return {@link JailMain} instance
     */
    public JailMain getPlugin() {
        return this.plugin;
    }

    /**
     * Returns a HashSet of all the jails.
     * 
     * @return HashSet of all the jail instances.
     */
    public HashSet<Jail> getJails() {
        return new HashSet<Jail>(jails.values());
    }

    /**
     * Returns an array of all the names of the jails.
     * 
     * @return Array of the jail names
     */
    public String[] getJailNames() {
        String[] toReturn = new String[jails.size()];
        
        int count = 0;
        for(Jail j : this.jails.values()) {
            toReturn[count] = j.getName();
            count++;
        }
        
        return toReturn;
    }
    
    /**
     * Gets a list of Jail names that start with the provided prefix.
     * 
     * <p>
     * 
     * If the provided prefix is empty, then we add all of the jails.
     * 
     * @param prefix The start of the jails to get
     * @return List of jails that matched the prefix
     */
    public List<String> getJailsByPrefix(String prefix) {
        List<String> results = new ArrayList<String>();
        
        for(Jail j : this.jails.values())
            if(prefix.isEmpty() || StringUtil.startsWithIgnoreCase(j.getName(), prefix))
                results.add(j.getName());
        
        Collections.sort(results);
        
        return results;
    }

    /**
     * Adds a jail to the collection of them.
     * 
     * @param jail The jail to add
     * @param n True if this is a new jail, false if it isn't.
     */
    public void addJail(Jail jail, boolean n) {
        this.jails.put(jail.getName().toLowerCase(), jail);
        if(n) plugin.getJailIO().saveJail(jail);
    }

    /**
     * Removes a {@link Jail}.
     * 
     * @param name of the jail to remove
     */
    public void removeJail(String name) {
        plugin.getJailIO().removeJail(this.jails.get(name.toLowerCase()));
        this.jails.remove(name.toLowerCase());
    }

    /**
     * Gets a jail by the given name.
     * 
     * @param name The name of the jail to get.
     * @return The {@link Jail} with the given name, if no jail found this <strong>will</strong> return null.
     */
    public Jail getJail(String name) {
        if(name.isEmpty() && jails.isEmpty())
            return null;
        else
            return name.isEmpty() ? this.jails.values().iterator().next() : this.jails.get(name.toLowerCase());
    }

    /**
     * Gets the nearest {@link Jail} to the player, if the sender is a player or else it will get the first jail defined.
     * 
     * @param sender The sender who we are looking around.
     * @return The nearest {@link Jail} to the sender if it is a player or else the first jail defined.
     */
    public Jail getNearestJail(CommandSender sender) {
        if(jails.isEmpty()) return null;
        
        if(sender instanceof Player) {
            Location loc = ((Player) sender).getLocation();

            Jail j = null;
            double len = -1;

            for(Jail jail : jails.values()) {
                double clen = jail.getDistance(loc);

                if (clen < len || len == -1) {
                    len = clen;
                    j = jail;
                }
            }

            return (j == null ? jails.values().iterator().next() : j);
        }else {
            return jails.values().iterator().next();
        }
    }

    /**
     * Gets the jail which this location is in, will return null if none exist.
     * 
     * @param loc to get the jail from
     * @return The jail this block is in, null if no jail found.
     */
    public Jail getJailFromLocation(Location loc) {
        for(Jail j : jails.values()) {
            if(Util.isInsideAB(loc.toVector(), j.getMinPoint().toVector(), j.getMaxPoint().toVector())) {
                return j;
            }
        }

        return null;
    }

    /**
     * Gets whether the location is inside of a Jail.
     * 
     * @param l to determine if is in a jail
     * @return whether it is inside a jail or not
     */
    public boolean isLocationAJail(Location l) {
        for(Jail j : jails.values()) {
            if(Util.isInsideAB(l.toVector(), j.getMinPoint().toVector(), j.getMaxPoint().toVector())) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks to see if the given name for a {@link Jail} is valid, returns true if it is a valid jail.
     * 
     * @param name The name of the jail to check.
     * @return True if a valid jail was found, false if no jail was found.
     */
    public boolean isValidJail(String name) {
        return this.jails.containsKey(name.toLowerCase());
    }

    /**
     * Gets all the {@link Cell cells} in the jail system, best for system wide count of the cells or touching each cell.
     * 
     * @return HashSet of all the Cells.
     */
    public HashSet<Cell> getAllCells() {
        HashSet<Cell> cells = new HashSet<Cell>();

        for(Jail j : jails.values())
            cells.addAll(j.getCells());

        return cells;
    }

    /**
     * Adds a prisoner to the cache.
     * 
     * @param cache object to store
     * @return The same object given
     */
    public CachePrisoner addCacheObject(CachePrisoner cache) {
        plugin.debug("Adding " + cache.getPrisoner().getUUID().toString() + " to the cache.");
        this.cache.put(cache.getPrisoner().getUUID(), cache);
        return this.cache.get(cache.getPrisoner().getUUID());
    }

    /**
     * Checks if the given uuid is in the cache.
     * 
     * @param uuid of the player
     * @return true if in cache, false if not
     */
    public boolean inCache(UUID uuid) {
        return this.cache.containsKey(uuid);
    }

    /**
     * Gets a cached prisoner object.
     * 
     * @param uuid of the prisoner to get
     * @return the cahced prisoner object, will be null if it doesn't exist
     */
    public CachePrisoner getCacheObject(UUID uuid) {
        return this.cache.get(uuid);
    }

    /**
     * Removes the cache object stored for this uuid.
     * 
     * @param uuid of the prisoner to remove
     */
    public void removeCacheObject(UUID uuid) {
        plugin.debug("Removing " + uuid.toString() + " from the cache.");
        this.cache.remove(uuid);
    }

    /**
     * Gets all the prisoners in the system, best for a system wide count of the prisoners or accessing all the prisoners at once.
     * 
     * @return HashSet of Prisoners.
     */
    public HashMap<UUID, Prisoner> getAllPrisoners() {
        HashMap<UUID, Prisoner> prisoners = new HashMap<UUID, Prisoner>();

        for(Jail j : jails.values())
            prisoners.putAll(j.getAllPrisoners());

        return prisoners;
    }

    /**
     * Gets the {@link Jail jail} the given prisoner is in.
     * 
     * @param prisoner The prisoner data for the prisoner we are checking
     * @return The jail the player is in, <strong>CAN BE NULL</strong>.
     */
    public Jail getJailPrisonerIsIn(Prisoner prisoner) {
        if(prisoner == null) return null;
        else return getJailPlayerIsIn(prisoner.getUUID());
    }

    /**
     * Gets the {@link Jail jail} the given player is in.
     * 
     * <p>
     * 
     * Checks the cache first.
     * 
     * @param uuid The uuid of the player who's jail we are getting.
     * @return The jail the player is in, <strong>CAN BE NULL</strong>.
     */
    public Jail getJailPlayerIsIn(UUID uuid) {
        if(this.cache.containsKey(uuid)) {
            plugin.debug(uuid.toString() + " is in the cache (getJailPlayerIsIn).");
            return this.cache.get(uuid).getJail();
        }

        for(Jail j : jails.values())
            if(j.isPlayerJailed(uuid))
                return j;

        return null;
    }

    /**
     * Gets if the given uuid of a player is jailed or not, in all the jails and cells.
     * 
     * @param uuid The uuid of the player to check.
     * @return true if they are jailed, false if not.
     */
    public boolean isPlayerJailed(UUID uuid) {
        return getJailPlayerIsIn(uuid) != null;
    }

    /**
     * Gets the {@link Prisoner} data from for this user, if they are jailed.
     * 
     * @param uuid The uuid of prisoner who's data to get
     * @return {@link Prisoner prisoner} data.
     */
    public Prisoner getPrisoner(UUID uuid) {
        Jail j = getJailPlayerIsIn(uuid);

        return j == null ? null : j.getPrisoner(uuid);
    }

    /**
     * Gets the {@link Jail} the player is in from their last known username, null if not jailed.
     * 
     * @param username Last known username to search by
     * @return {@link Jail jail} player is in
     */
    public Jail getJailPlayerIsInByLastKnownName(String username) {
        for(Jail j : jails.values())
            for(Prisoner p : j.getAllPrisoners().values())
                if(p.getLastKnownName().equalsIgnoreCase(username))
                    return j;

        return null;
    }

    /**
     * Gets the {@link Prisoner}'s data from the last known username, returning null if no prisoner has that name.
     * 
     * @param username Last known username to go by
     * @return {@link Prisoner prisoner} data
     */
    public Prisoner getPrisonerByLastKnownName(String username) {
        for(Prisoner p : this.getAllPrisoners().values())
            if(p.getLastKnownName().equalsIgnoreCase(username))
                return p;

        return null;
    }

    /**
     * Checks if the provided username is jailed, using last known username.
     * 
     * @param username Last known username to go by
     * @return true if they are jailed, false if not
     */
    public boolean isPlayerJailedByLastKnownUsername(String username) {
        return this.getPrisonerByLastKnownName(username) != null;
    }

    /**
     * Clears a {@link Jail} of all its prisoners if the jail is provided, otherwise it releases all the prisoners in all the jails.
     * 
     * @param jail The name of the jail to release the prisoners in, null if wanting to clear all.
     * @return The resulting message to be sent to the caller of this method.
     */
    public String clearJailOfPrisoners(String jail) {
        //If they don't pass in a jail name, clear all the jails
        if(jail != null) {
            Jail j = getJail(jail);

            if(j != null) {
                for(Prisoner p : j.getAllPrisoners().values()) {
                    getPlugin().getPrisonerManager().schedulePrisonerRelease(p);
                }

                return Lang.PRISONERSCLEARED.get(j.getName());
            }else {
                return Lang.NOJAIL.get(jail);
            }
        }else {
            return clearAllJailsOfAllPrisoners();
        }
    }

    /**
     * Clears all the {@link Jail jails} of prisoners by releasing them.
     * 
     * @return The resulting message to be sent to the caller of this method.
     */
    public String clearAllJailsOfAllPrisoners() {
        //No name of a jail has been passed, so release all of the prisoners in all the jails
        if(getJails().size() == 0) {
            return Lang.NOJAILS.get();
        }else {
            for(Jail j : getJails()) {
                for(Prisoner p : j.getAllPrisoners().values()) {
                    getPlugin().getPrisonerManager().schedulePrisonerRelease(p);
                }
            }

            return Lang.PRISONERSCLEARED.get(Lang.ALLJAILS);
        }
    }

    /**
     * Forcefully clears all the jails if name provided is null.
     * 
     * <p>
     * 
     * This method just clears them from the storage, doesn't release them.
     * 
     * @param name of the jail to clear, null if all of them.
     * @return The resulting message to be sent to the caller of this method.
     */
    public String forcefullyClearJailOrJails(String name) {
        if(name == null) {
            if(getJails().size() == 0) {
                return Lang.NOJAILS.get();
            }else {
                for(Jail j : getJails()) {
                    j.clearPrisoners();
                }

                return Lang.PRISONERSCLEARED.get(Lang.ALLJAILS);
            }
        }else {
            Jail j = getJail(name);

            if(j != null) {
                j.clearPrisoners();
                return Lang.PRISONERSCLEARED.get(j.getName());
            }else {
                return Lang.NOJAIL.get(name);
            }
        }
    }

    /**
     * Deletes a jail's cell, checking everything is setup right for it to be deleted.
     * 
     * @param jail Name of the jail to delete a cell in.
     * @param cell Name of the cell to delete.
     * @return The resulting message to be sent to the caller of this method.
     */
    public String deleteJailCell(String jail, String cell) {
        //Check if the jail name provided is a valid jail
        if(isValidJail(jail)) {
            Jail j = getJail(jail);

            //check if the cell is a valid cell
            if(j.isValidCell(cell)) {
                if(j.getCell(cell).hasPrisoner()) {
                    //The cell has a prisoner, so tell them to first transfer the prisoner
                    //or release the prisoner
                    return Lang.CELLREMOVALUNSUCCESSFUL.get(new String[] { cell, jail });
                }else {
                    j.removeCell(cell);
                    return Lang.CELLREMOVED.get(new String[] { cell, jail });
                }
            }else {
                //No cell found by the provided name in the stated jail
                return Lang.NOCELL.get(new String[] { cell, jail });
            }
        }else {
            //No jail found by the provided name
            return Lang.NOJAIL.get(jail);
        }
    }

    /**
     * Deletes all the cells in a jail, returns a list of Strings.
     * 
     * @param jail The name of the jail to delete all the jails in.
     * @return An array of strings of messages to send.
     */
    public String[] deleteAllJailCells(String jail) {
        LinkedList<String> msgs = new LinkedList<String>();

        //Check if the jail name provided is a valid jail
        if(isValidJail(jail)) {
            Jail j = getJail(jail);

            if(j.getCellCount() == 0) {
                //There are no cells in this jail, thus we can't delete them.
                msgs.add(Lang.NOCELLS.get(j.getName()));
            }else {
                //Keep a local copy of the hashset so that we don't get any CMEs.
                HashSet<Cell> cells = new HashSet<Cell>(j.getCells());

                for(Cell c : cells) {
                    if(c.hasPrisoner()) {
                        //The cell has a prisoner, so tell them to first transfer the prisoner
                        //or release the prisoner
                        msgs.add(Lang.CELLREMOVALUNSUCCESSFUL.get(new String[] { c.getName(), j.getName() }));
                    }else {
                        j.removeCell(c.getName());
                        msgs.add(Lang.CELLREMOVED.get(new String[] { c.getName(), j.getName() }));
                    }
                }
            }
        }else {
            //No jail found by the provided name
            msgs.add(Lang.NOJAIL.get(jail));
        }

        return msgs.toArray(new String[msgs.size()]);
    }

    /**
     * Deletes a jail while doing some checks to verify it can be deleted.
     * 
     * @param jail The name of the jail to delete.
     * @return The resulting message to be sent to the caller of this method.
     */
    public String deleteJail(String jail) {
        //Check if the jail name provided is a valid jail
        if(isValidJail(jail)) {
            //check if the jail doesn't contain prisoners
            if(getJail(jail).getAllPrisoners().size() == 0) {
                //There are no prisoners, so we can delete it
                removeJail(jail);
                return Lang.JAILREMOVED.get(jail);
            }else {
                //The jail has prisoners, they need to release them first
                return Lang.JAILREMOVALUNSUCCESSFUL.get(jail);
            }
        }else {
            //No jail found by the provided name
            return Lang.NOJAIL.get(jail);
        }
    }

    /**
     * Returns whether or not the player is creating a jail or a cell.
     * 
     * <p>
     * 
     * If you want to check to see if they're just creating a jail then use {@link #isCreatingAJail(String) isCreatingAJail} or if you want to see if they're creating a cell then use {@link #isCreatingACell(String) isCreatingACell}.
     * 
     * @param name The name of the player, in any case as we convert it to lowercase.
     * @return True if the player is creating a jail or cell, false if they're not creating anything.
     */
    public boolean isCreatingSomething(String name) {
        return this.jailCreators.containsKey(name.toLowerCase()) || this.cellCreators.containsKey(name.toLowerCase());
    }

    /**
     * Returns a message used for telling them what they're creating and what step they're on.
     * 
     * @param player the name of the player to check
     * @return The details for the step they're on
     */
    public String getStepMessage(String player) {
        String message = "";

        if(isCreatingACell(player)) {//Check whether it is a jail cell
            CreationPlayer cp = this.getCellCreationPlayer(player);
            message = "You're already creating a Cell with the name '" + cp.getCellName() + "' and you still need to ";

            switch(cp.getStep()) {
                case 1:
                    message += "set the teleport in location.";
                    break;
                case 2:
                    message += "select all the signs.";
                    break;
                case 3:
                    message += "set the double chest location.";
                    break;
            }

        }else if(isCreatingAJail(player)) {//If not a cell, then check if a jail.
            CreationPlayer cp = this.getJailCreationPlayer(player);
            message = "You're already creating a Jail with the name '" + cp.getJailName() + "' and you still need to ";

            switch(cp.getStep()) {
                case 1:
                    message += "select the first point.";
                    break;
                case 2:
                    message += "select the second point.";
                    break;
                case 3:
                    message += "set the teleport in location.";
                    break;
                case 4:
                    message += "set the release location.";
                    break;
            }
        }

        return message;
    }

    /**
     * Returns whether or not someone is creating a <strong>Jail</strong>.
     * 
     * @param name the player's name to check
     * @return Whether they are creating a jail or not.
     */
    public boolean isCreatingAJail(String name) {
        return this.jailCreators.containsKey(name.toLowerCase());
    }

    /**
     * Method for setting a player to be creating a Jail, returns whether or not they were added successfully.
     * 
     * @param player The player who is creating a jail.
     * @param jailName The name of the jail we are creating.
     * @return True if they were added successfully, false if they are already creating a Jail.
     */
    public boolean addCreatingJail(String player, String jailName) {
        if(isCreatingAJail(player)) {
            return false;
        }else {
            this.jailCreators.put(player.toLowerCase(), new CreationPlayer(jailName));
            return true;
        }
    }

    /**
     * Returns the instance of the CreationPlayer for this player, null if there was none found.
     * 
     * @param name the player's name
     * @return gets the player's {@link CreationPlayer} instance
     */
    public CreationPlayer getJailCreationPlayer(String name) {
        return this.jailCreators.get(name.toLowerCase());
    }

    /**
     * Removes a CreationPlayer with the given name from the jail creators.
     * 
     * @param name player's name to remove
     */
    public void removeJailCreationPlayer(String name) {
        this.jailCreators.remove(name.toLowerCase());
    }

    /**
     * Returns whether or not someone is creating a <strong>Cell</strong>.
     * 
     * @param name the player's name to check
     * @return Whether they are creating a jail cell or not.
     */
    public boolean isCreatingACell(String name) {
        return this.cellCreators.containsKey(name.toLowerCase());
    }

    /**
     * Method for setting a player to be creating a Cell, returns whether or not they were added successfully.
     * 
     * @param player The player who is creating a jail.
     * @param jailName The name of the jail this cell is going.
     * @param cellName The name of the cell we are creating.
     * @return True if they were added successfully, false if they are already creating a Jail.
     */
    public boolean addCreatingCell(String player, String jailName, String cellName) {
        if(isCreatingACell(player)) {
            return false;
        }else {
            this.cellCreators.put(player.toLowerCase(), new CreationPlayer(jailName, cellName));
            return true;
        }
    }

    /**
     * Returns the instance of the CreationPlayer for this player, null if there was none found.
     * 
     * @param name the player's name to get
     * @return The player's {@link CreationPlayer} instance.
     */
    public CreationPlayer getCellCreationPlayer(String name) {
        return this.cellCreators.get(name.toLowerCase());
    }

    /**
     * Removes a CreationPlayer with the given name from the cell creators.
     * 
     * @param name player's name to remove
     */
    public void removeCellCreationPlayer(String name) {
        this.cellCreators.remove(name.toLowerCase());
    }

    /**
     * Gets the instance of the {@link JailCreationSteps}.
     * 
     * @return {@link JailCreationSteps} instance
     */
    public JailCreationSteps getJailCreationSteps() {
        return this.jcs;
    }

    /**
     * Gets the instance of the {@link CellCreationSteps}.
     * 
     * @return the {@link CellCreationSteps} instance
     */
    public CellCreationSteps getCellCreationSteps() {
        return this.ccs;
    }

    /**
     * Adds something to the confirming list.
     * 
     * @param name who to add
     * @param confirmer {@link ConfirmPlayer} of what they're confirming
     */
    public void addConfirming(String name, ConfirmPlayer confirmer) {
        getPlugin().debug("Adding a confirming for " + name + " to confirm " + confirmer.getConfirming().toString().toLowerCase());
        this.confirms.put(name, confirmer);
    }

    /**
     * Removes a name from the confirming list.
     * 
     * @param name who to remove
     */
    public void removeConfirming(String name) {
        this.confirms.remove(name);
    }

    /**
     * Checks if the given name is confirming something.
     * 
     * @param name the player's name
     * @return Whether they are confirming something or not
     */
    public boolean isConfirming(String name) {
        return this.confirms.containsKey(name);
    }

    /**
     * Returns true if the confirmation has expired, false if it is still valid.
     * 
     * @param name the player's name
     * @return Whether their confirmation has expired or not.
     */
    public boolean confirmingHasExpired(String name) {
        //If the expiry time is LESS than the current time, it has expired
        return this.confirms.get(name).getExpiryTime() < System.currentTimeMillis();
    }

    /**
     * Returns the original arguments for what we are confirming.
     * 
     * @param name the player's name
     * @return an array of strings which is their original arguments
     */
    public String[] getOriginalArgs(String name) {
        return this.confirms.get(name).getArguments();
    }

    /**
     * Returns what the given name is confirming.
     * 
     * @param name the player's name
     * @return What they are confirming
     */
    public Confirmation getWhatIsConfirming(String name) {
        return this.confirms.get(name).getConfirming();
    }
} @CommandInfo(
        maxArgs = 1,
        minimumArgs = 1,
        needsPlayer = false,
        pattern = "check",
        permission = "jail.command.jailcheck",
        usage = "/jail check [name]"
        )
public class JailCheckCommand implements Command {

    // Checks the status of the specified prisoner
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        //Otherwise let's check the first argument
        if(jm.isPlayerJailedByLastKnownUsername(args[1])) {
            Prisoner p = jm.getPrisonerByLastKnownName(args[1]);

            //graywolf663: Being gray's evil twin; CONSOLE (10)
            //prisoner: reason; jailer (time in minutes)
            sender.sendMessage(ChatColor.BLUE + " " + p.getLastKnownName() + ": " + p.getReason() + "; " + p.getJailer() + " (" + Util.getDurationBreakdown(p.getRemainingTime()) + " )");
        }else {
            sender.sendMessage(Lang.NOTJAILED.get(args[1]));
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        List<String> results = new ArrayList<String>();
        
        for(Prisoner p : jm.getAllPrisoners().values()) {
            if(args.length == 2 && StringUtil.startsWithIgnoreCase(p.getLastKnownName(), args[1].toLowerCase())) {
                results.add(p.getLastKnownName());
            }else {
                results.add(p.getLastKnownName());
            }
        }
        
        Collections.sort(results);
        
        if(args.length == 2)
            for(Player p : jm.getPlugin().getServer().getOnlinePlayers())
                if(args[1].isEmpty() || StringUtil.startsWithIgnoreCase(p.getName(), args[1]))
                    if(!results.contains(p.getName()))
                        results.add(p.getName());
        
        return results;
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 0,
        needsPlayer = false,
        pattern = "clear|clearforce",
        permission = "jail.command.jailclear",
        usage = "/jail clear (-f) (jail)"
        )
public class JailClearCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        boolean force = false;

        //Check if we need to forcefully clear something
        for(String s : args)
            if(s.equalsIgnoreCase("-f") || s.equalsIgnoreCase("-force"))
                force = true;

        if(jm.isConfirming(sender.getName())) {
            sender.sendMessage(Lang.ALREADY.get());
        }else if(force && sender.hasPermission("jail.command.jailclearforce")) {
            jm.addConfirming(sender.getName(), new ConfirmPlayer(sender.getName(), args, Confirmation.CLEARFORCE));
            sender.sendMessage(Lang.START.get());
        }else {
            jm.addConfirming(sender.getName(), new ConfirmPlayer(sender.getName(), args, Confirmation.CLEAR));
            sender.sendMessage(Lang.START.get());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        List<String> results = new ArrayList<String>();
        
        for(Jail j : jm.getJails()) {
            if((args.length == 2 && StringUtil.startsWithIgnoreCase(j.getName(), args[1]))
                    || (args.length == 3 && StringUtil.startsWithIgnoreCase(j.getName(), args[2]))) {
                results.add(j.getName());
            }else {
                results.add(j.getName());
            }
        }
        
        Collections.sort(results);
        
        return results;
    }
} @CommandInfo(
        maxArgs = -1,
        minimumArgs = 0,
        needsPlayer = false,
        pattern = "jail|j",
        permission = "jail.command.jail",
        usage = "/jail [name] (-t time) (-j JailName) (-c CellName) (-a AnyCell) (-m Muted) (-r A reason for jailing)"
        )
public class JailCommand implements Command {
    private static final String noJailPermission = "jail.cantbejailed";
    private List<String> commands = Arrays.asList(new String[] { "p", "t", "i", "j", "c", "a", "m", "r", "f" });

    /*
     * Executes the command. Checks the following:
     * 
     * - If there are any jails.
     * - If the command can be parsed correctly.
     * - If the player is already jailed.
     * - If the given time can be parsed correctly, defaults to what is defined in the config
     * - If the jail is reasonable or not, else sets the one from the config
     * - If the cell is not empty then checks to be sure that cell exists
     * - If the prisoner is online or not.
     */
    @SuppressWarnings("deprecation")
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        if(jm.getJails().isEmpty()) {
            sender.sendMessage(Lang.NOJAILS.get());
            return true;
        }

        //This is just to add the -p param so CliFactory doesn't blow up
        List<String> arguments = new LinkedList<String>(Arrays.asList(args));
        //Only add the "-p" if it doesn't already contain it, this way people can do `/jail -p check` in the event someone
        //has a name which is one of our subcommands
        if(!arguments.contains("-p")) arguments.add(0, "-p");

        Jailing params = null;

        try {
            params = CliFactory.parseArguments(Jailing.class, arguments.toArray(new String[arguments.size()]));
        }catch(ArgumentValidationException e) {
            sender.sendMessage(ChatColor.RED + e.getMessage());
            return true;
        }

        //Check if they've actually given us a player to jail
        if(params.getPlayer() == null) {
            sender.sendMessage(Lang.PROVIDEAPLAYER.get(Lang.JAILING));
            return true;
        }else {
            jm.getPlugin().debug("We are getting ready to handle jailing: " + params.getPlayer());
        }

        //Check if the given player is already jailed or not
        if(jm.isPlayerJailedByLastKnownUsername(params.getPlayer())) {
            sender.sendMessage(Lang.ALREADYJAILED.get(params.getPlayer()));
            return true;
        }
        
        Player p = jm.getPlugin().getServer().getPlayer(params.getPlayer());
        
        //If the player instance is not null and the player has the permission
        //'jail.cantbejailed' then don't allow this to happen
        if(p != null && p.hasPermission(noJailPermission)) {
            sender.sendMessage(Lang.CANTBEJAILED.get());
            return true;
        }
        
        String uuid = "";
        if(p == null) {
            if (!jm.getPlugin().getConfig().getBoolean(Settings.ALLOWJAILINGOFFLINEPLAYERS.getPath())) {
                sender.sendMessage(Lang.PLAYERHASNEVERPLAYEDBEFORE.get());
                return true;
            }

            //TODO: Make this whole jail command non-blocking
            OfflinePlayer of = jm.getPlugin().getServer().getOfflinePlayer(params.getPlayer());
            
            if(!of.hasPlayedBefore() && !jm.getPlugin().getConfig().getBoolean(Settings.ALLOWJAILINGNEVERPLAYEDBEFOREPLAYERS.getPath()) && !params.isForce()) {
                sender.sendMessage(Lang.PLAYERHASNEVERPLAYEDBEFORE.get(params.getPlayer()));
                return true;
            }else {
                uuid = of.getUniqueId().toString();
            }
        }else {
            uuid = p.getUniqueId().toString();
        }

        //Try to parse the time, if they give us nothing in the time parameter then we get the default time
        //from the config and if that isn't there then we default to thirty minutes.
        Long time = 10L;
        try {
            if(params.isTime()) {
                time = Util.getTime(params.getTime());
            }else {
                time = Util.getTime(jm.getPlugin().getConfig().getString(Settings.DEFAULTTIME.getPath(), "30m"));
            }
        }catch(Exception e) {
            sender.sendMessage(Lang.NUMBERFORMATINCORRECT.get());
            return true;
        }
        
        //Check if they provided the infinite argument
        //if so, then set the time jailed forever
        if(params.isInfinite()) {
            time = -1L;
        }

        //Check the jail params. If it is empty, let's get the default jail
        //from the config. If that is nearest, let's make a call to getting the nearest jail to
        //the sender but otherwise if it isn't nearest then let's set it to the default jail
        //which is defined in the config.
        String jailName = "";
        if(!params.isJail()) {
            String dJail = jm.getPlugin().getConfig().getString(Settings.DEFAULTJAIL.getPath());

            if(dJail.equalsIgnoreCase("nearest")) {
                jailName = jm.getNearestJail(sender).getName();
            }else {
                jailName = dJail;
            }
        }else if(!jm.isValidJail(params.getJail())) {
            sender.sendMessage(Lang.NOJAIL.get(params.getJail()));
            return true;
        }else {
            jailName = params.getJail();
        }

        //Get the jail instance from the name of jail in the params.
        Jail j = jm.getJail(jailName);
        if(!j.isEnabled()) {
            sender.sendMessage(Lang.WORLDUNLOADED.get(j.getName()));
            return true;
        }

        ICell c = null;
        //Check if the cell is defined
        if(params.isCell()) {
            //Check if it is a valid cell
            if(!jm.getJail(jailName).isValidCell(params.getCell())) {
                //There is no cell by that name
                sender.sendMessage(Lang.NOCELL.get(new String[] { params.getCell(), jailName }));
                return true;
            }else if(jm.getJail(jailName).getCell(params.getCell()).hasPrisoner()) {
                //If the cell has a prisoner, don't allow jailing them to that particular cell but suggest another one
                sender.sendMessage(Lang.CELLNOTEMPTY.get(params.getCell()));
                Cell suggestedCell = jm.getJail(jailName).getFirstEmptyCell();
                if(suggestedCell != null) {
                    sender.sendMessage(Lang.SUGGESTEDCELL.get(new String[] { jailName, suggestedCell.getName() }));
                }else {
                    sender.sendMessage(Lang.NOEMPTYCELLS.get(jailName));
                }

                return true;
            }else {
                c = jm.getJail(jailName).getCell(params.getCell());
            }
        }

        //If they want just any open cell or automatic jailing in cells is turned on
        //and a cell wasn't already found, then find try to find a cell
        if(params.isAnyCell() && c == null) {
            c = jm.getJail(jailName).getFirstEmptyCell();
            if(c == null) {
                //If there wasn't an empty cell, then tell them so.
                sender.sendMessage(Lang.NOEMPTYCELLS.get(jailName));
                return true;
            }
        }else if(jm.getPlugin().getConfig().getBoolean(Settings.AUTOMATICCELL.getPath(), true) && j.hasCells() && c == null) {
            c = jm.getJail(jailName).getFirstEmptyCell();
            if(c == null) {
                //If there wasn't an empty cell, then tell them so.
                sender.sendMessage(Lang.NOEMPTYCELLS.get(jailName));
                return true;
            }
        }

        //If the jailer gave no reason, then let's get the default reason
        String reason = "";
        if(params.isReason()) {
            StringBuilder sb = new StringBuilder();
            for(String s : params.getReason()) {
                sb.append(s).append(' ');
            }

            sb.deleteCharAt(sb.length() - 1);
            reason = sb.toString();
            
        }else {
        	reason = Lang.DEFAULTJAILEDREASON.get();
        }

        //If the config has automatic muting, then let's set them as muted
        boolean muted = params.getMuted();
        if(jm.getPlugin().getConfig().getBoolean(Settings.AUTOMATICMUTE.getPath())) {
            muted = true;
        }

        Prisoner pris = new Prisoner(uuid, params.getPlayer(), muted, time, sender.getName(), reason);

        //call the event
        PrePrisonerJailedEvent event = new PrePrisonerJailedEvent(j, c, pris, p, p == null, pris.getJailer());
        jm.getPlugin().getServer().getPluginManager().callEvent(event);

        //check if the event is cancelled
        if(event.isCancelled()) {
            if(event.getCancelledMessage().isEmpty())
                sender.sendMessage(Lang.CANCELLEDBYANOTHERPLUGIN.get(params.getPlayer()));
            else
                sender.sendMessage(event.getCancelledMessage());

            return true;
        }

        //recall data from the event
        j = event.getJail();
        c = event.getCell();
        pris = event.getPrisoner();
        p = event.getPlayer();

        //Player is not online
        if(p == null) {
            sender.sendMessage(Lang.OFFLINEJAIL.get(new String[] { pris.getLastKnownName(), String.valueOf(pris.getRemainingTimeInMinutes()) }));
        }else {
            //Player *is* online
            sender.sendMessage(Lang.ONLINEJAIL.get(new String[] { pris.getLastKnownName(), String.valueOf(pris.getRemainingTimeInMinutes()) }));
        }

        try {
            jm.getPlugin().getPrisonerManager().prepareJail(j, c, p, pris);
        } catch (Exception e) {
            sender.sendMessage(ChatColor.RED + e.getMessage());
            return true;
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //by the time it gets to this command it'll have at least two arguments
        String last = args[args.length - 1];
        
        jm.getPlugin().debug("The last item is: " + last);
        
        if(last.isEmpty() || !commands.contains(last.replace("-", ""))) {
            //the current part is empty. Need to look at their previous
            //item and if it is a valid option, then provide them a valid tab complete option
            if(args.length - 2 > -1) {
                String previous = args[args.length - 2];
                jm.getPlugin().debug("args[args.length - 2]: " + previous);
                
                if(previous.equalsIgnoreCase("-p")) return getPlayers(jm, last);
                else if(previous.equalsIgnoreCase("-j")) return jm.getJailsByPrefix(last);
                else if(previous.equalsIgnoreCase("-c")) {
                    //Since we need to give them a list of the cells in a jail
                    //we need to get the jail they're giving
                    int jailIndex = ArrayUtils.indexOf(args, "-j");
                    if(jailIndex != -1) {
                        String jail = args[jailIndex + 1];
                        jm.getPlugin().debug("The jail is: " + jail);
                        if(jm.isValidJail(jail)) return getCells(jm, jail, last);
                    }
                }else if(previous.endsWith("r")) return Collections.emptyList();
                else if(!commands.contains(args[args.length - 2].replace("-", ""))) return Util.getUnusedItems(commands, args, false);
            }else {
            	return getPlayers(jm, last);
            }
        }else if(last.equalsIgnoreCase("-")) {
            //add some smart checking so that it only returns a list of what isn't already
            //in the command :)
            return Util.getUnusedItems(commands, args, false);
        }else {
        	jm.getPlugin().debug("Getting the list of online players.");
            return getPlayers(jm, last);
        }
        
        jm.getPlugin().debug("Returning an empty list.");
        return Collections.emptyList();
    }
    
    private List<String> getPlayers(JailManager jm, String first) {
        List<String> results = new ArrayList<String>();
        
        for(Player p : jm.getPlugin().getServer().getOnlinePlayers())
            if(first.isEmpty() || StringUtil.startsWithIgnoreCase(p.getName(), first))
                if(!jm.isPlayerJailed(p.getUniqueId()) && !p.hasPermission(noJailPermission)) //don't send back them if they're already jailed or can't be jailed
                    results.add(p.getName());
        
        Collections.sort(results);
        jm.getPlugin().debug("The list we're returning is: " + Util.getStringFromList(", ", results));
        
        return results;
    }
    
    private List<String> getCells(JailManager jm, String jail, String cell) {
        List<String> results = new ArrayList<String>();
        
        for(Cell c : jm.getJail(jail).getCells())
            if(!c.hasPrisoner() && (cell.isEmpty() || StringUtil.startsWithIgnoreCase(c.getName(), cell)))
            	results.add(c.getName());
        
        Collections.sort(results);
        
        return results;
    }
} @CommandInfo(
        maxArgs = 0,
        minimumArgs = 0,
        needsPlayer = false,
        pattern = "confirm|con",
        permission = "",
        usage = "/jail confirm"
        )
public class JailConfirmCommand implements Command{

    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        //Check if the sender is actually confirming something.
        if(jm.isConfirming(sender.getName())) {
            if(jm.confirmingHasExpired(sender.getName())) {
                //Their confirmation time frame has closed
                sender.sendMessage(Lang.EXPIRED.get());
                jm.removeConfirming(sender.getName());
            }else {
                switch(jm.getWhatIsConfirming(sender.getName())) {
                    case CLEAR:
                        //Copy the original arguments for easy access
                        String[] cArgs = jm.getOriginalArgs(sender.getName());
                        //Clear a jail if the args length is two, else send null
                        String msg = jm.clearJailOfPrisoners(cArgs.length == 2 ? cArgs[1] : null);
                        //Send the message we got back
                        sender.sendMessage(msg);
                        //Remove them from confirming so they can't do it again
                        jm.removeConfirming(sender.getName());
                        break;
                    case CLEARFORCE:
                        //Copy the original arguments for easy access
                        String[] cArgs2 = jm.getOriginalArgs(sender.getName());
                        //Forcefully clear a jail if the args length is two, else send null to clear all
                        String msg2 = jm.forcefullyClearJailOrJails(cArgs2.length == 2 ? cArgs2[1] : null);
                        //Send the message we got back
                        sender.sendMessage(msg2);
                        jm.removeConfirming(sender.getName());
                        break;
                    case DELETECELL:
                        //Copy the original arguments for easy access
                        String[] cArgs3 = jm.getOriginalArgs(sender.getName());
                        //delete a cell from a jail with the given arguments
                        String msg3 = jm.deleteJailCell(cArgs3[1], cArgs3[2]);
                        //Send the message we got back
                        sender.sendMessage(msg3);
                        jm.removeConfirming(sender.getName());
                        break;
                    case DELETECELLS:
                        //Copy the original arguments for easy access
                        String[] cArgs4 = jm.getOriginalArgs(sender.getName());
                        //delete a cell from a jail with the given arguments
                        String[] msgs4 = jm.deleteAllJailCells(cArgs4[1]);
                        //Send the messages we got back
                        for(String s : msgs4) {
                            sender.sendMessage(s);
                        }

                        jm.removeConfirming(sender.getName());
                        break;
                    case DELETE:
                        //Copy the original arguments for easy access
                        String[] cArgs5 = jm.getOriginalArgs(sender.getName());
                        //delete a cell from a jail with the given arguments
                        String msg5 = jm.deleteJail(cArgs5[1]);
                        //Send the message we got back
                        sender.sendMessage(msg5);
                        jm.removeConfirming(sender.getName());
                        break;
                    default:
                        sender.sendMessage(Lang.NOTHING.get());
                        jm.removeConfirming(sender.getName());
                        break;
                }
            }
        }else {
            //They aren't confirming anything right now.
            sender.sendMessage(Lang.NOTHING.get());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //confirming doesn't require any tab completing
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 1,
        needsPlayer = true,
        pattern = "createcells|createcell|cellcreate|cellscreate|cc",
        permission = "jail.command.jailcreatecells",
        usage = "/jail createcell [jail] (cellname)"
        )
public class JailCreateCellCommand implements Command {

    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        Player player = (Player) sender;
        String name = player.getName();
        String jail = args[1];
        String cell = "";

        //Only get the cell name they provide if they provide it
        if(args.length >= 3) {
            cell = args[2];
        }

        //Check if the player is currently creating something else
        if(jm.isCreatingSomething(name)) {
            String message = jm.getStepMessage(name);
            if(!message.isEmpty()) {
                player.sendMessage(ChatColor.RED + message);
            }else {
                player.sendMessage(ChatColor.RED + "You're already creating something else, please finish it or cancel.");
            }
        }else {
            //Not creating anything, so let them create some cells.
            if(jm.isValidJail(jail)) {
                Jail j = jm.getJail(jail);

                //If they didn't provide a cell name, let's provide one ourself.
                if(cell.isEmpty()) cell = "cell_n" + (j.getCellCount() + 1);

                if(j.getCell(cell) == null) {
                    if(jm.addCreatingCell(name, jail, cell)) {
                        jm.getCellCreationSteps().startStepping(player);
                    }else {
                        player.sendMessage(ChatColor.RED + "Appears you're creating a cell or something went wrong on our side.");
                    }
                }else {
                    player.sendMessage(ChatColor.RED + "There's already a cell with the name '" + cell + "', please pick a new one or remove that cell.");
                }
            }else {
                player.sendMessage(ChatColor.RED + "No such jail found by the name of '" + jail + "'.");
            }
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //We shouldn't provide when they want a cell name
        if(args.length >= 3) return Collections.emptyList();
        
        return jm.getJailsByPrefix(args.length == 2 ? args[1] : "");
    }
} @CommandInfo(
        maxArgs = 1,
        minimumArgs = 1,
        needsPlayer = true,
        pattern = "create",
        permission = "jail.command.jailcreate",
        usage = "/jail create [name]"
        )
public class JailCreateCommand implements Command {

    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        Player player = (Player) sender;
        String name = player.getName();
        String jail = args[1];

        //Check if the player is currently creating something else
        if(jm.isCreatingSomething(name)) {
            String message = jm.getStepMessage(name);
            if(!message.isEmpty()) {
                player.sendMessage(ChatColor.RED + message);
            }else {
                player.sendMessage(ChatColor.RED + "You're already creating something else, please finish it or cancel.");
            }
        }else {
            if(jm.isValidJail(jail)) {
                player.sendMessage(ChatColor.RED + "Jail by the name of '" + jail + "' already exist!");
            }else {
                if(jm.addCreatingJail(name, jail)) {
                    jm.getJailCreationSteps().startStepping(player);
                }else {
                    player.sendMessage(ChatColor.RED + "Seems like you're already creating a Jail or something went wrong on our side.");
                }
            }
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //Creating a jail shouldn't provide tab completion
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 2,
        needsPlayer = false,
        pattern = "deletecell|dc",
        permission = "jail.command.jaildeletecell",
        usage = "/jail deletecell [jail] [cell]"
        )
public class JailDeleteCellCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.isConfirming(sender.getName())) {
            sender.sendMessage(Lang.ALREADY.get());
        }else {
            jm.addConfirming(sender.getName(), new ConfirmPlayer(sender.getName(), args, Confirmation.DELETECELL));
            sender.sendMessage(Lang.START.get());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        List<String> results = new ArrayList<String>();
        
        switch(args.length) {
            case 1:
                results.addAll(jm.getJailsByPrefix(""));
                break;
            case 2:
                results = jm.getJailsByPrefix(args[1]);
                break;
            case 3:
                if(jm.isValidJail(args[1])) {
                    Jail j = jm.getJail(args[1]);
                    
                    for(Cell c : j.getCells())
                        if(args[2].isEmpty() || StringUtil.startsWithIgnoreCase(c.getName(), args[2]))
                            results.add(c.getName());
                }
                break;
        }
        
        Collections.sort(results);
        
        return results;
    }
} @CommandInfo(
        maxArgs = 1,
        minimumArgs = 1,
        needsPlayer = false,
        pattern = "deletecells|dcs",
        permission = "jail.command.jaildeletecells",
        usage = "/jail deletecells [jail]"
        )
public class JailDeleteCellsCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.isConfirming(sender.getName())) {
            sender.sendMessage(Lang.ALREADY.get());
        }else {
            jm.addConfirming(sender.getName(), new ConfirmPlayer(sender.getName(), args, Confirmation.DELETECELLS));
            sender.sendMessage(Lang.START.get());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        return jm.getJailsByPrefix(args.length == 2 ? args[1] : "");
    }
} @CommandInfo(
        maxArgs = 1,
        minimumArgs = 1,
        needsPlayer = false,
        pattern = "delete|d",
        permission = "jail.command.jaildelete",
        usage = "/jail delete [jail]"
        )
public class JailDeleteCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.isConfirming(sender.getName())) {
            sender.sendMessage(Lang.ALREADY.get());
        }else {
            jm.addConfirming(sender.getName(), new ConfirmPlayer(sender.getName(), args, Confirmation.DELETE));
            sender.sendMessage(Lang.START.get());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        return jm.getJailsByPrefix(args.length == 2 ? args[1] : "");
    }
} @CommandInfo(
        maxArgs = 1,
        minimumArgs = 0,
        needsPlayer = false,
        pattern = "help|h",
        permission = "jail.command.jailhelp",
        usage = "/jail help [page]"
        )
public class JailHelpCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        sender.sendMessage(ChatColor.GREEN + "This command will be filled out shortly, use this link for now:");
        sender.sendMessage(ChatColor.GREEN + "https://github.com/graywolf336/Jail/wiki/Commands");
        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //TODO implement
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 1,
        minimumArgs = 1,
        needsPlayer = false,
        pattern = "listcells|lc",
        permission = "jail.command.jaillistcells",
        usage = "/jail listcells [jail]"
        )
public class JailListCellsCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        sender.sendMessage(ChatColor.AQUA + "----------Cells----------");

        if(!jm.getJails().isEmpty()) {
            if(jm.getJail(args[1]) != null) {
                Jail j = jm.getJail(args[1]);

                List<String> cells = new ArrayList<String>();
                
                for(Cell c : j.getCells()) {
                    cells.add(c.getName() + (c.getPrisoner() == null ? "" : " (" + c.getPrisoner().getLastKnownName() + ")"));
                }
                
                Collections.sort(cells);
                
                sender.sendMessage(cells.isEmpty() ? Lang.NOCELLS.get(j.getName()) : ChatColor.GREEN + Util.getStringFromList(", ", cells));
            }else {
                sender.sendMessage(Lang.NOJAIL.get(args[1]));
            }
        }else {
            sender.sendMessage(Lang.NOJAILS.get());
        }

        sender.sendMessage(ChatColor.AQUA + "-------------------------");
        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        return jm.getJailsByPrefix(args.length == 2 ? args[1] : "");
    }
} @CommandInfo(
        maxArgs = 1,
        minimumArgs = 0,
        needsPlayer = false,
        pattern = "list|l",
        permission = "jail.command.jaillist",
        usage = "/jail list (jail)"
        )
public class JailListCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        sender.sendMessage(ChatColor.AQUA + "----------" + (args.length == 1 ? "Jails" : "Prisoners") + "----------");

        //Check if there are any jails
        if(jm.getJails().isEmpty()) {
            sender.sendMessage(" " + Lang.NOJAILS.get());
        }else {
            //Check if they have provided a jail to list or not
            if(args.length == 1) {
                //No jail provided, so give them a list of the jails
                for(Jail j : jm.getJails()) {
                    if(j.isEnabled()) sender.sendMessage(ChatColor.BLUE + "    " + j.getName() + " (" + j.getAllPrisoners().size() + ")");
                    else sender.sendMessage(ChatColor.RED + "    " + j.getName() + " (" + j.getAllPrisoners().size() + ") - WORLD UNLOADED");
                }
            }else {
                Jail j = jm.getJail(args[1]);

                if(j == null) {
                    //No jail was found
                    sender.sendMessage(" " + Lang.NOJAIL.get(args[1]));
                }else {
                    Collection<Prisoner> pris = j.getAllPrisoners().values();

                    if(pris.isEmpty()) {
                        //If there are no prisoners, then send that message
                        sender.sendMessage(" " + Lang.NOPRISONERS.get(j.getName()));
                    }else {
                        for(Prisoner p : pris) {
                            //graywolf663: Being gray's evil twin; CONSOLE (10)
                            //prisoner: reason; jailer (time in minutes)
                            sender.sendMessage(ChatColor.BLUE + " " + p.getLastKnownName() + ": " + p.getReason() + "; " + p.getJailer() + " (" + p.getRemainingTimeInMinutes() + " mins)");
                        }
                    }
                }
            }
        }

        sender.sendMessage(ChatColor.AQUA + "-------------------------");
        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        return jm.getJailsByPrefix(args.length == 2 ? args[1] : "");
    }
} @CommandInfo(
        maxArgs = 1,
        minimumArgs = 1,
        needsPlayer = false,
        pattern = "mute|m",
        permission = "jail.command.jailmute",
        usage = "/jail mute [name]"
        )
public class JailMuteCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        //Let's check if the player they're sending us is jailed
        if(jm.isPlayerJailedByLastKnownUsername(args[1])) {
            //They are, so let's toggle whether they are muted or not
            boolean muted = !jm.getPrisonerByLastKnownName(args[1]).isMuted();
            jm.getPrisonerByLastKnownName(args[1]).setMuted(muted);

            //Send the message to the sender based upon whether they are muted or unmuted
            if(muted)
                sender.sendMessage(Lang.NOWMUTED.get(args[1]));
            else
                sender.sendMessage(Lang.NOWUNMUTED.get(args[1]));
        }else {
            //The player provided is not jailed, so let's tell the sender that
            sender.sendMessage(Lang.NOTJAILED.get(args[1]));
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        switch(args.length) {
            case 2:
                List<String> results = new ArrayList<String>();
                for(Player p : jm.getPlugin().getServer().getOnlinePlayers())
                    if(StringUtil.startsWithIgnoreCase(p.getName(), args[1].toLowerCase()))
                        results.add(p.getName());
                
                Collections.sort(results);
                return results;
            default:
                return Collections.emptyList();
        }
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 0,
        needsPlayer = true,
        pattern = "pay",
        permission = "jail.usercmd.jailpay",
        usage = "/jail pay (amount) (name)"
        )
public class JailPayCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.getPlugin().getConfig().getBoolean(Settings.JAILPAYENABLED.getPath())) {
            IJailPayManager pm = jm.getPlugin().getJailPayManager();

            switch(args.length) {
                case 1:
                    // `/jail pay`
                    //send how much it costs to get out
                    if(jm.isPlayerJailedByLastKnownUsername(sender.getName())) {
                        Prisoner p = jm.getPrisonerByLastKnownName(sender.getName());
                        String amt = "";

                        if(pm.usingItemsForPayment()) {
                            amt = String.valueOf((int) Math.ceil(pm.calculateBill(p)));
                        }else {
                            amt = String.valueOf(pm.calculateBill(p));
                        }

                        if(p.getRemainingTime() > 0) {
                            if(pm.isTimedEnabled()) {
                                sender.sendMessage(Lang.PAYCOST.get(new String[] { pm.getCostPerMinute(), pm.getCurrencyName(), amt }));
                            }else {
                                sender.sendMessage(Lang.PAYNOTENABLED.get());
                                jm.getPlugin().debug("Jail pay 'timed' paying is not enabled (config has 0 as the cost).");
                            }
                        }else {
                            if(pm.isInfiniteEnabled()) {
                                sender.sendMessage(Lang.PAYCOST.get(new String[] { amt, pm.getCurrencyName() }));
                            }else {
                                sender.sendMessage(Lang.PAYNOTENABLED.get());
                                jm.getPlugin().debug("Jail pay 'infinite' paying is not enabled (config has 0 as the cost).");
                            }
                        }
                    }else {
                        sender.sendMessage(Lang.YOUARENOTJAILED.get());
                    }

                    break;
                case 2:
                    // `/jail pay <amount>`
                    //They are trying to pay for their self
                    if(jm.isPlayerJailedByLastKnownUsername(sender.getName())) {
                        Prisoner p = jm.getPrisonerByLastKnownName(sender.getName());

                        if(p.getRemainingTime() > 0) {
                            if(!pm.isTimedEnabled()) {
                                sender.sendMessage(Lang.PAYNOTENABLED.get());
                                return true;
                            }
                        }else {
                            if(!pm.isInfiniteEnabled()) {
                                sender.sendMessage(Lang.PAYNOTENABLED.get());
                                return true;
                            }
                        }

                        if(args[1].startsWith("-")) {
                            sender.sendMessage(Lang.PAYNONEGATIVEAMOUNTS.get());
                        }else {
                            double amt = 0;

                            try {
                                amt = Double.parseDouble(args[1]);
                            }catch(NumberFormatException e) {
                                sender.sendMessage(ChatColor.RED + "<amount> must be a number.");
                                throw e;
                            }

                            if(pm.hasEnoughToPay((Player) sender, amt)) {
                                double bill = pm.calculateBill(p);

                                if(p.getRemainingTime() > 0) {
                                    //timed sentence
                                    if(amt >= bill) {
                                        pm.pay((Player) sender, bill);
                                        sender.sendMessage(Lang.PAYPAIDRELEASED.get(String.valueOf(bill)));
                                        jm.getPlugin().getPrisonerManager().schedulePrisonerRelease(p);
                                    }else {
                                        long minutes = pm.getMinutesPayingFor(amt);
                                        pm.pay((Player) sender, amt);
                                        long remain = p.subtractTime(TimeUnit.MILLISECONDS.convert(minutes, TimeUnit.MINUTES));
                                        sender.sendMessage(Lang.PAYPAIDLOWEREDTIME.get(new String[] { String.valueOf(amt), String.valueOf(TimeUnit.MINUTES.convert(remain, TimeUnit.MILLISECONDS)) }));
                                    }
                                }else {
                                    //infinite jailing
                                    if(amt >= bill) {
                                        pm.pay((Player) sender, bill);
                                        sender.sendMessage(Lang.PAYPAIDRELEASED.get(String.valueOf(bill)));
                                        jm.getPlugin().getPrisonerManager().schedulePrisonerRelease(p);
                                    }else {
                                        //You haven't provided enough money to get them out
                                        sender.sendMessage(Lang.PAYNOTENOUGHMONEYPROVIDED.get());
                                    }
                                }
                            }else {
                                sender.sendMessage(Lang.PAYNOTENOUGHMONEY.get());
                            }
                        }
                    }else {
                        sender.sendMessage(Lang.YOUARENOTJAILED.get());
                    }
                    break;
                case 3:
                    // `/jail pay <amount> <person>
                    //they are trying to pay for someone else
                    if(jm.isPlayerJailedByLastKnownUsername(sender.getName())) {
                        //When they are jailed they can not pay for someone else
                        sender.sendMessage(Lang.PAYCANTPAYWHILEJAILED.get());
                    }else {
                        if(jm.isPlayerJailedByLastKnownUsername(args[2])) {
                            Prisoner p = jm.getPrisonerByLastKnownName(args[2]);

                            if(p.isJailedForever()) {
                                if(!pm.isInfiniteEnabled()) {
                                    sender.sendMessage(Lang.PAYNOTENABLED.get());
                                    return true;
                                }
                            }else {
                                if(!pm.isTimedEnabled()) {
                                    sender.sendMessage(Lang.PAYNOTENABLED.get());
                                    return true;
                                }
                            }

                            if(args[1].startsWith("-")) {
                                sender.sendMessage(Lang.PAYNONEGATIVEAMOUNTS.get());
                            }else {
                                double amt = 0;

                                try {
                                    amt = Double.parseDouble(args[1]);
                                }catch(NumberFormatException e) {
                                    sender.sendMessage(ChatColor.RED + "<amount> must be a number.");
                                    throw e;
                                }


                                if(pm.hasEnoughToPay((Player) sender, amt)) {
                                    double bill = pm.calculateBill(p);

                                    if(p.getRemainingTime() > 0) {
                                        //timed sentence
                                        if(amt >= bill) {
                                            pm.pay((Player) sender, bill);
                                            sender.sendMessage(Lang.PAYPAIDRELEASEDELSE.get(new String[] { String.valueOf(bill), p.getLastKnownName() }));
                                            jm.getPlugin().getPrisonerManager().schedulePrisonerRelease(p);
                                        }else {
                                            long minutes = pm.getMinutesPayingFor(amt);
                                            pm.pay((Player) sender, amt);
                                            long remain = p.subtractTime(TimeUnit.MILLISECONDS.convert(minutes, TimeUnit.MINUTES));
                                            sender.sendMessage(Lang.PAYPAIDLOWEREDTIMEELSE.get(new String[] { String.valueOf(amt), p.getLastKnownName(), String.valueOf(TimeUnit.MINUTES.convert(remain, TimeUnit.MILLISECONDS)) }));
                                        }
                                    }else {
                                        //infinite jailing
                                        if(amt >= bill) {
                                            pm.pay((Player) sender, bill);
                                            sender.sendMessage(Lang.PAYPAIDRELEASEDELSE.get(new String[] { String.valueOf(bill), p.getLastKnownName() }));
                                            jm.getPlugin().getPrisonerManager().schedulePrisonerRelease(p);
                                        }else {
                                            //You haven't provided enough money to get them out
                                            sender.sendMessage(Lang.PAYNOTENOUGHMONEYPROVIDED.get());
                                        }
                                    }
                                }else {
                                    sender.sendMessage(Lang.PAYNOTENOUGHMONEY.get());
                                }
                            }
                        }else {
                            //Person they're trying to pay for is not jailed
                            sender.sendMessage(Lang.NOTJAILED.get(args[2]));
                        }
                    }
                    break;
                default:
                    return false;
            }
        }else {
            jm.getPlugin().debug("Jail pay not enabled.");
            sender.sendMessage(Lang.PAYNOTENABLED.get());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.getPlugin().getConfig().getBoolean(Settings.JAILPAYENABLED.getPath())) {
            switch(args.length) {
                case 3:
                    List<String> results = new ArrayList<String>();
                    for(Prisoner p : jm.getAllPrisoners().values())
                        if(!p.isOfflinePending()) //Don't list if they're offline pending
                            if(p.getRemainingTime() != -1) //Don't list if they're jailed forever
                                if(StringUtil.startsWithIgnoreCase(p.getLastKnownName(), args[2]))
                                    results.add(p.getLastKnownName());
                    
                    Collections.sort(results);
                    
                    return results;
                default:
                    break;
            }
        }
        
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 1,
        needsPlayer = false,
        pattern = "record|r",
        permission = "jail.command.jailrecord",
        usage = "/jail record [name] (display)"
        )
public class JailRecordCommand implements Command {
    @SuppressWarnings("deprecation")
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        if(args.length == 2) {
            // /jail record <username>
            List<String> entries = jm.getPlugin().getJailIO().getRecordEntries(args[1]);

            sender.sendMessage(Lang.RECORDTIMESJAILED.get(new String[] { args[1], String.valueOf(entries.size()) }));
        }else if(args.length == 3) {
            // /jail record <username> something
            List<String> entries = jm.getPlugin().getJailIO().getRecordEntries(args[1]);

            //Send all the record entries
            for(String s : entries) {
                sender.sendMessage(s);
            }

            sender.sendMessage(Lang.RECORDTIMESJAILED.get(new String[] { args[1], String.valueOf(entries.size()) }));
        }else {
            //They didn't do the command right
            //send them back to get the usage
            return false;
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //We won't be implementing tab complete on this for now (unless requested)
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 0,
        minimumArgs = 0,
        needsPlayer = false,
        pattern = "reload",
        permission = "jail.command.jailreload",
        usage = "/jail reload"
        )
public class JailReloadCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        try {
            jm.getPlugin().reloadEverything();
            sender.sendMessage(Lang.PLUGINRELOADED.get());
        }catch (Exception e) {
            e.printStackTrace();
            sender.sendMessage(ChatColor.RED + "Failed to reload due to (see the console): " + e.getMessage());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //Reloading the plugin doesn't require tab completions
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = -1,
        minimumArgs = 2,
        needsPlayer = false,
        pattern = "signs",
        permission = "jail.command.jailsigns",
        usage = "/jail signs [clean|refresh|update|verify] [jail] (cell)..."
        )
public class JailSignsCommand implements Command {
    private static final String[] options =  new String[] { "clean", "refresh", "update", "verify" };

    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(!jm.isValidJail(args[2])) {
            sender.sendMessage(" " + Lang.NOJAIL.get(args[1]));
            return true;
        }

        Jail j = jm.getJail(args[2]);

        HashMap<String, List<String>> items = new HashMap<String, List<String>>();

        if(args[1].equalsIgnoreCase("clean")) {
            //if they type clean, we can remove all signs which are no longer signs
            //then provide them a list of signs which got cleaned up

            for(Cell c : j.getCells()) {
                if(Util.isStringInsideArray(c.getName(), args) || args.length == 3) {
                    if(c.hasSigns()) {
                        List<String> cleaned = c.cleanSigns();
                        if(!cleaned.isEmpty()) {
                            items.put(c.getName(), cleaned);
                        }
                    }
                }
            }

            if(items.isEmpty()) {
                sender.sendMessage(Lang.NOINVALIDSIGNS.get());
            }else {
                sender.sendMessage(Lang.CLEANEDSIGNS.get());
                for(Entry<String, List<String>> e : items.entrySet()) {
                    sender.sendMessage("  " + e.getKey());
                    for(String s : e.getValue())
                        sender.sendMessage("    Sign: " + s);
                }
            }

            return true;
        }else if(args[1].equalsIgnoreCase("refresh") || args[1].equalsIgnoreCase("update")) {
            //if they type refresh, we will go through all the signs and update them to
            //display the correct message on the sign whether empty or time

            int cells = 0;
            int updated = 0;
            int removed = 0;

            for(Cell c : j.getCells()) {
                if(Util.isStringInsideArray(c.getName(), args) || args.length == 3) {
                    HashMap<String, List<String>> results = c.updateSigns();
                    updated += results.get("updated").size();
                    removed += results.get("removed").size();
                    cells++;
                }
            }

            sender.sendMessage(Lang.SIGNSREFRESHED.get(new String[] { String.valueOf(updated), String.valueOf(removed), String.valueOf(cells) }));
            return true;
        }else if(args[1].equalsIgnoreCase("verify")) {
            //if they type verify, we will go through all the signs and then provide them
            //as a list of cells which have signs that aren't actually signs and provide
            //the location where the signs are

            for(Cell c : j.getCells()) {
                if(Util.isStringInsideArray(c.getName(), args) || args.length == 3) {
                    List<String> invalids = c.getInvalidSigns();
                    if(!invalids.isEmpty()) {
                        items.put(c.getName(), invalids);
                    }
                }
            }

            if(items.isEmpty()) {
                sender.sendMessage(Lang.NOINVALIDSIGNS.get());
            }else {
                sender.sendMessage(Lang.INVALIDSIGNS.get());
                for(Entry<String, List<String>> e : items.entrySet()) {
                    sender.sendMessage("  " + e.getKey());
                    for(String s : e.getValue())
                        sender.sendMessage("    " + Lang.SIGN.get() + ": " + s);
                }
            }
            return true;
        }else
            return false;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        List<String> results = new ArrayList<String>();

        switch(args.length) {
            case 2:
                for(String s : options)
                    if(args[1].isEmpty() || StringUtil.startsWithIgnoreCase(s, args[1]))
                        results.add(s);
                break;
            case 3:
                results.addAll(jm.getJailsByPrefix(args[2]));
                break;
            default:
                if(jm.isValidJail(args[2]))
                    for(Cell c : jm.getJail(args[2]).getCells())
                        if(!Util.isStringInsideArray(c.getName(), args))
                            results.add(c.getName());
                break;
        }

        Collections.sort(results);

        return results;
    }
} @CommandInfo(
        maxArgs = 0,
        minimumArgs = 0,
        needsPlayer = true,
        pattern = "status|s",
        permission = "jail.usercmd.jailstatus",
        usage = "/jail status"
        )
public class JailStatusCommand implements Command{

    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        Player pl = (Player) sender;

        if(jm.isPlayerJailed(pl.getUniqueId())) {
            Prisoner p = jm.getPrisoner(pl.getUniqueId());
            //They are jailed, so let's tell them some information
            sender.sendMessage(Lang.STATUS.get(new String[] { p.getReason(), p.getJailer(), String.valueOf(p.getRemainingTimeInMinutes()), Util.getDurationBreakdown(p.getRemainingTime()) }));
        }else {
            //the sender of the command is not jailed, tell them that
            sender.sendMessage(Lang.YOUARENOTJAILED.get());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //Checking your jail status doesn't require tab completion
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 0,
        minimumArgs = 0,
        needsPlayer = true,
        pattern = "stick",
        permission = "jail.usercmd.jailstick",
        usage = "/jail stick"
        )
public class JailStickCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.getPlugin().getConfig().getBoolean(Settings.JAILSTICKENABLED.getPath())) {
            boolean using = jm.getPlugin().getJailStickManager().toggleUsingStick(((Player) sender).getUniqueId());

            if(using) {
                sender.sendMessage(Lang.JAILSTICKENABLED.get());
            }else {
                sender.sendMessage(Lang.JAILSTICKDISABLED.get());
            }
        }else {
            sender.sendMessage(Lang.JAILSTICKUSAGEDISABLED.get());
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //Nothing to tab complete on jail stick
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 0,
        minimumArgs = 0,
        needsPlayer = true,
        pattern = "stop",
        permission = "jail.command.jailstop",
        usage = "/jail stop"
        )
public class JailStopCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        boolean nothing = true;

        if(jm.isCreatingACell(sender.getName())) {
            jm.removeCellCreationPlayer(sender.getName());
            sender.sendMessage(ChatColor.RED + "You have stopped creating cells.");
            nothing = false;
        }

        if(jm.isCreatingAJail(sender.getName())) {
            jm.removeJailCreationPlayer(sender.getName());
            sender.sendMessage(ChatColor.RED + "You have stopped creating a jail.");
            nothing = false;
        }

        if(nothing) {
            sender.sendMessage(ChatColor.RED + "You've stopped creating....nothing.");
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //no tab completion required for stop command
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 1,
        needsPlayer = false,
        pattern = "teleportin|telein",
        permission = "jail.command.jailtelein",
        usage = "/jail telein [jail] (name)"
        )
public class JailTeleInCommand implements Command {
    @SuppressWarnings("deprecation")
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        Jail j = jm.getJail(args[1]);

        //The jail doesn't exist
        if(j == null) {
            sender.sendMessage(Lang.NOJAIL.get(args[1]));
        }else {
            //The jail does exist
            //now let's check the size of the command
            //if it has two args then someone is sending someone else in
            //otherwise it is just the sender going in
            if(args.length == 3) {
                Player p = jm.getPlugin().getServer().getPlayer(args[2]);

                //If the player they're trying to send is offline, don't do anything
                if(p == null) {
                    sender.sendMessage(Lang.PLAYERNOTONLINE.get(args[2]));
                }else {
                    p.teleport(j.getTeleportIn());
                    sender.sendMessage(Lang.TELEIN.get(new String[] { args[2], args[1] }));
                }
            }else {
                if(sender instanceof Player) {
                    ((Player) sender).teleport(j.getTeleportIn());
                    sender.sendMessage(Lang.TELEIN.get(new String[] { sender.getName(), args[1] }));
                }else {
                    sender.sendMessage(Lang.PLAYERCONTEXTREQUIRED.get());
                }
            }
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        switch(args.length) {
            case 2:
                return jm.getJailsByPrefix(args[1]);
            case 3:
                List<String> results = new ArrayList<String>();
                
                for(Player p : jm.getPlugin().getServer().getOnlinePlayers())
                    if(args[2].isEmpty() || StringUtil.startsWithIgnoreCase(p.getName(), args[2]))
                        results.add(p.getName());
                
                Collections.sort(results);
                
                return results;
            default:
                return Collections.emptyList();
        }
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 1,
        needsPlayer = false,
        pattern = "teleportout|teleout",
        permission = "jail.command.jailteleout",
        usage = "/jail teleout [jail] (name)"
        )
public class JailTeleOutCommand implements Command {
    @SuppressWarnings("deprecation")
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        Jail j = jm.getJail(args[1]);

        //The jail doesn't exist
        if(j == null) {
            sender.sendMessage(Lang.NOJAIL.get(args[1]));
        }else {
            //The jail does exist
            //now let's check the size of the command
            //if it has two args then someone is sending someone else in
            //otherwise it is just the sender going in
            if(args.length == 3) {
                Player p = jm.getPlugin().getServer().getPlayer(args[2]);

                //If the player they're trying to send is offline, don't do anything
                if(p == null) {
                    sender.sendMessage(Lang.PLAYERNOTONLINE.get(args[2]));
                }else {
                    p.teleport(j.getTeleportFree());
                    sender.sendMessage(Lang.TELEOUT.get(new String[] { args[2], args[1] }));
                }
            }else {
                if(sender instanceof Player) {
                    ((Player) sender).teleport(j.getTeleportFree());
                    sender.sendMessage(Lang.TELEOUT.get(new String[] { sender.getName(), args[1] }));
                }else {
                    sender.sendMessage(Lang.PLAYERCONTEXTREQUIRED.get());
                }
            }
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        switch(args.length) {
            case 2:
                return jm.getJailsByPrefix(args[1]);
            case 3:
                List<String> results = new ArrayList<String>();
                
                for(Player p : jm.getPlugin().getServer().getOnlinePlayers())
                    if(args[2].isEmpty() || StringUtil.startsWithIgnoreCase(p.getName(), args[2]))
                        results.add(p.getName());
                
                Collections.sort(results);
                
                return results;
            default:
                return Collections.emptyList();
        }
    }
} @CommandInfo(
        maxArgs = 3,
        minimumArgs = 2,
        needsPlayer = false,
        pattern = "time|t",
        permission = "jail.command.jailtime",
        usage = "/jail time [add|remove|set|show] [name] <time>"
        )
public class JailTimeCommand implements Command {
    private static final String[] options = new String[] { "add", "remove", "set", "show" };
    
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.isPlayerJailedByLastKnownUsername(args[2])) {
            Prisoner p = jm.getPrisonerByLastKnownName(args[2]);

            switch(args.length) {
                case 3:
                    if(args[1].equalsIgnoreCase("show")) {
                        sender.sendMessage(Lang.PRISONERSTIME.get(new String[] { p.getLastKnownName(), String.valueOf(p.getRemainingTimeInMinutes()) }));
                    }else {
                        return false;
                    }
                    break;
                case 4:
                    if(args[1].equalsIgnoreCase("add")) {
                        p.addTime(Util.getTime(args[3]));
                    }else if(args[1].equalsIgnoreCase("remove")) {
                        p.subtractTime(Util.getTime(args[3]));
                    }else if(args[1].equalsIgnoreCase("set")) {
                        p.setRemainingTime(Util.getTime(args[3]));
                    }else {
                        return false;
                    }

                    sender.sendMessage(Lang.PRISONERSTIME.get(new String[] { p.getLastKnownName(), String.valueOf(p.getRemainingTimeInMinutes()) }));
                    break;
                default:
                    return false;
            }
        }else {
            sender.sendMessage(Lang.NOTJAILED.get(args[2]));
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        List<String> results = new ArrayList<String>();
        
        switch(args.length) {
            case 2:
                for(String s : options)
                    if(args[1].isEmpty() || StringUtil.startsWithIgnoreCase(s, args[1]))
                        results.add(s);
                break;
            case 3:
                for(Prisoner p : jm.getAllPrisoners().values())
                    if(StringUtil.startsWithIgnoreCase(p.getLastKnownName(), args[2]))
                        results.add(p.getLastKnownName());
                break;
            default:
                break;
        }
        
        Collections.sort(results);
        
        return results;
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 2,
        needsPlayer = false,
        pattern = "transferall|transall|ta",
        permission = "jail.command.jailtransferall",
        usage = "/jail transferall [current] [target]"
        )
public class JailTransferAllCommand implements Command {
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.getJails().isEmpty()) {
            sender.sendMessage(Lang.NOJAILS.get());
            return true;
        }

        jm.getPlugin().debug("Starting to transfer everyone from '" + args[1] + "' into '" + args[2] + "'.");

        //Check if the oldjail is not a valid jail
        if(!jm.isValidJail(args[1])) {
            sender.sendMessage(Lang.NOJAIL.get(args[1]));
            return true;
        }else if(!jm.isValidJail(args[2])) {
            //Check if the targetjail is a valid jail as well
            sender.sendMessage(Lang.NOJAIL.get(args[2]));
            return true;
        }

        jm.getPlugin().debug("Sending the transferring off, jail checks all came clean.");

        Jail old = jm.getJail(args[1]);
        HashSet<Prisoner> oldPrisoners = new HashSet<Prisoner>(old.getAllPrisoners().values());

        //Transfer all the prisoners
        for(Prisoner p : oldPrisoners) {
            jm.getPlugin().getPrisonerManager().transferPrisoner(old, old.getCellPrisonerIsIn(p.getUUID()), jm.getJail(args[2]), null, p);
        }

        //Send the messages to the sender when completed
        sender.sendMessage(Lang.TRANSFERALLCOMPLETE.get(new String[] { old.getName(), args[2] }));

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        switch(args.length) {
            case 2:
                return jm.getJailsByPrefix(args[1]);
            case 3:
                List<String> results = new ArrayList<String>();
                
                for(Jail j : jm.getJails())
                    if(!j.getName().equalsIgnoreCase(args[1]) && (args[2].isEmpty() || StringUtil.startsWithIgnoreCase(j.getName(), args[2])))
                        results.add(j.getName());
                
                Collections.sort(results);
                
                return results;
            default:
                return Collections.emptyList();
        }
    }
} @CommandInfo(
        maxArgs = 6,
        minimumArgs = 2,
        needsPlayer = false,
        pattern = "transfer|trans",
        permission = "jail.command.jailtransfer",
        usage = "/jail transfer [-p player] (-j jail) (-c cell)"
        )
public class JailTransferCommand implements Command {
    private List<String> commands = Arrays.asList(new String[] { "p", "j", "c" });
    
    public boolean execute(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.getJails().isEmpty()) {
            sender.sendMessage(Lang.NOJAILS.get());
            return true;
        }

        //Convert to a List<String> so we can edit the list
        List<String> arguments = new LinkedList<String>(Arrays.asList(args));
        //remove the first argument of "transfer"
        arguments.remove(0);

        //Parse the command
        Transfer params = null;

        try {
            params = CliFactory.parseArguments(Transfer.class, arguments.toArray(new String[arguments.size()]));
        }catch(ArgumentValidationException e) {
            sender.sendMessage(ChatColor.RED + e.getMessage());
            return true;
        }

        //Verify they gave us a player and if so check if they're jailed
        if(params.getPlayer() == null) {
            sender.sendMessage(Lang.PROVIDEAPLAYER.get(Lang.TRANSFERRING));
            return true;
        }else if(!jm.isPlayerJailedByLastKnownUsername(params.getPlayer())) {
            sender.sendMessage(Lang.NOTJAILED.get(params.getPlayer()));
            return true;
        }

        jm.getPlugin().debug("Checking everything before we transfer: " + params.getPlayer());

        //If they didn't provide a jail, tell them we need one
        if(params.getJail() == null) {
            sender.sendMessage(Lang.PROVIDEAJAIL.get(Lang.TRANSFERRING));
            return true;
        }else if(!jm.isValidJail(params.getJail())) {
            //Check if the jail they did provided is not a valid jail
            sender.sendMessage(Lang.NOJAIL.get(params.getJail()));
            return true;
        }

        jm.getPlugin().debug("They provided a valid jail, so let's check the target cell.");

        Jail target = jm.getJail(params.getJail());
        Cell targetCell = null;

        //Check if they provided a cell and if so does it exist
        if(params.getCell() != null) {
            if(target.getCell(params.getCell()) == null) {
                sender.sendMessage(Lang.NOCELL.get(new String[] { params.getCell(), params.getJail() }));
                return true;
            }else {
                //Store the cell for easy of access and also check if it already is full
                targetCell = target.getCell(params.getCell());
                if(targetCell.hasPrisoner()) {
                    //If the cell has a prisoner, don't allow jailing them to that particular cell
                    sender.sendMessage(Lang.CELLNOTEMPTY.get(params.getCell()));

                    //But suggest the first empty cell we find
                    Cell suggestedCell = jm.getJail(params.getCell()).getFirstEmptyCell();
                    if(suggestedCell != null) {
                        sender.sendMessage(Lang.SUGGESTEDCELL.get(new String[] { params.getCell(), suggestedCell.getName() }));
                    }else {
                        sender.sendMessage(Lang.NOEMPTYCELLS.get(params.getCell()));
                    }

                    return true;
                }
            }
        }

        jm.getPlugin().debug("Calling the PrePrisonerTransferredEvent, jail and cell check all came out clean.");
        Prisoner p = jm.getPrisonerByLastKnownName(params.getPlayer());

        //Throw the custom event before transferring them, allowing another plugin to cancel it.
        PrePrisonerTransferredEvent event = new PrePrisonerTransferredEvent(jm.getJailPlayerIsIn(p.getUUID()),
                jm.getJailPlayerIsIn(p.getUUID()).getCellPrisonerIsIn(p.getUUID()),
                target, targetCell, p, jm.getPlugin().getServer().getPlayer(p.getUUID()), sender.getName());
        jm.getPlugin().getServer().getPluginManager().callEvent(event);

        if(event.isCancelled()) {
            if(event.getCancelledMessage().isEmpty()) {
                //The plugin didn't provide a cancel message/reason so send the default one
                sender.sendMessage(Lang.TRANSFERCANCELLEDBYANOTHERPLUGIN.get(params.getPlayer()));
            }else {
                sender.sendMessage(event.getCancelledMessage());
            }

            return true;
        }

        //Start the transferring of the prisoner
        jm.getPlugin().getPrisonerManager().transferPrisoner(jm.getJailPlayerIsIn(p.getUUID()),
                jm.getJailPlayerIsIn(p.getUUID()).getCellPrisonerIsIn(p.getUUID()),
                target, targetCell, p);

        //Send the messages to the sender, if no cell then say that but if cell send that as well
        if(targetCell == null) {
            sender.sendMessage(Lang.TRANSFERCOMPLETENOCELL.get(new String[] { params.getPlayer(), target.getName() }));
        }else {
            sender.sendMessage(Lang.TRANSFERCOMPLETECELL.get(new String[] { params.getPlayer(), target.getName(), targetCell.getName() }));
        }

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //by the time it gets to this command it'll have at least two arguments
        String last = args[args.length - 1];
        
        if(last.isEmpty() || !commands.contains(last.replace("-", ""))) {
            //the current part is empty. Need to look at their previous
            //item and if it is a valid option, then provide them a valid tab complete option
            if(args.length - 2 > -1) {
                String previous = args[args.length - 2];
                jm.getPlugin().debug("args[args.length - 2]: " + previous);
                
                if(previous.equalsIgnoreCase("-p")) return getPlayers(jm, last);
                else if(previous.equalsIgnoreCase("-j")) return jm.getJailsByPrefix(last);
                else if(previous.equalsIgnoreCase("-c")) {
                    //Since we need to give them a list of the cells in a jail
                    //we need to get the jail they're giving
                    int jailIndex = ArrayUtils.indexOf(args, "-j");
                    if(jailIndex != -1) {
                        String jail = args[jailIndex + 1];
                        jm.getPlugin().debug("The jail is: " + jail);
                        if(jm.isValidJail(jail)) return getCells(jm, jail, last);
                    }
                }else if(!commands.contains(args[args.length - 2].replace("-", ""))) return Util.getUnusedItems(commands, args, true);
            }
        }else if(last.equalsIgnoreCase("-")) {
            //add some smart checking so that it only returns a list of what isn't already
            //in the command :)
            return Util.getUnusedItems(commands, args, true);
        }else {
            return getPlayers(jm, last);
        }
        
        return Collections.emptyList();
    }
    
    private List<String> getPlayers(JailManager jm, String first) {
        List<String> results = new ArrayList<String>();
        
        for(Player p : jm.getPlugin().getServer().getOnlinePlayers())
            if(first.isEmpty() || StringUtil.startsWithIgnoreCase(p.getName(), first))
                results.add(p.getName());
        
        Collections.sort(results);
        
        return results;
    }
    
    private List<String> getCells(JailManager jm, String jail, String cell) {
        List<String> results = new ArrayList<String>();
        
        for(Cell c : jm.getJail(jail).getCells())
            if(!c.hasPrisoner() && (cell.isEmpty() || StringUtil.startsWithIgnoreCase(c.getName(), cell)))
            	results.add(c.getName());
        
        Collections.sort(results);
        
        return results;
    }
} @CommandInfo(
        maxArgs = 0,
        minimumArgs = 0,
        needsPlayer = false,
        pattern = "version|ver",
        permission = "jail.command.jailversion",
        usage = "/jail version"
        )
public class JailVersionCommand implements Command {

    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        // Sends the version number to the sender
        sender.sendMessage("Jail Version: " + jm.getPlugin().getDescription().getVersion());

        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        //no tab completion required for version command
        return Collections.emptyList();
    }
} @CommandInfo(
        maxArgs = 2,
        minimumArgs = 1,
        needsPlayer = true,
        pattern = "vote|v",
        permission = "jail.usercmd.jailvote",
        usage = "/jail vote [player] (yes|no)"
        )
public class JailVoteCommand implements Command {
    private static final String[] options =  new String[] { "yes", "no" };
    
    @SuppressWarnings("deprecation")
    public boolean execute(JailManager jm, CommandSender sender, String... args) {
        if(jm.getPlugin().getConfig().getBoolean(Settings.JAILVOTEENABLED.getPath()) && jm.getPlugin().getJailVoteManager() != null && !jm.getJails().isEmpty()) {
            Player p = (Player) sender;
            JailVoteManager jvm = jm.getPlugin().getJailVoteManager();
            
            switch(args.length) {
                case 2:
                    if(jvm.isVotedFor(args[1])) {
                        if(jvm.hasVotedAlready(args[1], p.getUniqueId())) {
                            sender.sendMessage(Lang.VOTEALREADYVOTEDFOR.get(args[1]));
                        }else {
                            if(jvm.addVote(args[1], p.getUniqueId(), true)) {
                                sender.sendMessage(Lang.VOTEYESSUCCESS.get(args[1]));
                            }else {
                                sender.sendMessage(Lang.VOTEUNSUCCESSFUL.get());
                            }
                        }
                    }else if(sender.hasPermission("jail.vote.start")) {
                        Player voteAgainst = jm.getPlugin().getServer().getPlayer(args[1]);  
                        if(voteAgainst == null) {
                            sender.sendMessage(Lang.PLAYERNOTONLINE.get());
                        }else {
                            if(voteAgainst.hasPermission("jail.cantbejailed")) {
                                sender.sendMessage(Lang.CANTBEJAILED.get());
                            }else {
                                jvm.addVote(new JailVote(voteAgainst.getName()));
                                jvm.addVote(voteAgainst.getName(), p.getUniqueId(), true);
                                
                                jm.getPlugin().getServer().broadcastMessage(Lang.VOTEBROADCASTHEADER.get());
                                jm.getPlugin().getServer().broadcastMessage(Lang.VOTEBROADCASTLINE1.get(new String[] { sender.getName(), args[1] }));
                                jm.getPlugin().getServer().broadcastMessage(Lang.VOTEBROADCASTLINE2.get(voteAgainst.getName()));
                                jm.getPlugin().getServer().broadcastMessage(Lang.VOTEBROADCASTLINE3.get(voteAgainst.getName()));
                                jm.getPlugin().getServer().broadcastMessage(Lang.VOTEBROADCASTLINE4.get(jvm.getTimerLengthDescription()));
                                jm.getPlugin().getServer().broadcastMessage(Lang.VOTEBROADCASTFOOTER.get());
                                
                                jvm.scheduleCalculating(args[1]);
                            }
                        }
                    }else {
                        jm.getPlugin().debug(sender.getName() + " tried to start a vote to jail someone but didn't have permission, jail.vote.start");
                        sender.sendMessage(Lang.VOTENOPERMISSIONTOSTART.get(args[1]));
                    }
                    break;
                case 3:
                    String name = args[1];
                    
                    if(jvm.isVotedFor(name)) {
                        if(jvm.hasVotedAlready(args[1], p.getUniqueId())) {
                            sender.sendMessage(Lang.VOTEALREADYVOTEDFOR.get(args[1]));
                        }else {
                            if(args[2].equalsIgnoreCase("yes")) {
                                jvm.addVote(args[1], p.getUniqueId(), true);
                                sender.sendMessage(Lang.VOTEYESSUCCESS.get(args[1]));
                            }else {
                                jvm.addVote(args[1], p.getUniqueId(), false);
                                sender.sendMessage(Lang.VOTENOSUCCESS.get(args[1]));
                            }
                        }
                    }else {
                        sender.sendMessage(Lang.VOTENOVOTEFORTHATPLAYER.get(name));
                    }
                    break;
                default:
                    return false;
            }
        }else {
            sender.sendMessage(Lang.VOTENOTENABLED.get());
            
            if(jm.getPlugin().getJailVoteManager() == null) {
                jm.getPlugin().getLogger().severe("Jail Vote Manager didn't load correctly, it is null.");
            }else if(jm.getJails().isEmpty()) {
                jm.getPlugin().getLogger().severe("There are no jails, Jail Vote needs a Jail to work.");
            }
        }
        
        return true;
    }

    public List<String> provideTabCompletions(JailManager jm, CommandSender sender, String... args) throws Exception {
        if(jm.getPlugin().getConfig().getBoolean(Settings.JAILVOTEENABLED.getPath()) && jm.getPlugin().getJailVoteManager() != null && !jm.getJails().isEmpty()) {
            List<String> results = new ArrayList<String>();
            
            switch(args.length) {
                case 2:
                    for(Player p : jm.getPlugin().getServer().getOnlinePlayers())
                        if(args[1].isEmpty() || StringUtil.startsWithIgnoreCase(p.getName(), args[1]))
                            results.add(p.getName());
                    break;
                case 3:
                    for(String s : options)
                        if(args[2].isEmpty() || StringUtil.startsWithIgnoreCase(s, args[2]))
                            results.add(s);
                    break;
                default:
                    break;
            }
            
            Collections.sort(results);
            
            return results;
        }else {
            return Collections.emptyList();
        }
    }
} public enum Lang {
    // actions section
    /** Section for when they break a block. */
    BLOCKBREAKING("actions"),
    /** Section for when they place a block. */
    BLOCKPLACING("actions"),
    /** Section for when they try to do a command that isn't whitelisted. */
    COMMAND("actions"),
    /** Section for when a player tramples a crop and protection is enabled. */
    CROPTRAMPLING("actions"),
    /** Section for when a player interacts with a block that is blacklisted. */
    INTERACTIONBLOCKS("actions"),
    /** Section for when a player interacts with an item that is blacklisted. */
    INTERACTIONITEMS("actions"),
    /** Section for when a player moves outside of the jail. */
    MOVING("actions"),

    // Jailing section
    /** The message displayed when players are kicked for being afk. */
    AFKKICKMESSAGE("jailing"),
    /** The message sent when jailing someone that is already jailed. */
    ALREADYJAILED("jailing"),
    /** The message sent when we broadcast/log the message for time below -1. */
    BROADCASTMESSAGEFOREVER("jailing"),
    /** The message sent when we broadcast/log the message for any time above -1. */
    BROADCASTMESSAGEFORMINUTES("jailing"),
    /** The message sent to the broadcast/log the unjailing of someone. */
    BROADCASTUNJAILING("jailing"),
    /** The message sent to the sender when trying to jail someone and a plugin cancels it but doesn't leave a message why. */
    CANCELLEDBYANOTHERPLUGIN("jailing"),
    /** The message sent when trying to jail someone who can't be jailed by permission. */
    CANTBEJAILED("jailing"),
    /** The message sent to the sender when they are trying to jail into a cell which is not empty. */
    CELLNOTEMPTY("jailing"),
    /** The jailer set whenever a jailer is not provided. */
    DEFAULTJAILER("jailing"),
    /** The message sent when someone is jailed without a reason. */
    DEFAULTJAILEDREASON("jailing"),
    /** The message sent when someone is unjailed yet they never came online and so they were forcefully unjailed. */
    FORCEUNJAILED("jailing"),
    /** The message sent when players are jailed without a reason. */
    JAILED("jailing"),
    /** The message sent when players are jailed with a reason. */
    JAILEDWITHREASON("jailing"),
    /** The message sent when players are jailed and they try to talk. */
    MUTED("jailing"),
    /** The message sent when the sender tries to jail someone in a cell and there aren't any cells to suggest. */
    NOEMPTYCELLS("jailing"),
    /** The message sent to the sender when they list all the prisoners in a jail which has no prisoners. */
    NOPRISONERS("jailing"),
    /** The message sent when a player is not jailed and the sender is trying to see/do something about it. */
    NOTJAILED("jailing"),
    /** The message sent to the sender when they mute a prisoner. */
    NOWMUTED("jailing"),
    /** The message sent to the sender when they unmute a prisoner. */
    NOWUNMUTED("jailing"),
    /** The message sent to the jailer when they jail someone offline. */
    OFFLINEJAIL("jailing"),
    /** The message sent to the jailer when they jail someone who is online. */
    ONLINEJAIL("jailing"),
    /** The message sent to the jailer when they jail someone who has never played before. */
    PLAYERHASNEVERPLAYEDBEFORE("jailing"),
    /** The message sent when finding out how much time a prisoner has. */
    PRISONERSTIME("jailing"),
    /** The message sent to the prisoner when they try to do something but it is protected. */
    PROTECTIONMESSAGE("jailing"),
    /** The message sent to the prisoner when they try to do something and it is protected, but no penalty. */
    PROTECTIONMESSAGENOPENALTY("jailing"),
    /** The message sent to the sender when they need to provide a player. */
    PROVIDEAPLAYER("jailing"),
    /** The message sent to the sender when they need to provide a jail. */
    PROVIDEAJAIL("jailing"),
    /** The message sent to someone trying to jail someone else with a jail stick that requires health below a certain amount. */
    RESISTEDARRESTJAILER("jailing"),
    /** The message sent to the person someone else is trying to jail that requires their health below a certain amount. */
    RESISTEDARRESTPLAYER("jailing"),
    /** The message sent when to a prisoner about their status in jail. */
    STATUS("jailing"),
    /** The message sent to the sender of a command when suggesting a cell. */
    SUGGESTEDCELL("jailing"),
    /** The message sent to the sender when they teleport someone to a jail's teleport in location. */
    TELEIN("jailing"),
    /** The message sent to the sender when they teleport someone to a jail's teleport out location. */
    TELEOUT("jailing"),
    /** The message sent to the sender when they transfer all a jail's prisoners to another jail. */
    TRANSFERALLCOMPLETE("jailing"),
    /** The message sent when another plugin cancels the transferring but doesn't provide a reason why. */
    TRANSFERCANCELLEDBYANOTHERPLUGIN("jailing"),
    /** The message sent to the sender when they transfer someone to a jail and a cell. */
    TRANSFERCOMPLETECELL("jailing"),
    /** The message sent to the sender when they transfer someone to a jail. */
    TRANSFERCOMPLETENOCELL("jailing"),
    /** The message sent to the player when they get transferred to a new jail. */
    TRANSFERRED("jailing"),
    /** The message sent when players are released from jail. */
    UNJAILED("jailing"),
    /** The message sent to the person who released a prisoner from jail. */
    UNJAILSUCCESS("jailing"),
    /** The message sent when you jailing offline players is not allowed. */
    UNALLOWEDTOJAILOFFLINE("jailing"),
    /** The message went when an offline player is unjailed. */
    WILLBEUNJAILED("jailing"),
    /** The message sent when trying to jail a player in an unloaded world. */
    WORLDUNLOADED("jailing"),
    /** The message sent when a player joins and is jailed in a world that is unloaded. */
    WORLDUNLOADEDKICK("jailing"),
    /** The message sent to the sender when they check their jail status and they aren't jailed. */
    YOUARENOTJAILED("jailing"),

    // Handcuffing section
    /** The message sent to the sender when trying to handcuff someone who can't be. */
    CANTBEHANDCUFFED("handcuffing"),
    /** The message sent to the sender whenever they try to handcuff someone who is in jail. */
    CURRENTLYJAILEDHANDCUFF("handcuffing", "currentlyjailed"),
    /** The message sent to the sender when the player doesn't have any handcuffs. */
    NOTHANDCUFFED("handcuffing"),
    /** The message sent to the handcuff on a successful handcuffing. */
    HANDCUFFSON("handcuffing"),
    /** The message sent when players are handcuffed. */
    HANDCUFFED("handcuffing"),
    /** The message sent to the player who has release handcuffs. */
    HANDCUFFSRELEASED("handcuffing"),
    /** The message sent when the player has his/her handcuffs removed. */
    UNHANDCUFFED("handcuffing"),

    // General section, used by different parts
    /** Part message of any messages which require 'all the jails' or such. */
    ALLJAILS("general"),
    /** The one line on signs when the cell is empty. */
    CELLEMPTYSIGN("general"),
    /** The message sent to the sender whenever they try to remove a cell but was unsuccessful due to a prisoner. */
    CELLREMOVALUNSUCCESSFUL("general"),
    /** The message sent whenever a cell is successfully removed. */
    CELLREMOVED("general"),
    /** The message sent when cleaning our cell signs. */
    CLEANEDSIGNS("general"),
    /** The message sent when seeing what signs are invalid. */
    INVALIDSIGNS("general"),
    /** The line on a cell's sign when the prisoner is jailed forever. */
    JAILEDFOREVERSIGN("general"),
    /** The simple word jailing to be put in other parts. */
    JAILING("general"),
    /** The message sent to the sender when they try to remove a jail but there are still prisoners in there. */
    JAILREMOVALUNSUCCESSFUL("general"),
    /** The message sent whenever a jail is successfully removed. */
    JAILREMOVED("general"),
    /** The message sent whenever a player toggles using jail stick to disabled. */
    JAILSTICKDISABLED("general"),
    /** The message sent whenever a player toggles using jail stick to enabled. */
    JAILSTICKENABLED("general"),
    /** The message sent whenever a player tries to toggle using jail stick but the config has it disabled. */
    JAILSTICKUSAGEDISABLED("general"),
    /** Message sent when doing something that requires a cell but the given name of a cell doesn't exist. */
    NOCELL("general"),
    /** Message sent when needing a cell or something and there are no cells. */
    NOCELLS("general"),
    /** Message sent when no invalid signs were found. */
    NOINVALIDSIGNS("general"),
    /** The message sent whenever the sender does something which the jail does not found. */
    NOJAIL("general"),
    /** The message sent whenever the sender does something and there are no jails. */
    NOJAILS("general"),
    /** The message sent whenever the sender/player doesn't have permission. */
    NOPERMISSION("general"),
    /** The message sent whenever the sender/player supplies a number format that is incorrect. */
    NUMBERFORMATINCORRECT("general"),
    /** The message sent whenever something is done that needs a player but doesn't have it. */
    PLAYERCONTEXTREQUIRED("general"),
    /** The message sent whenever an online player is required but not found. */
    PLAYERNOTONLINE("general"),
    /** The message sent to the sender when the plugin data has been reloaded. */
    PLUGINRELOADED("general"),
    /** The message sent to the sender of a command when the plugin didn't start correct. */
    PLUGINNOTLOADED("general"),
    /** The message sent whenever the prisoners are cleared from jail(s). */
    PRISONERSCLEARED("general"),
    /** The format we should use when entering a record into flatfile or showing it. */
    RECORDENTRY("general"),
    /** The message format sent saying how many times a user has been jailed. */
    RECORDTIMESJAILED("general"),
    /** The simple word: sign. */
    SIGN("general"),
    /** The message sent when the signs are refreshed. */
    SIGNSREFRESHED("general"),
    /** The format of the time entry we should use for the record entries. */
    TIMEFORMAT("general"),
    /** The simple word transferring to be put in other parts. */
    TRANSFERRING("general"),
    /** The message sent whenever someone does a command we don't know. */
    UNKNOWNCOMMAND("general"),

    // Jail pay
    /** The message sent when the jail pay portion is not enabled. */
    PAYNOTENABLED("jailpay", "notenabled"),
    /** The message sent when finding out how much it costs. */
    PAYCOST("jailpay", "cost"),
    /** The message sent when finding out how much it costs and they are jailed forever. */
    PAYCOSTINFINITE("jailpay", "costinfinite"),
    /** The message sent when someone tries to pay a negative amount. */
    PAYNONEGATIVEAMOUNTS("jailpay", "nonegativeamounts"),
    /** The message sent when someone is jailed and tries to pay for someone else. */
    PAYCANTPAYWHILEJAILED("jailpay", "cantpayforotherswhilejailed"),
    /** The message sent whenever someone tries to pay an amount they don't have. */
    PAYNOTENOUGHMONEY("jailpay", "notenoughmoney"),
    /** The message sent when they try to pay an amount but it isn't enough for the jailing sentence. */
    PAYNOTENOUGHMONEYPROVIDED("jailpay", "notenoughmoneyprovided"),
    /** The message sent when they pay and get released. */
    PAYPAIDRELEASED("jailpay", "paidreleased"),
    /** The message sent when they pay for someone else and release them. */
    PAYPAIDRELEASEDELSE("jailpay", "paidreleasedelse"),
    /** The message sent when they pay and lower their time. */
    PAYPAIDLOWEREDTIME("jailpay", "paidloweredtime"),
    /** The message sent when they pay and lower someone else's time. */
    PAYPAIDLOWEREDTIMEELSE("jailpay", "paidloweredtimeelse"),

    // Jail vote
    /** The header sent when broadcasting a new jail vote. */
    VOTEBROADCASTHEADER("jailvote.broadcast", "header"),
    /** The footer sent when broadcasting a new jail vote. */
    VOTEBROADCASTFOOTER("jailvote.broadcast", "footer"),
    /** Line1 of the broadcast message sent when a new jail vote is happening. */
    VOTEBROADCASTLINE1("jailvote.broadcast", "line1"),
    /** Line2 of the broadcast message sent when a new jail vote is happening. */
    VOTEBROADCASTLINE2("jailvote.broadcast", "line2"),
    /** Line3 of the broadcast message sent when a new jail vote is happening. */
    VOTEBROADCASTLINE3("jailvote.broadcast", "line3"),
    /** Line4 of the broadcast message sent when a new jail vote is happening. */
    VOTEBROADCASTLINE4("jailvote.broadcast", "line4"),
    /** The message sent when someone tries to vote for a player when a vote isn't running. */
    VOTENOVOTEFORTHATPLAYER("jailvote", "novotegoingforthatplayer"),
    /** The message sent to a player who tries to start a vote to jail someone and doesn't have permission. */
    VOTENOPERMISSIONTOSTART("jailvote", "nopermissiontostartvote"),
    /** The message sent when jail vote is not enabled. */
    VOTENOTENABLED("jailvote", "notenabled"),
    /** The message sent whenever someone's vote is not successful. */
    VOTEUNSUCCESSFUL("jailvote", "voteunsuccessful"),
    /** The message sent whenever a player successfully votes no. */
    VOTENOSUCCESS("jailvote", "votenosuccessful"),
    /** The message sent whenever a player successfully votes yes. */
    VOTEYESSUCCESS("jailvote", "voteyessuccessful"),
    /** The message broadcasted whenever a vote is tied. */
    VOTESTIED("jailvote", "votestied"),
    /** The message broadcasted whenever there are more no votes. */
    VOTESSAIDNO("jailvote", "morenovotes"),
    /** The message broadcasted whenever there aren't the minimum yes votes. */
    VOTESNOTENOUGHYES("jailvote", "notenoughyes"),
    /** The message broadcasted whenever the player the vote is for is no longer online. */
    VOTEPLAYERNOLONGERONLINE("jailvote", "playernolongeronline"),
    /** The message sent when a player tries to vote again for someone. */
    VOTEALREADYVOTEDFOR("jailvote", "alreadyvotedfor"),

    // Confirming action messages.
    /** The message sent when the sender is already confirming something. */
    ALREADY("confirm"),
    /** The message sent when their confirmation has expired. */
    EXPIRED("confirm"),
    /** The message sent to the sender when they tried to confirm something but don't have anything to confirm. */
    NOTHING("confirm"),
    /** The message sent to the sender when they type something and need to confirm it. */
    START("confirm");

    private String                   section, name, path;
    private static YamlConfiguration lang;

    Lang(String section) {
        this.section = section;
        this.name = toString().toLowerCase();
        this.path = "language." + this.section + "." + this.name;
    }

    Lang(String section, String name) {
        this.section = section;
        this.name = name;
        this.path = "language." + this.section + "." + this.name;
    }

    /**
     * Sets the {@link YamlConfiguration} instance to use.
     *
     * @param file
     *            of the language to use.
     */
    public static void setFile(YamlConfiguration file) {
        lang = file;
    }

    /** Gets the {@link YamlConfiguration} instance. */
    public static YamlConfiguration getFile() {
        return lang;
    }

    /** Writes any new language settings to the language file in storage. */
    public static boolean writeNewLanguage(YamlConfiguration newLang) {
        boolean anything = false;

        for(Lang l : values()) {
            if(!lang.contains(l.path)) {
                lang.set(l.path, newLang.getString(l.path));
                anything = true;
            }
        }

        return anything;
    }

    /** Returns the message in the language, no variables are replaced. */
    public String get() {
        return get(new String[] {});
    }

    /** Returns the message in the language, no variables are replaced. */
    public String get(Lang langString) {
        return get(new String[] { langString.get() });
    }

    /**
     * Returns the message in the language, with the provided variables being replaced.
     *
     * @param variables
     *            All the variables to replace, in order from 0 to however many.
     * @return The message as a colorful message or an empty message if that
     *         isn't defined in the language file.
     */
    public String get(String... variables) {
        String message = lang.getString(path);

        if (message == null) return "";

        for (int i = 0; i < variables.length; i++) {
            message = message.replaceAll("%" + i + "%", variables[i]);
        }

        return ChatColor.translateAlternateColorCodes('&', message);
    }
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.graywolf336.jail.JailMain;
import com.graywolf336.jail.JailManager;
import com.graywolf336.jail.command.subcommands.JailCheckCommand;
import com.graywolf336.jail.command.subcommands.JailClearCommand;
import com.graywolf336.jail.command.subcommands.JailCommand;
import com.graywolf336.jail.command.subcommands.JailConfirmCommand;
import com.graywolf336.jail.command.subcommands.JailCreateCellCommand;
import com.graywolf336.jail.command.subcommands.JailCreateCommand;
import com.graywolf336.jail.command.subcommands.JailDeleteCellCommand;
import com.graywolf336.jail.command.subcommands.JailDeleteCellsCommand;
import com.graywolf336.jail.command.subcommands.JailDeleteCommand;
import com.graywolf336.jail.command.subcommands.JailHelpCommand;
import com.graywolf336.jail.command.subcommands.JailListCellsCommand;
import com.graywolf336.jail.command.subcommands.JailListCommand;
import com.graywolf336.jail.command.subcommands.JailMuteCommand;
import com.graywolf336.jail.command.subcommands.JailPayCommand;
import com.graywolf336.jail.command.subcommands.JailRecordCommand;
import com.graywolf336.jail.command.subcommands.JailReloadCommand;
import com.graywolf336.jail.command.subcommands.JailSignsCommand;
import com.graywolf336.jail.command.subcommands.JailStatusCommand;
import com.graywolf336.jail.command.subcommands.JailStickCommand;
import com.graywolf336.jail.command.subcommands.JailStopCommand;
import com.graywolf336.jail.command.subcommands.JailTeleInCommand;
import com.graywolf336.jail.command.subcommands.JailTeleOutCommand;
import com.graywolf336.jail.command.subcommands.JailTimeCommand;
import com.graywolf336.jail.command.subcommands.JailTransferAllCommand;
import com.graywolf336.jail.command.subcommands.JailTransferCommand;
import com.graywolf336.jail.command.subcommands.JailVersionCommand;
import com.graywolf336.jail.command.subcommands.JailVoteCommand;
import com.graywolf336.jail.enums.Lang;
     * @param jailmanager The instance of {@link JailManager}.
     * @param sender The sender of the command.
     * @param args The arguments passed to the command.
     */
    public boolean parseCommand(JailManager jailmanager, CommandSender sender, String[] args) {
        Command c = null;

        //If they didn't provide any arguments (aka just: /jail) then we will need to send them some help
        if(args.length == 0) {
            //TODO: Create the help page(s)
            c = getMatches("jail").get(0);

        }else {
            //Get the matches from the first argument passed
            List<Command> matches = getMatches(args[0].toLowerCase());

            if(matches.isEmpty()) {
                //No matches found, thus it is more likely than not they are trying to jail someone
                c = getMatches("jail").get(0);

            } else if(matches.size() > 1) {
                //If there was found more than one match
                //then let's send the usage of each match to the sender
                for(Command cmd : matches)
                    showUsage(sender, cmd);
                return true;

            }else {
                //Only one match was found, so let's continue
                c = matches.get(0);
            }
        }

        CommandInfo i = c.getClass().getAnnotation(CommandInfo.class);

        // First, let's check if the sender has permission for the command.
        if(!i.permission().isEmpty() && !sender.hasPermission(i.permission())) {
        	jailmanager.getPlugin().debug("Sender has no permission: " + i.permission());
            sender.sendMessage(Lang.NOPERMISSION.get() + (jailmanager.getPlugin().inDebug() ? " (" + i.permission() + ")" : ""));
            return true;
        }

        // Next, let's check if we need a player and then if the sender is actually a player
        if(i.needsPlayer() && !(sender instanceof Player)) {
            jailmanager.getPlugin().debug("Sender is not a player.");
            sender.sendMessage(Lang.PLAYERCONTEXTREQUIRED.get());
            return true;
        }

        // Now, let's check the size of the arguments passed. If it is shorter than the minimum required args, let's show the usage.
        // The reason we are subtracting one is because the command is now `/jail <subcommand>` and the subcommand is viewed as an argument
        if(args.length - 1 < i.minimumArgs()) {
            jailmanager.getPlugin().debug("Sender didn't provide enough arguments.");
            showUsage(sender, c);
            return true;
        }

        // Then, if the maximumArgs doesn't equal -1, we need to check if the size of the arguments passed is greater than the maximum args.
        // The reason we are subtracting one is because the command is now `/jail <subcommand>` and the subcommand is viewed as an argument
        if(i.maxArgs() != -1 && i.maxArgs() < args.length - 1) {
            jailmanager.getPlugin().debug("Sender provided too many arguments.");
            showUsage(sender, c);
            return true;
        }

        // Since everything has been checked and we're all clear, let's execute it.
        // But if get back false, let's show the usage message.
        try {
            if(!c.execute(jailmanager, sender, args)) {
                showUsage(sender, c);
                return true;
            }else {
                return true;
            }
        } catch (Exception e) {
            if(jailmanager.getPlugin().inDebug()) {
                e.printStackTrace();
            }

            jailmanager.getPlugin().getLogger().severe("An error occured while handling the command: " + i.usage());
            showUsage(sender, c);
            return true;
        }
    }

    private List<Command> getMatches(String command) {
        List<Command> result = new ArrayList<Command>();

        for(Entry<String, Command> entry : commands.entrySet()) {
            if(command.matches(entry.getKey())) {
                result.add(entry.getValue());
            }
        }

        return result;
    }

    /**
     * Shows the usage information to the sender, if they have permission.
     * 
     * @param sender The sender of the command
     * @param command The command to send usage of.
     */
    private void showUsage(CommandSender sender, Command command) {
        CommandInfo info = command.getClass().getAnnotation(CommandInfo.class);
        if(!sender.hasPermission(info.permission())) return;

        sender.sendMessage(info.usage());
    }

    private void loadCommands() {
        load(JailCreateCellCommand.class);
        load(JailCheckCommand.class);
        load(JailClearCommand.class);
        load(JailCommand.class);
        load(JailConfirmCommand.class);
        load(JailCreateCommand.class);
        load(JailDeleteCellCommand.class);
        load(JailDeleteCellsCommand.class);
        load(JailDeleteCommand.class);
        load(JailHelpCommand.class);