public interface MethodInterface {
    /**
     * Creates and load the different configuration files.
     */
    void loadFiles();

    /**
     * Request JSON from the <code>url</code> and extract the <code>key</code>
     *
     * @param url the url
     * @param key the key
     * @return the value corresponding to the key
     */
    String getFromUrlJson(String url, String key);

    /**
     * Get the advanced ban version.
     *
     * @return the version
     */
    String getVersion();

    /**
     * Get key from a config file at a <code>path</code>.
     *
     * @param file the file
     * @param path the path
     * @return the string [ ]
     */
    String[] getKeys(Object file, String path);

    /**
     * Get the config.yml file
     *
     * @return the config
     */
    Object getConfig();

    /**
     * Get the messages.yml file
     *
     * @return the messages
     */
    Object getMessages();

    /**
     * Get the layouts.yml file
     *
     * @return the layouts
     */
    Object getLayouts();

    /**
     * Set up metrics.
     */
    void setupMetrics();

    boolean isBungee();

    String clearFormatting(String text);

    /**
     * Get the plugin instance.
     *
     * @return the plugin
     */
    Object getPlugin();

    /**
     * Get the data folder for string punishments.
     *
     * @return the data folder
     */
    File getDataFolder();

    /**
     * Register a command name to be handled by advancedban.
     *
     * @param cmd the cmd
     * @param tabCompleter behaviour when tab completion is triggered
     */
    void setCommandExecutor(String cmd, TabCompleter tabCompleter);

    /**
     * Send a message to a specific player.
     *
     * @param player the player
     * @param msg    the msg
     */
    void sendMessage(Object player, String msg);

    /**
     * Get a players name.
     *
     * @param player the player
     * @return the name
     */
    String getName(Object player);

    /**
     * Get a players name based on the intern uuid fetcher.
     *
     * @param uuid the uuid
     * @return the name
     */
    String getName(String uuid);

    /**
     * Get a players ip.
     *
     * @param player the player
     * @return the ip
     */
    String getIP(Object player);

    /**
     * Get a players uuid based on the intern uuid fetcher.
     *
     * @param player the player
     * @return the intern uuid
     */
    String getInternUUID(Object player);

    /**
     * Get a players uuid based on the intern uuid fetcher.
     *
     * @param player the player
     * @return the intern uuid
     */
    String getInternUUID(String player);

    /**
     * Check if player has the given permission.
     *
     * @param player the player
     * @param perms  the perms
     * @return the boolean
     */
    boolean hasPerms(Object player, String perms);

    /**
     * Get a Permissionable player that can be checked for offline permissions.
     *
     * @param name the name of the user
     * @return the offline permission user
     */
    Permissionable getOfflinePermissionPlayer(String name);

    /**
     * Check whether player is online.
     *
     * @param name the name
     * @return the boolean
     */
    boolean isOnline(String name);

    /**
     * Get online player by name.
     *
     * @param name the name
     * @return the player
     */
    Object getPlayer(String name);

    /**
     * Kick a player.
     *
     * @param player the player
     * @param reason the reason
     */
    void kickPlayer(String player, String reason);

    /**
     * Get online players.
     *
     * @return the object [ ]
     */
    Object[] getOnlinePlayers();

    /**
     * Schedule async repeating task.
     *
     * @param rn the rn
     * @param l1 the l 1
     * @param l2 the l 2
     */
    void scheduleAsyncRep(Runnable rn, long l1, long l2);

    /**
     * Schedule async task.
     *
     * @param rn the rn
     * @param l1 the l 1
     */
    void scheduleAsync(Runnable rn, long l1);

    /**
     * Run async task.
     *
     * @param rn the rn
     */
    void runAsync(Runnable rn);

    /**
     * Run sync task.
     *
     * @param rn the rn
     */
    void runSync(Runnable rn);

    /**
     * (see implementation)
     *
     * @param cmd the cmd
     */
    void executeCommand(String cmd);

    /**
     * (see implementation)
     *
     * @param player the player
     * @return the boolean
     */
    boolean callChat(Object player);

    /**
     * (see implementation)
     *
     * @param player the player
     * @param cmd    the cmd
     * @return the boolean
     */
    boolean callCMD(Object player, String cmd);

    /**
     * Get MySQL.yml file.
     *
     * @return the my sql file
     */
    Object getMySQLFile();

    /**
     * Parse json string and retrieve value at given key.
     *
     * @param json the json
     * @param key  the key
     * @return the string
     */
    String parseJSON(InputStreamReader json, String key);

    /**
     * Parse json string and retrieve value at given key.
     *
     * @param json the json
     * @param key  the key
     * @return the string
     */
    String parseJSON(String json, String key);

    /**
     * Get boolean at the given path from the given file.
     *
     * @param file the file
     * @param path the path
     * @return the boolean
     */
    Boolean getBoolean(Object file, String path);

    /**
     * Get string at the given path from the given file.
     *
     * @param file the file
     * @param path the path
     * @return the string
     */
    String getString(Object file, String path);

    /**
     * Get long at the given path from the given file.
     *
     * @param file the file
     * @param path the path
     * @return the long
     */
    Long getLong(Object file, String path);

    /**
     * Get integer at the given path from the given file.
     *
     * @param file the file
     * @param path the path
     * @return the integer
     */
    Integer getInteger(Object file, String path);

    /**
     * Get string list at the given path from the given file.
     *
     * @param file the file
     * @param path the path
     * @return the string list
     */
    List<String> getStringList(Object file, String path);

    /**
     * Get boolean at the given path from the given file or default if not present.
     *
     * @param file the file
     * @param path the path
     * @param def  the def
     * @return the boolean
     */
    boolean getBoolean(Object file, String path, boolean def);

    /**
     * Get string at the given path from the given file or default if not present.
     *
     * @param file the file
     * @param path the path
     * @param def  the def
     * @return the string
     */
    String getString(Object file, String path, String def);

    /**
     * Get long at the given path from the given file or default if not present.
     *
     * @param file the file
     * @param path the path
     * @param def  the def
     * @return the long
     */
    long getLong(Object file, String path, long def);

    /**
     * Get integer at the given path from the given file or default if not present.
     *
     * @param file the file
     * @param path the path
     * @param def  the def
     * @return the integer
     */
    int getInteger(Object file, String path, int def);

    /**
     * Check whether file contains given path.
     *
     * @param file the file
     * @param path the path
     * @return the boolean
     */
    boolean contains(Object file, String path);

    /**
     * Get file name.
     *
     * @param file the file
     * @return the file name
     */
    String getFileName(Object file);

    /**
     * Call punishment event.
     *
     * @param punishment the punishment
     */
    void callPunishmentEvent(Punishment punishment);

    /**
     * Call revoke punishment event.
     *
     * @param punishment the punishment
     * @param massClear  the mass clear
     */
    void callRevokePunishmentEvent(Punishment punishment, boolean massClear);

    /**
     * Check whether server is in online mode.
     * Should indicate whether the intern uuid fetcher is going to work.
     *
     * @return the boolean
     */
    boolean isOnlineMode();

    /**
     * Broadcast a message to every user with the given permission.
     *
     * @param perm         the perm
     * @param notification the notification
     */
    void notify(String perm, List<String> notification);

    /**
     * Log a message.
     *
     * @param msg the msg
     */
    void log(String msg);

    /**
     * Whether this instance is used for unit testing.
     *
     * @return the boolean
     */
    boolean isUnitTesting();
} public class Universal {

    private static Universal instance = null;

    public static void setRedis(boolean redis) {
        Universal.redis = redis;
    }

    private final Map<String, String> ips = new HashMap<>();
    private MethodInterface mi;
    private LogManager logManager;

    private static boolean redis = false;


    private final Gson gson = new Gson();





    /**
     * Get universal.
     *
     * @return the universal instance
     */
    public static Universal get() {
        return instance == null ? instance = new Universal() : instance;
    }

    /**
     * Initially sets up the plugin.
     *
     * @param mi the mi
     */
    public void setup(MethodInterface mi) {
        this.mi = mi;
        mi.loadFiles();
        logManager = new LogManager();
        UpdateManager.get().setup();
        UUIDManager.get().setup();

        try {
            DatabaseManager.get().setup(mi.getBoolean(mi.getConfig(), "UseMySQL", false));
        } catch (Exception ex) {
            log("Failed enabling database-manager...");
            debugException(ex);
        }

        mi.setupMetrics();
        PunishmentManager.get().setup();

        for (Command command : Command.values()) {
            for (String commandName : command.getNames()) {
                mi.setCommandExecutor(commandName, command.getTabCompleter());
            }
        }

        String upt = "You have the newest version";
        String response = getFromURL("https://api.spigotmc.org/legacy/update.php?resource=8695");
        if (response == null) {
            upt = "Failed to check for updates :(";
        } else if ((!mi.getVersion().startsWith(response))) {
            upt = "There is a new version available! [" + response + "]";
        }

        if (mi.getBoolean(mi.getConfig(), "DetailedEnableMessage", true)) {
            mi.log("\n \n&8[]=====[&7Enabling AdvancedBan&8]=====[]&r"
                    + "\n&8| &cInformation:&r"
                    + "\n&8|   &cName: &7AdvancedBan&r"
                    + "\n&8|   &cDeveloper: &7Leoko&r"
                    + "\n&8|   &cVersion: &7" + mi.getVersion() + "&r"
                    + "\n&8|   &cStorage: &7" + (DatabaseManager.get().isUseMySQL() ? "MySQL (external)" : "HSQLDB (local)") + "&r"
                    + "\n&8| &cSupport:&r"
                    + "\n&8|   &cGithub: &7https://github.com/DevLeoko/AdvancedBan/issues &r"
                    + "\n&8|   &cDiscord: &7https://discord.gg/ycDG6rS &r"
                    + "\n&8| &cTwitter: &7@LeokoGar&r"
                    + "\n&8| &cUpdate:&r"
                    + "\n&8|   &7" + upt  + "&r"
                    + "\n&8[]================================[]&r\n ");
        } else {
            mi.log("&cEnabling AdvancedBan on Version &7&r" + mi.getVersion());
            mi.log("&cCoded by &7Leoko &8| &7Twitter: @LeokoGar&r");
        }
    }

    /**
     * Shutdown.
     */
    public void shutdown() {
        DatabaseManager.get().shutdown();

        if (mi.getBoolean(mi.getConfig(), "DetailedDisableMessage", true)) {
            mi.log("\n \n&8[]=====[&7Disabling AdvancedBan&8]=====[]"
                    + "\n&8| &cInformation:"
                    + "\n&8|   &cName: &7AdvancedBan"
                    + "\n&8|   &cDeveloper: &7Leoko"
                    + "\n&8|   &cVersion: &7" + getMethods().getVersion()
                    + "\n&8|   &cStorage: &7" + (DatabaseManager.get().isUseMySQL() ? "MySQL (external)" : "HSQLDB (local)")
                    + "\n&8| &cSupport:"
                    + "\n&8|   &cGithub: &7https://github.com/DevLeoko/AdvancedBan/issues"
                    + "\n&8|   &cDiscord: &7https://discord.gg/ycDG6rS"
                    + "\n&8| &cTwitter: &7@LeokoGar"
                    + "\n&8[]================================[]&r\n ");
        } else {
            mi.log("&cDisabling AdvancedBan on Version &7" + getMethods().getVersion());
            mi.log("&cCoded by Leoko &8| &7Twitter: @LeokoGar");
        }
    }

    /**
     * Gets methods.
     *
     * @return the methods
     */
    public MethodInterface getMethods() {
        return mi;
    }

    /**
     * Is bungee boolean.
     *
     * @return the boolean
     */
    public boolean isBungee() {
        return mi.isBungee();
    }

    public Map<String, String> getIps() {
        return ips;
    }

    public static boolean isRedis() {
        return redis;
    }

    public Gson getGson() {
        return gson;
    }

    /**
     * Gets from url.
     *
     * @param surl the surl
     * @return the from url
     */
    public String getFromURL(String surl) {
        String response = null;
        try {
            URL url = new URL(surl);
            Scanner s = new Scanner(url.openStream());
            if (s.hasNext()) {
                response = s.next();
                s.close();
            }
        } catch (IOException exc) {
            debug("!! Failed to connect to URL: " + surl);
        }
        return response;
    }

    /**
     * Is mute command boolean.
     *
     * @param cmd the cmd
     * @return the boolean
     */
    public boolean isMuteCommand(String cmd) {
        return isMuteCommand(cmd, getMethods().getStringList(getMethods().getConfig(), "MuteCommands"));
    }

    /**
     * Visible for testing. Do not use this. Please use {@link #isMuteCommand(String)}.
     * 
     * @param cmd          the command
     * @param muteCommands the mute commands from the config
     * @return true if the command matched any of the mute commands.
     */
    boolean isMuteCommand(String cmd, List<String> muteCommands) {
        String[] words = cmd.split(" ");
        // Handle commands with colons
        if (words[0].indexOf(':') != -1) {
            words[0] = words[0].split(":", 2)[1];
        }
        for (String muteCommand : muteCommands) {
            if (muteCommandMatches(words, muteCommand)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Visible for testing. Do not use this.
     * 
     * @param commandWords the command run by a player, separated into its words
     * @param muteCommand a mute command from the config
     * @return true if they match, false otherwise
     */
    boolean muteCommandMatches(String[] commandWords, String muteCommand) {
        // Basic equality check
        if (commandWords[0].equalsIgnoreCase(muteCommand)) {
            return true;
        }
        // Advanced equality check
        // Essentially a case-insensitive "startsWith" for arrays
        if (muteCommand.indexOf(' ') != -1) {
            String[] muteCommandWords = muteCommand.split(" ");
            if (muteCommandWords.length > commandWords.length) {
                return false;
            }
            for (int n = 0; n < muteCommandWords.length; n++) {
                if (!muteCommandWords[n].equalsIgnoreCase(commandWords[n])) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Is exempt player boolean.
     *
     * @param name the name
     * @return the boolean
     */
    public boolean isExemptPlayer(String name) {
        List<String> exempt = getMethods().getStringList(getMethods().getConfig(), "ExemptPlayers");
        if (exempt != null) {
            for (String str : exempt) {
                if (name.equalsIgnoreCase(str)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Broadcast leoko boolean.
     *
     * @return the boolean
     */
    public boolean broadcastLeoko() {
        File readme = new File(getMethods().getDataFolder(), "readme.txt");
        if (!readme.exists()) {
            return true;
        }
        try {
            if (Files.readAllLines(Paths.get(readme.getPath()), Charset.defaultCharset()).get(0).equalsIgnoreCase("I don't want that there will be any message when the dev of this plugin joins the server! I want this even though the plugin is 100% free and the join-message is the only reward for the Dev :(")) {
                return false;
            }
        } catch (IOException ignore) {
        }
        return true;
    }

    /**
     * Call connection string.
     *
     * @param name the name
     * @param ip   the ip
     * @return the string
     */
    public String callConnection(String name, String ip) {
        name = name.toLowerCase();
        String uuid = UUIDManager.get().getUUID(name);
        if (uuid == null) return "[AdvancedBan] Failed to fetch your UUID";

        if (ip != null) {
            getIps().remove(name);
            getIps().put(name, ip);
        }

        InterimData interimData = PunishmentManager.get().load(name, uuid, ip);

        if (interimData == null) {
            if (getMethods().getBoolean(mi.getConfig(), "LockdownOnError", true)) {
                return "[AdvancedBan] Failed to load player data!";
            } else {
                return null;
            }
        }

        Punishment pt = interimData.getBan();

        if (pt == null) {
            interimData.accept();
            return null;
        }

        return pt.getLayoutBSN();
    }

    /**
     * Has perms boolean.
     *
     * @param player the player
     * @param perms  the perms
     * @return the boolean
     */
    public boolean hasPerms(Object player, String perms) {
        if (mi.hasPerms(player, perms)) {
            return true;
        }

        if (mi.getBoolean(mi.getConfig(), "EnableAllPermissionNodes", false)) {
            while (perms.contains(".")) {
                perms = perms.substring(0, perms.lastIndexOf('.'));
                if (mi.hasPerms(player, perms + ".all")) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Log.
     *
     * @param msg the msg
     */
    public void log(String msg) {
        mi.log("§8[§cAdvancedBan§8] §7" + msg);
        debugToFile(msg);
    }

    /**
     * Debug.
     *
     * @param msg the msg
     */
    public void debug(Object msg) {
        if (mi.getBoolean(mi.getConfig(), "Debug", false)) {
            mi.log("§8[§cAdvancedBan§8] §cDebug: §7" + msg.toString());
        }
        debugToFile(msg);
    }

    public void debugException(Exception exc) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        exc.printStackTrace(pw);
        debug(sw.toString());
    }

    /**
     * Debug.
     *
     * @param ex the ex
     */
    public void debugSqlException(SQLException ex) {
        if (mi.getBoolean(mi.getConfig(), "Debug", false)) {
            debug("§7An error has occurred with the database, the error code is: '" + ex.getErrorCode() + "'");
            debug("§7The state of the sql is: " + ex.getSQLState());
            debug("§7Error message: " + ex.getMessage());
        }
        debugException(ex);
    }

    private void debugToFile(Object msg) {
        File debugFile = new File(mi.getDataFolder(), "logs/latest.log");
        if (!debugFile.exists()) {
            try {
                debugFile.createNewFile();
            } catch (IOException ex) {
                System.out.print("An error has occurred creating the 'latest.log' file again, check your server.");
                System.out.print("Error message" + ex.getMessage());
            }
        } else {
            logManager.checkLastLog(false);
        }
        try {
            FileUtils.writeStringToFile(debugFile, "[" + new SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis()) + "] " + mi.clearFormatting(msg.toString()) + "\n", "UTF8", true);
        } catch (IOException ex) {
            System.out.print("An error has occurred writing to 'latest.log' file.");
            System.out.print(ex.getMessage());
        }
    }
} public class MessageManager {

    private static MethodInterface mi() {
    	return Universal.get().getMethods();
    }

    /**
     * Get the message from the given path.<br>
     * The parameters work as described in {@link #sendMessage(Object, String, boolean, String...)}.
     *
     * @param path       the path
     * @param parameters the parameters
     * @return the message
     */
    public static String getMessage(String path, String... parameters) {
    	MethodInterface mi = mi();
        String str = mi.getString(mi.getMessages(), path);
        if (str == null) {
            str = "Failed! See console for details!";
            System.out.println("!! Message-Error!\n"
                    + "In order to solve the problem please:"
                    + "\n  - Check the Message.yml-File for any missing or double \" or '"
                    + "\n  - Visit yamllint.com to  validate your Message.yml"
                    + "\n  - Delete the message file and restart the server");
        } else {
            str = replace(str, parameters).replace('&', '§');
        }
        return str;
    }


    /**
     * Get the message from the given path.<br>
     * The parameters work as described in {@link #sendMessage(Object, String, boolean, String...)}.
     *
     * @param path       the path
     * @param prefix     whether to prepend a prefix (can be overridden by the DisablePrefix option)
     * @param parameters the parameters
     * @return the message
     */
    public static String getMessage(String path, boolean prefix, String... parameters) {
    	MethodInterface mi = mi();
        String prefixStr = "";
        if(prefix && !mi.getBoolean(mi.getConfig(), "Disable Prefix", false))
            prefixStr = getMessage("General.Prefix")+" ";

        return prefixStr+getMessage(path, parameters);
    }

    /**
     * Get the layout (basically just a string list) from the given path in the given file.<br>
     * The parameters work as described in {@link #sendMessage(Object, String, boolean, String...)}.
     *
     * @param file       the file (see {@link MethodInterface#getConfig()}, {@link MethodInterface#getMessages()},
     *                   {@link MethodInterface#getLayouts()})
     * @param path       the path
     * @param parameters the parameters
     * @return the layout
     */
    public static List<String> getLayout(Object file, String path, String... parameters) {
    	MethodInterface mi = mi();
        if (mi.contains(file, path)) {
            List<String> list = new ArrayList<>();
            for (String str : mi.getStringList(file, path)) {
                list.add(replace(str, parameters).replace('&', '§'));
            }
            return list;
        }
        String fileName = mi.getFileName(file);
		System.out.println("!! Message-Error in " + fileName + "!\n"
		        + "In order to solve the problem please:"
		        + "\n  - Check the " + fileName + "-File for any missing or double \" or '"
		        + "\n  - Visit yamllint.com to  validate your " + fileName
		        + "\n  - Delete the message file and restart the server");
		return Collections.singletonList("Failed! See console for details!");
    }

    /**
     * Send message from the given path directly to the given receiver.<br><br>
     * <b>How the <code>parameters</code> work:</b>
     * The amount of parameters given has to be an even number as the parameters are interpreted in pairs.<br>
     * The first parameter is the String to search for and the second one is the one it will be replaced with.<br>
     * Same goes for the third and fourth and for the fifth and sixth and so on.<br><br>
     * <b>e.g.:</b> <code>getMessage("some.path", "NAME", "Leoko", "ID", "#342")</code> will get the message located at
     * "some.path" and replace each <i>%NAME%</i> with <i>Leoko</i> and each <i>%ID%</i> with <i>#342</i>.
     *
     * @param receiver   the receiver (Bukkit or Bungeecord player object)
     * @param path       the path
     * @param prefix     whether to use the global prefix
     * @param parameters the parameters
     */
    public static void sendMessage(Object receiver, String path, boolean prefix, String... parameters) {
    	MethodInterface mi = mi();
        final String message = getMessage(path, parameters);
        if(!message.isEmpty()) {
            final String prefixString = prefix && !mi.getBoolean(mi.getConfig(), "Disable Prefix", false) ? getMessage("General.Prefix") + " " : "";
            mi.sendMessage(receiver, prefixString + message);
        }
    }

    private static String replace(String str, String... parameters) {
        for (int i = 0; i < parameters.length - 1; i = i + 2) {
            str = str.replaceAll("%" + parameters[i] + "%", parameters[i + 1]);
        }
        return str;
    }
} public class PunishmentManager {

    private static PunishmentManager instance = null;
    private final Set<Punishment> punishments = Collections.synchronizedSet(new HashSet<>());
    private final Set<Punishment> history = Collections.synchronizedSet(new HashSet<>());
    private final Set<String> cached = Collections.synchronizedSet(new HashSet<>());
    
    private Universal universal() {
    	return Universal.get();
    }

    /**
     * Get the punishment manager.
     *
     * @return the punishment manager instance
     */
    public static synchronized PunishmentManager get() {
        return instance == null ? instance = new PunishmentManager() : instance;
    }

    /**
     * Initially clears out all expired punishments.
     */
    public void setup() {
        DatabaseManager.get().executeStatement(SQLQuery.DELETE_OLD_PUNISHMENTS, TimeManager.getTime());
        // Seems useless as the Interim Data which get's loaded just is ignored
//        for (Object player : mi.getOnlinePlayers()) {
//            String name = mi.getName(player).toLowerCase();
//            load(name, UUIDManager.get().getUUID(name), mi.getIP(player));
//        }
    }

    /**
     * Get a users punishments as {@link InterimData}. This method is meant to be called if the goal eventually is
     * to add the users punishments to the cache. If you are just interested in the specific punishments there are
     * more convenient methods as {@link #getBan(String)}, {@link #getMute(String)}, {@link #getWarns(String)}
     * or {@link #getPunishments(String, PunishmentType, boolean)}.
     *
     * @param name the users name
     * @param uuid the users uuid
     * @param ip   the users ip
     * @return the interim data
     */
    public InterimData load(String name, String uuid, String ip) {
        Set<Punishment> punishments = new HashSet<>();
        Set<Punishment> history = new HashSet<>();
        try (ResultSet resultsPunishments = DatabaseManager.get().executeResultStatement(SQLQuery.SELECT_USER_PUNISHMENTS_WITH_IP, uuid, ip); ResultSet resultsHistory = DatabaseManager.get().executeResultStatement(SQLQuery.SELECT_USER_PUNISHMENTS_HISTORY_WITH_IP, uuid, ip)) {
            if (resultsHistory == null || resultsPunishments == null)
                return null;

            while (resultsPunishments.next()) {
                punishments.add(getPunishmentFromResultSet(resultsPunishments));
            }
            while (resultsHistory.next()) {
                history.add(getPunishmentFromResultSet(resultsHistory));
            }

        } catch (SQLException ex) {
        	Universal universal = universal();
            universal.log("An error has occurred loading the punishments from the database.");
            universal.debugSqlException(ex);
            return null;
        }
        return new InterimData(uuid, name, ip, punishments, history);
    }

    /**
     * Discard a players punishments from the cache.
     *
     * @param name the name
     */
    public void discard(String name) {
        name = name.toLowerCase();
        String ip = Universal.get().getIps().get(name);
        String uuid = UUIDManager.get().getUUID(name);
        cached.remove(name);
        cached.remove(uuid);
        cached.remove(ip);

        Iterator<Punishment> iterator = punishments.iterator();
        while (iterator.hasNext()) {
            Punishment punishment = iterator.next();
            if (punishment.getUuid().equals(uuid) || punishment.getUuid().equals(ip)) {
                iterator.remove();
            }
        }

        iterator = history.iterator();
        while (iterator.hasNext()) {
            Punishment punishment = iterator.next();
            if (punishment.getUuid().equals(uuid) || punishment.getUuid().equals(ip)) {
                iterator.remove();
            }
        }
    }

    /**
     * Get all punishments which belong to the given uuid or ip.
     *
     * @param target  the uuid or ip to search for
     * @param put     the basic punishment type to search for ({@link PunishmentType#BAN} would also include Tempbans).
     *                Use <code>null</code> to search for all punishments.
     * @param current if only active punishments should be included.
     * @return the punishments
     */
    public List<Punishment> getPunishments(String target, PunishmentType put, boolean current) {
        List<Punishment> ptList = new ArrayList<>();

        if (isCached(target)) {
            for (Iterator<Punishment> iterator = (current ? punishments : history).iterator(); iterator.hasNext(); ) {
                Punishment pt = iterator.next();
                if ((put == null || put == pt.getType().getBasic()) && pt.getUuid().equals(target)) {
                    if (!current || !pt.isExpired()) {
                        ptList.add(pt);
                    } else {
                        pt.delete(null, false, false);
                        iterator.remove();
                    }
                }
            }
        } else {
            try (ResultSet rs = DatabaseManager.get().executeResultStatement(current ? SQLQuery.SELECT_USER_PUNISHMENTS : SQLQuery.SELECT_USER_PUNISHMENTS_HISTORY, target)) {
                while (rs.next()) {
                    Punishment punishment = getPunishmentFromResultSet(rs);
                    if ((put == null || put == punishment.getType().getBasic()) && (!current || !punishment.isExpired())) {
                        ptList.add(punishment);
                    }
                }
            } catch (SQLException ex) {
            	Universal universal = universal();
                universal.log("An error has occurred getting the punishments for " + target);
                universal.debugSqlException(ex);
            }
        }
        return ptList;
    }

    /**
     * Get parsed punishments from the database queried by the given {@link SQLQuery}.<br>
     * The parameters work as described in {@link MessageManager#sendMessage(Object, String, boolean, String...)}.
     *
     * @param sqlQuery   the sql query
     * @param parameters the parameters
     * @return the punishments
     */
    public List<Punishment> getPunishments(SQLQuery sqlQuery, Object... parameters) {
        List<Punishment> ptList = new ArrayList<>();

        ResultSet rs = DatabaseManager.get().executeResultStatement(sqlQuery, parameters);
        try {
            while (rs.next()) {
                Punishment punishment = getPunishmentFromResultSet(rs);
                ptList.add(punishment);
            }
            rs.close();
        } catch (SQLException ex) {
        	Universal universal = universal();
            universal.log("An error has occurred executing a query in the database.");
            universal.debug("Query: \n" + sqlQuery);
            universal.debugSqlException(ex);
        }
        return ptList;
    }

    /**
     * Get an active punishment by id.
     *
     * @param id the id
     * @return the punishment
     */
    public Punishment getPunishment(int id) {
        final Optional<Punishment> cachedPunishment = getLoadedPunishments(false).stream()
                .filter(punishment -> punishment.getId() == id).findAny();

        if (cachedPunishment.isPresent())
            return cachedPunishment.get();


        try (ResultSet rs = DatabaseManager.get().executeResultStatement(SQLQuery.SELECT_PUNISHMENT_BY_ID, id)) {
            if (rs.next()) {
                final Punishment punishment = getPunishmentFromResultSet(rs);
                if (!punishment.isExpired())
                    return punishment;
            }
        } catch (SQLException ex) {
        	Universal universal = universal();
            universal.log("An error has occurred getting a punishment by his id.");
            universal.debug("Punishment id: '" + id + "'");
            universal.debugSqlException(ex);
        }

        return null;
    }

    /**
     * Get an active warning by id.
     *
     * @param id the id
     * @return the warning
     */
    public Punishment getWarn(int id) {
        Punishment punishment = getPunishment(id);

        if (punishment == null)
            return null;

        return punishment.getType().getBasic() == PunishmentType.WARNING ? punishment : null;
    }

    /**
     * Get a players active warnings.
     *
     * @param uuid the players uuid
     * @return the warns
     */
    public List<Punishment> getWarns(String uuid) {
        return getPunishments(uuid, PunishmentType.WARNING, true);
    } /**
     * Get an active note by id.
    *
    * @param id the id
    * @return the note
    */
   public Punishment getNote(int id) {
       Punishment punishment = getPunishment(id);

       if (punishment == null)
           return null;

       return punishment.getType().getBasic() == PunishmentType.NOTE ? punishment : null;
   }

   /**
    * Get a players active note.
    *
    * @param uuid the players uuid
    * @return the note
    */
   public List<Punishment> getNotes(String uuid) {
       return getPunishments(uuid, PunishmentType.NOTE, true);
   }

    /**
     * Get a players active ban.
     *
     * @param uuid the players uuid (can also be an IP)
     * @return the ban or <code>null</code> if not banned
     */
    public Punishment getBan(String uuid) {
        List<Punishment> punishments = getPunishments(uuid, PunishmentType.BAN, true);
        return punishments.isEmpty() ? null : punishments.get(0);
    }

    /**
     * Get a players active mute.
     *
     * @param uuid the players uuid
     * @return the mute or <code>null</code> if not muted
     */
    public Punishment getMute(String uuid) {
        List<Punishment> punishments = getPunishments(uuid, PunishmentType.MUTE, true);
        return punishments.isEmpty() ? null : punishments.get(0);
    }

    /**
     * Check whether a player is banned.
     *
     * @param uuid the players uuid (can also be an IP)
     * @return whether the player is banned
     */
    public boolean isBanned(String uuid) {
        return getBan(uuid) != null;
    }

    /**
     * Check whether a player is muted.
     *
     * @param uuid the players uuid
     * @return whether the player is muted
     */
    public boolean isMuted(String uuid) {
        return getMute(uuid) != null;
    }

    /**
     * Check whether the data for the given uuid, ip or username are currently cached.
     *
     * @param target the target (uuid, ip or username)
     * @return whether the targets data is cached
     */
    public boolean isCached(String target) {
        return cached.contains(target);
    }

    /**
     * Mark the InterimData as cached.
     * This method des not acually cache the data, see {@link InterimData#accept()} to do that.
     *
     * @param data the data
     */
    public void setCached(InterimData data) {
        cached.add(data.getName());
        cached.add(data.getIp());
        cached.add(data.getUuid());
    }

    /**
     * Get punishment-time calculation level.
     * This level is represented by the amount a user has been punished using the given time-layout.
     *
     * @param uuid   the players uuid
     * @param layout the time-layout name
     * @return the calculation level
     */
    public int getCalculationLevel(String uuid, String layout) {
        if (isCached(uuid)) {
            return (int) history.stream().filter(pt -> pt.getUuid().equals(uuid) && layout.equalsIgnoreCase(pt.getCalculation())).count();
        }

        int i = 0;
        try (ResultSet resultSet = DatabaseManager.get().executeResultStatement(SQLQuery.SELECT_USER_PUNISHMENTS_HISTORY_BY_CALCULATION, uuid, layout)) {

            while (resultSet.next()) {
                i++;
            }

        } catch (SQLException ex) {
        	Universal universal = universal();
            universal.log("An error has occurred getting the level for the layout '" + layout + "' for '" + uuid + "'");
            universal.debugSqlException(ex);
        }
        return i;
    }

    /**
     * Get how many warnings a player has.
     *
     * @param uuid the players uuid
     * @return the current warning count
     */
    public int getCurrentWarns(String uuid) {
        return getWarns(uuid).size();
    }
    /**
     * Get how many notes a player has.
     *
     * @param uuid the players uuid
     * @return the current note count
     */
    public int getCurrentNotes(String uuid) {
        return getNotes(uuid).size();
    }

    /**
     * Get all cached punishments.
     *
     * @param checkExpired whether to look for and remove expired punishments
     * @return the cached punishments
     */
    public Set<Punishment> getLoadedPunishments(boolean checkExpired) {
        if (checkExpired) {
            List<Punishment> toDelete = new ArrayList<>();
            for (Punishment pu : punishments) {
                if (pu.isExpired()) {
                    toDelete.add(pu);
                }
            }
            for (Punishment pu : toDelete) {
                pu.delete();
            }
        }
        return punishments;
    }

    /**
     * Get a Punishment from a {@link ResultSet}
     *
     * @param rs the result set
     * @return the punishment from the result set
     * @throws SQLException the sql exception
     */
    public Punishment getPunishmentFromResultSet(ResultSet rs) throws SQLException {
        return new Punishment(
                rs.getString("name"),
                rs.getString("uuid"), rs.getString("reason"),
                rs.getString("operator"),
                PunishmentType.valueOf(rs.getString("punishmentType")),
                rs.getLong("start"),
                rs.getLong("end"),
                rs.getString("calculation"),
                rs.getInt("id"));
    }

    /**
     * Get all cached history punishments.
     *
     * @return the loaded history
     */
    public Set<Punishment> getLoadedHistory() {
        return history;
    }


//    public long getCalculation(String layout, String name, String uuid) {
//        long end = TimeManager.getTime();
//        MethodInterface mi = Universal.get().getMethods();
//
//        int i = getCalculationLevel(name, uuid);
//
//        List<String> timeLayout = mi.getStringList(mi.getLayouts(), "Time." + layout);
//        String time = timeLayout.get(timeLayout.size() <= i ? timeLayout.size() - 1 : i);
//        long toAdd = TimeManager.toMilliSec(time.toLowerCase());
//        end += toAdd;
//
//        return end;
//    }
} public class TimeManager {
    /**
     * Get the current timestamp in milliseconds.
     *
     * @return the timestamp
     */
    public static long getTime() {
        return new Date().getTime() + Universal.get().getMethods().getInteger(Universal.get().getMethods().getConfig(), "TimeDiff", 0) * 60 * 60 * 1000;
    }

    /**
     * Convert a Time String to the amount of milliseconds.
     * These Strings are used for the temporary advancedban punish commands.
     *
     * @param s the time string
     * @return the amount of milliseconds equivalent to the given string
     */
    public static long toMilliSec(String s) {
        // This is not my regex :P | From: http://stackoverflow.com/a/8270824
        String[] sl = s.toLowerCase().split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");

        long i = Long.parseLong(sl[0]);
        switch (sl[1]) {
            case "s":
                return i * 1000;
            case "m":
                return i * 1000 * 60;
            case "h":
                return i * 1000 * 60 * 60;
            case "d":
                return i * 1000 * 60 * 60 * 24;
            case "w":
                return i * 1000 * 60 * 60 * 24 * 7;
            case "mo":
                return i * 1000 * 60 * 60 * 24 * 30;
            default:
                return -1;
        }
    }
} public enum Command {
    BAN(
            PunishmentType.BAN.getPerms(),
            ".+",
            new PunishmentTabCompleter(false),
            new PunishmentProcessor(PunishmentType.BAN),
            PunishmentType.BAN.getConfSection("Usage"),
            "ban"),

    TEMP_BAN(
            PunishmentType.TEMP_BAN.getPerms(),
            "(-s )?\\S+ ?([1-9][0-9]*([wdhms]|mo)|#.+)( .*)?",
            new PunishmentTabCompleter(true),
            new PunishmentProcessor(PunishmentType.TEMP_BAN),
            PunishmentType.TEMP_BAN.getConfSection("Usage"),
            "tempban"),

    IP_BAN(
            PunishmentType.IP_BAN.getPerms(),
            ".+",
            new PunishmentTabCompleter(false),
            new PunishmentProcessor(PunishmentType.IP_BAN),
            PunishmentType.IP_BAN.getConfSection("Usage"),
            "ipban", "banip", "ban-ip"),

    TEMP_IP_BAN(
            PunishmentType.TEMP_IP_BAN.getPerms(),
            "(-s )?\\S+ ?([1-9][0-9]*([wdhms]|mo)|#.+)( .*)?",
            new PunishmentTabCompleter(true),
            new PunishmentProcessor(PunishmentType.TEMP_IP_BAN),
            PunishmentType.TEMP_IP_BAN.getConfSection("Usage"),
            "tempipban"),

    MUTE(
            PunishmentType.MUTE.getPerms(),
            ".+",
            new PunishmentTabCompleter(false),
            new PunishmentProcessor(PunishmentType.MUTE),
            PunishmentType.MUTE.getConfSection("Usage"),
            "mute"),

    TEMP_MUTE(
            PunishmentType.TEMP_MUTE.getPerms(),
            "(-s )?\\S+ ?([1-9][0-9]*([wdhms]|mo)|#.+)( .*)?",
            new PunishmentTabCompleter(true),
            new PunishmentProcessor(PunishmentType.TEMP_MUTE),
            PunishmentType.TEMP_MUTE.getConfSection("Usage"),
            "tempmute"),

    WARN(
            PunishmentType.WARNING.getPerms(),
            ".+",
            new PunishmentTabCompleter(false),
            new PunishmentProcessor(PunishmentType.WARNING),
            PunishmentType.WARNING.getConfSection("Usage"),
            "warn"),

    TEMP_WARN(
            PunishmentType.TEMP_WARNING.getPerms(),
            "(-s )?\\S+ ?([1-9][0-9]*([wdhms]|mo)|#.+)( .*)?",
            new PunishmentTabCompleter(true),
            new PunishmentProcessor(PunishmentType.TEMP_WARNING),
            PunishmentType.TEMP_WARNING.getConfSection("Usage"),
            "tempwarn"),

    NOTE(
            PunishmentType.NOTE.getPerms(),
            ".+",
            new PunishmentTabCompleter(false),
            new PunishmentProcessor(PunishmentType.NOTE),
            PunishmentType.NOTE.getConfSection("Usage"),
            "note"),

    KICK(
            PunishmentType.KICK.getPerms(),
            ".+",
            new PunishmentTabCompleter(false),
            input -> {
                if (!Universal.get().getMethods().isOnline(input.getPrimaryData())) {
                    MessageManager.sendMessage(input.getSender(), "Kick.NotOnline", true,
                            "NAME", input.getPrimary());
                    return;
                }

                new PunishmentProcessor(PunishmentType.KICK).accept(input);
            },
            PunishmentType.KICK.getConfSection("Usage"),
            "kick"),

    UN_BAN("ab." + PunishmentType.BAN.getName() + ".undo",
            "\\S+",
            new BasicTabCompleter("[Name/IP]"),
            new RevokeProcessor(PunishmentType.BAN),
            "Un" + PunishmentType.BAN.getConfSection("Usage"),
            "unban"),

    UN_MUTE("ab." + PunishmentType.MUTE.getName() + ".undo",
            "\\S+",
            new BasicTabCompleter(CleanTabCompleter.PLAYER_PLACEHOLDER, "[Name]"),
            new RevokeProcessor(PunishmentType.MUTE),
            "Un" + PunishmentType.MUTE.getConfSection("Usage"),
            "unmute"),

    UN_WARN("ab." + PunishmentType.WARNING.getName() + ".undo",
            "[0-9]+|(?i:clear \\S+)",
            new CleanTabCompleter((user, args) -> {
                if(args.length == 1) {
                    return list("[ID]", "clear");
                }else if(args.length == 2 && args[0].equalsIgnoreCase("clear")){
                    return list(CleanTabCompleter.PLAYER_PLACEHOLDER, "[Name]");
                } else {
                    return list();
                }
            }),
            input -> {
                final String confSection = PunishmentType.WARNING.getName();
                if (input.getPrimaryData().equals("clear")) {
                    input.next();
                    String name = input.getPrimary();
                    String uuid = processName(input);
                    if (uuid == null)
                        return;

                    List<Punishment> punishments = PunishmentManager.get().getWarns(uuid);
                    if (punishments.isEmpty()) {
                        MessageManager.sendMessage(input.getSender(), "Un" + confSection + ".Clear.Empty",
                                true, "NAME", name);
                        return;
                    }

                    String operator = Universal.get().getMethods().getName(input.getSender());
                    for (Punishment punishment : punishments) {
                        punishment.delete(operator, true, true);
                    }
                    MessageManager.sendMessage(input.getSender(), "Un" + confSection + ".Clear.Done",
                            true, "COUNT", String.valueOf(punishments.size()));
                } else {
                    new RevokeByIdProcessor("Un" + confSection, PunishmentManager.get()::getWarn).accept(input);
                }
            },
            "Un" + PunishmentType.WARNING.getConfSection("Usage"),
            "unwarn"),
    UN_NOTE("ab." + PunishmentType.NOTE.getName() + ".undo",
            "[0-9]+|(?i:clear \\S+)",
            new CleanTabCompleter((user, args) -> {
                if(args.length == 1) {
                    return list("[ID]", "clear");
                }else if(args.length == 2 && args[0].equalsIgnoreCase("clear")){
                    return list(CleanTabCompleter.PLAYER_PLACEHOLDER, "[Name]");
                } else {
                    return list();
                }
            }),
            input -> {
                final String confSection = PunishmentType.NOTE.getName();
                if (input.getPrimaryData().equals("clear")) {
                    input.next();
                    String name = input.getPrimary();
                    String uuid = processName(input);
                    if (uuid == null)
                        return;

                    List<Punishment> punishments = PunishmentManager.get().getNotes(uuid);
                    if (punishments.isEmpty()) {
                        MessageManager.sendMessage(input.getSender(), "Un" + confSection + ".Clear.Empty",
                                true, "NAME", name);
                        return;
                    }

                    String operator = Universal.get().getMethods().getName(input.getSender());
                    for (Punishment punishment : punishments) {
                        punishment.delete(operator, true, true);
                    }
                    MessageManager.sendMessage(input.getSender(), "Un" + confSection + ".Clear.Done",
                            true, "COUNT", String.valueOf(punishments.size()));
                } else {
                    new RevokeByIdProcessor("Un" + confSection, PunishmentManager.get()::getNote).accept(input);
                }
            },
            "Un" + PunishmentType.NOTE.getConfSection("Usage"),
            "unnote"),

    UN_PUNISH("ab.all.undo",
            "[0-9]+",
            new BasicTabCompleter("<ID>"),
            new RevokeByIdProcessor("UnPunish", PunishmentManager.get()::getPunishment),
            "UnPunish.Usage",
            "unpunish"),

    CHANGE_REASON("ab.changeReason",
            "([0-9]+|(?i)(ban|mute) \\S+) .+",
            new CleanTabCompleter((user, args) -> {
                if(args.length <= 1) {
                    return list("<ID>", "ban", "mute");
                }else {
                    boolean playerTarget = args[0].equalsIgnoreCase("ban") || args[0].equalsIgnoreCase("mute");
                    if(args.length == 2 && playerTarget){
                        return list(CleanTabCompleter.PLAYER_PLACEHOLDER, "[Name]");
                    } else if((playerTarget && args.length == 3) || args.length == 2){
                        return list("new reason...");
                    } else {
                        return list();
                    }
                }
            }),
            input -> {
                Punishment punishment;

                if (input.getPrimaryData().matches("[0-9]*")) {
                    int id = Integer.parseInt(input.getPrimaryData());
                    input.next();

                    punishment = PunishmentManager.get().getPunishment(id);
                } else {
                    PunishmentType type = PunishmentType.valueOf(input.getPrimary().toUpperCase());
                    input.next();

                    String target = input.getPrimary();
                    if (!target.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")) {
                        target = processName(input);
                        if (target == null)
                            return;
                    } else {
                        input.next();
                    }

                    punishment = getPunishment(target, type);
                }

                String reason = processReason(input);
                if (reason == null)
                    return;

                if (punishment != null) {
                    punishment.updateReason(reason);
                    MessageManager.sendMessage(input.getSender(), "ChangeReason.Done",
                            true, "ID", String.valueOf(punishment.getId()));
                } else {
                    MessageManager.sendMessage(input.getSender(), "ChangeReason.NotFound", true);
                }
            },
            "ChangeReason.Usage",
            "change-reason"),

    BAN_LIST("ab.banlist",
            "([1-9][0-9]*)?",
            new BasicTabCompleter("<Page>"),
            new ListProcessor(
                    target -> PunishmentManager.get().getPunishments(SQLQuery.SELECT_ALL_PUNISHMENTS_LIMIT, 150),
                    "Banlist", false, false),
            "Banlist.Usage",
            "banlist"),

    HISTORY("ab.history",
            "\\S+( [1-9][0-9]*)?",
            new CleanTabCompleter((user, args) -> {
                if(args.length == 1)
                    return list(CleanTabCompleter.PLAYER_PLACEHOLDER, "[Name]");
                else if(args.length == 2)
                    return list("<Page>");
                else
                    return list();
            }),
            new ListProcessor(
                    target -> PunishmentManager.get().getPunishments(target, null, false),
                    "History", true, true),
            "History.Usage",
            "history"),

    WARNS(null,
            "\\S+( [1-9][0-9]*)?|\\S+|",
            new CleanTabCompleter((user, args) -> {
                if(args.length == 1)
                    if(Universal.get().getMethods().hasPerms(user, "ab.notes.other"))
                        return list(CleanTabCompleter.PLAYER_PLACEHOLDER, "<Name>", "<Page>");
                    else
                        return list("<Page>");
                else if(args.length == 2 && !args[0].matches("\\d+"))
                    return list("<Page>");
                else
                    return list();
            }),
            input -> {
                if (input.hasNext() && !input.getPrimary().matches("[1-9][0-9]*")) {
                    if (!Universal.get().hasPerms(input.getSender(), "ab.warns.other")) {
                        MessageManager.sendMessage(input.getSender(), "General.NoPerms", true);
                        return;
                    }

                    new ListProcessor(
                            target -> PunishmentManager.get().getPunishments(target, PunishmentType.WARNING, true),
                            "Warns", false, true).accept(input);
                } else {
                    if (!Universal.get().hasPerms(input.getSender(), "ab.warns.own")) {
                        MessageManager.sendMessage(input.getSender(), "General.NoPerms", true);
                        return;
                    }

                    String name = Universal.get().getMethods().getName(input.getSender());
                    String identifier = processName(new Command.CommandInput(input.getSender(), new String[]{name}));
                    new ListProcessor(
                            target -> PunishmentManager.get().getPunishments(identifier, PunishmentType.WARNING, true),
                            "WarnsOwn", false, false).accept(input);
                }
            },
            "Warns.Usage",
            "warns"),
    NOTES(null,
            "\\S+( [1-9][0-9]*)?|\\S+|",
            new CleanTabCompleter((user, args) -> {
                if(args.length == 1)
                    if(Universal.get().getMethods().hasPerms(user, "ab.notes.other"))
                        return list(CleanTabCompleter.PLAYER_PLACEHOLDER, "<Name>", "<Page>");
                    else
                        return list("<Page>");
                else if(args.length == 2 && !args[0].matches("\\d+"))
                    return list("<Page>");
                else
                    return list();
            }),
            input -> {
                if (input.hasNext() && !input.getPrimary().matches("[1-9][0-9]*")) {
                    if (!Universal.get().hasPerms(input.getSender(), "ab.notes.other")) {
                        MessageManager.sendMessage(input.getSender(), "General.NoPerms", true);
                        return;
                    }

                    new ListProcessor(
                            target -> PunishmentManager.get().getPunishments(target, PunishmentType.NOTE, true),
                            "Notes", false, true).accept(input);
                } else {
                    if (!Universal.get().hasPerms(input.getSender(), "ab.notes.own")) {
                        MessageManager.sendMessage(input.getSender(), "General.NoPerms", true);
                        return;
                    }

                    String name = Universal.get().getMethods().getName(input.getSender());
                    String identifier = processName(new Command.CommandInput(input.getSender(), new String[]{name}));
                    new ListProcessor(
                            target -> PunishmentManager.get().getPunishments(identifier, PunishmentType.NOTE, true),
                            "NotesOwn", false, false).accept(input);
                }
            },
            "Notes.Usage",
            "notes"),

    CHECK("ab.check",
            "\\S+",
            new BasicTabCompleter(CleanTabCompleter.PLAYER_PLACEHOLDER, "[Name]"),
            input -> {
                String name = input.getPrimary();

                String uuid = processName(input);
                if (uuid == null)
                    return;

                String ip = Universal.get().getIps().getOrDefault(name.toLowerCase(), "none cashed");
                String loc = Universal.get().getMethods().getFromUrlJson("http://ip-api.com/json/" + ip, "country");
                Punishment mute = PunishmentManager.get().getMute(uuid);
                Punishment ban = PunishmentManager.get().getBan(uuid);

                String cached = MessageManager.getMessage("Check.Cached", false);
                String notCached = MessageManager.getMessage("Check.NotCached", false);

                boolean nameCached = PunishmentManager.get().isCached(name.toLowerCase());
                boolean ipCached = PunishmentManager.get().isCached(ip);
                boolean uuidCached = PunishmentManager.get().isCached(uuid);

                Object sender = input.getSender();
                MessageManager.sendMessage(sender, "Check.Header", true, "NAME", name, "CACHED", nameCached ? cached : notCached);
                MessageManager.sendMessage(sender, "Check.UUID", false, "UUID", uuid, "CACHED", uuidCached ? cached : notCached);
                if (Universal.get().hasPerms(sender, "ab.check.ip")) {
                    MessageManager.sendMessage(sender, "Check.IP", false, "IP", ip, "CACHED", ipCached ? cached : notCached);
                }
                MessageManager.sendMessage(sender, "Check.Geo", false, "LOCATION", loc == null ? "failed!" : loc);
                MessageManager.sendMessage(sender, "Check.Mute", false, "DURATION", mute == null ? "§anone" : mute.getType().isTemp() ? "§e" + mute.getDuration(false) : "§cperma");
                if (mute != null) {
                    MessageManager.sendMessage(sender, "Check.MuteReason", false, "REASON", mute.getReason());
                }
                MessageManager.sendMessage(sender, "Check.Ban", false, "DURATION", ban == null ? "§anone" : ban.getType().isTemp() ? "§e" + ban.getDuration(false) : "§cperma");
                if (ban != null) {
                    MessageManager.sendMessage(sender, "Check.BanReason", false, "REASON", ban.getReason());
                }
                MessageManager.sendMessage(sender, "Check.Warn", false, "COUNT", PunishmentManager.get().getCurrentWarns(uuid) + "");

                MessageManager.sendMessage(sender, "Check.Note", false, "COUNT", PunishmentManager.get().getCurrentNotes(uuid) + "");
            },
            "Check.Usage",
            "check"),

    SYSTEM_PREFERENCES("ab.systemprefs",
            ".*",
            null,
            input -> {
                MethodInterface mi = Universal.get().getMethods();
                Calendar calendar = new GregorianCalendar();
                Object sender = input.getSender();
                mi.sendMessage(sender, "§c§lAdvancedBan v2 §cSystemPrefs");
                mi.sendMessage(sender, "§cServer-Time §8» §7" + calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE));
                mi.sendMessage(sender, "§cYour UUID (Intern) §8» §7" + mi.getInternUUID(sender));
                if (input.hasNext()) {
                    String target = input.getPrimaryData();
                    mi.sendMessage(sender, "§c" + target + "'s UUID (Intern) §8» §7" + mi.getInternUUID(target));
                    mi.sendMessage(sender, "§c" + target + "'s UUID (Fetched) §8» §7" + UUIDManager.get().getUUID(target));
                }
            },
            null,
            "systemprefs"),

    ADVANCED_BAN(null,
            ".*",
            new BasicTabCompleter("help", "reload"),
            input -> {
                MethodInterface mi = Universal.get().getMethods();
                Object sender = input.getSender();
                if (input.hasNext()) {
                    if (input.getPrimaryData().equals("reload")) {
                        if (Universal.get().hasPerms(sender, "ab.reload")) {
                            mi.loadFiles();
                            mi.sendMessage(sender, "§a§lAdvancedBan §8§l» §7Reloaded!");
                        } else {
                            MessageManager.sendMessage(sender, "General.NoPerms", true);
                        }
                        return;
                    } else if (input.getPrimaryData().equals("help")) {
                        if (Universal.get().hasPerms(sender, "ab.help")) {
                            mi.sendMessage(sender, "§8");
                            mi.sendMessage(sender, "§c§lAdvancedBan §7Command-Help");
                            mi.sendMessage(sender, "§8");
                            mi.sendMessage(sender, "§c/ban [Name] [Reason/@Layout]");
                            mi.sendMessage(sender, "§8» §7Ban a user permanently");
                            mi.sendMessage(sender, "§c/banip [Name/IP] [Reason/@Layout]");
                            mi.sendMessage(sender, "§8» §7Ban a user by IP");
                            mi.sendMessage(sender, "§c/tempban [Name] [Xmo/Xd/Xh/Xm/Xs/#TimeLayout] [Reason/@Layout]");
                            mi.sendMessage(sender, "§8» §7Ban a user temporary");
                            mi.sendMessage(sender, "§c/mute [Name] [Reason/@Layout]");
                            mi.sendMessage(sender, "§8» §7Mute a user permanently");
                            mi.sendMessage(sender, "§c/tempmute [Name] [Xmo/Xd/Xh/Xm/Xs/#TimeLayout] [Reason/@Layout]");
                            mi.sendMessage(sender, "§8» §7Mute a user temporary");
                            mi.sendMessage(sender, "§c/warn [Name] [Reason/@Layout]");
                            mi.sendMessage(sender, "§8» §7Warn a user permanently");
                            mi.sendMessage(sender, "§c/note [Name] [Note]");
                            mi.sendMessage(sender, "§8» §7Adds a note to a user");
                            mi.sendMessage(sender, "§c/tempwarn [Name] [Xmo/Xd/Xh/Xm/Xs/#TimeLayout] [Reason/@Layout]");
                            mi.sendMessage(sender, "§8» §7Warn a user temporary");
                            mi.sendMessage(sender, "§c/kick [Name] [Reason/@Layout]");
                            mi.sendMessage(sender, "§8» §7Kick a user");
                            mi.sendMessage(sender, "§c/unban [Name/IP]");
                            mi.sendMessage(sender, "§8» §7Unban a user");
                            mi.sendMessage(sender, "§c/unmute [Name]");
                            mi.sendMessage(sender, "§8» §7Unmute a user");
                            mi.sendMessage(sender, "§c/unwarn [ID] or /unwarn clear [Name]");
                            mi.sendMessage(sender, "§8» §7Deletes a warn");
                            mi.sendMessage(sender, "§c/unnote [ID] or /unnote clear [Name]");
                            mi.sendMessage(sender, "§8» §7Deletes a note");
                            mi.sendMessage(sender, "§c/change-reason [ID or ban/mute USER] [New reason]");
                            mi.sendMessage(sender, "§8» §7Changes the reason of a punishment");
                            mi.sendMessage(sender, "§c/unpunish [ID]");
                            mi.sendMessage(sender, "§8» §7Deletes a punishment by ID");
                            mi.sendMessage(sender, "§c/banlist <Page>");
                            mi.sendMessage(sender, "§8» §7See all punishments");
                            mi.sendMessage(sender, "§c/history [Name/IP] <Page>");
                            mi.sendMessage(sender, "§8» §7See a users history");
                            mi.sendMessage(sender, "§c/warns [Name] <Page>");
                            mi.sendMessage(sender, "§8» §7See your or a users warnings");
                            mi.sendMessage(sender, "§c/notes [Name] <Page>");
                            mi.sendMessage(sender, "§8» §7See your or a users notes");
                            mi.sendMessage(sender, "§c/check [Name]");
                            mi.sendMessage(sender, "§8» §7Get all information about a user");
                            mi.sendMessage(sender, "§c/AdvancedBan <reload/help>");
                            mi.sendMessage(sender, "§8» §7Reloads the plugin or shows help page");
                            mi.sendMessage(sender, "§8");
                        } else {
                            MessageManager.sendMessage(sender, "General.NoPerms", true);
                        }
                        return;
                    }
                }


                mi.sendMessage(sender, "§8§l§m-=====§r §c§lAdvancedBan v2 §8§l§m=====-§r ");
                mi.sendMessage(sender, "  §cDev §8• §7Leoko");
                mi.sendMessage(sender, "  §cStatus §8• §a§oStable");
                mi.sendMessage(sender, "  §cVersion §8• §7" + mi.getVersion());
                mi.sendMessage(sender, "  §cLicense §8• §7Public");
                mi.sendMessage(sender, "  §cStorage §8• §7" + (DatabaseManager.get().isUseMySQL() ? "MySQL (external)" : "HSQLDB (local)"));
                mi.sendMessage(sender, "  §cServer §8• §7" + (Universal.get().isBungee() ? "Bungeecord" : "Spigot/Bukkit"));
                if (Universal.get().isBungee()) {
                    mi.sendMessage(sender, "  §cRedisBungee §8• §7" + (Universal.isRedis() ? "true" : "false"));
                }
                mi.sendMessage(sender, "  §cUUID-Mode §8• §7" + UUIDManager.get().getMode());
                mi.sendMessage(sender, "  §cPrefix §8• §7" + (mi.getBoolean(mi.getConfig(), "Disable Prefix", false) ? "" : MessageManager.getMessage("General.Prefix")));
                mi.sendMessage(sender, "§8§l§m-=========================-§r ");
            },
            null,
            "advancedban");

    private final String permission;
    private final Predicate<String[]> syntaxValidator;
    private final TabCompleter tabCompleter;
    private final Consumer<CommandInput> commandHandler;
    private final String usagePath;
    private final String[] names;

    Command(String permission, Predicate<String[]> syntaxValidator,
            TabCompleter tabCompleter, Consumer<CommandInput> commandHandler, String usagePath, String... names) {
        this.permission = permission;
        this.syntaxValidator = syntaxValidator;
        this.tabCompleter = tabCompleter;
        this.commandHandler = commandHandler;
        this.usagePath = usagePath;
        this.names = names;
    }

    Command(String permission, String regex, TabCompleter tabCompleter, Consumer<CommandInput> commandHandler,
            String usagePath, String... names) {
        this(permission, (args) -> String.join(" ", args).matches(regex), tabCompleter, commandHandler, usagePath, names);
    }

    public boolean validateArguments(String[] args) {
        return syntaxValidator.test(args);
    }

    public void execute(Object player, String[] args) {
        commandHandler.accept(new CommandInput(player, args));
    }



    public TabCompleter getTabCompleter() {
        return tabCompleter;
    }

    public static Command getByName(String name) {
        String lowerCase = name.toLowerCase();
        for (Command command : values()) {
            for (String s : command.names) {
                if (s.equals(lowerCase))
                    return command;
            }
        }
        return null;
    }

    public String getPermission() {
        return this.permission;
    }

    public Predicate<String[]> getSyntaxValidator() {
        return this.syntaxValidator;
    }

    public Consumer<CommandInput> getCommandHandler() {
        return this.commandHandler;
    }

    public String getUsagePath() {
        return this.usagePath;
    }

    public String[] getNames() {
        return this.names;
    }

    public static class CommandInput {
        private Object sender;
        private String[] args;

        CommandInput(Object sender, String[] args) {
            this.sender = sender;
            this.args = args;
        }

        public String getPrimary() {
            return args.length == 0 ? null : args[0];
        }

        String getPrimaryData() {
            return getPrimary().toLowerCase();
        }

        public void removeArgument(int index) {
            args = ArrayUtils.remove(args, index);
        }

        public void next() {
            args = ArrayUtils.remove(args, 0);
        }

        public boolean hasNext() {
            return args.length > 0;
        }

        public Object getSender() {
            return this.sender;
        }

        public String[] getArgs() {
            return this.args;
        }
    }
} @FunctionalInterface
public interface Permissionable {
    /**
     * Checks whether the implementing object has the given permission
     *
     * @param permission the permission to check
     * @return the result
     */
    boolean hasPermission(String permission);
} public class Punishment {

    private static final MethodInterface mi = Universal.get().getMethods();
    private final String name, uuid, operator, calculation;
    private final long start, end;
    private final PunishmentType type;

    private String reason;
    private int id;

    public Punishment(String name, String uuid, String reason, String operator, PunishmentType type, long start, long end, String calculation, int id) {
        this.name = name;
        this.uuid = uuid;
        this.reason = reason;
        this.operator = operator;
        this.type = type;
        this.start = start;
        this.end = end;
        this.calculation = calculation;
        this.id = id;
    }

    public static void create(String name, String target, String reason, String operator, PunishmentType type, Long end,
                              String calculation, boolean silent) {
        new Punishment(name, target, reason, operator, end == -1 ? type.getPermanent() : type,
                TimeManager.getTime(), end, calculation, -1)
                .create(silent);
    }

    public String getReason() {
        return (reason == null ? mi.getString(mi.getConfig(), "DefaultReason", "none") : reason).replaceAll("'", "");
    }

    public String getHexId() {
        return Integer.toHexString(id).toUpperCase();
    }

    public String getDate(long date) {
        SimpleDateFormat format = new SimpleDateFormat(mi.getString(mi.getConfig(), "DateFormat", "dd.MM.yyyy-HH:mm"));
        return format.format(new Date(date));
    }

    public void create() {
        create(false);
    }

    public void create(boolean silent) {
        if (id != -1) {
            Universal.get().log("!! Failed! AB tried to overwrite the punishment:");
            Universal.get().log("!! Failed at: " + toString());
            return;
        }

        if (uuid == null) {
            Universal.get().log("!! Failed! AB has not saved the " + getType().getName() + " because there is no fetched UUID");
            Universal.get().log("!! Failed at: " + toString());
            return;
        }

        final int cWarnings = getType().getBasic() == PunishmentType.WARNING ? (PunishmentManager.get().getCurrentWarns(getUuid()) + 1) : 0;

        DatabaseManager.get().executeStatement(SQLQuery.INSERT_PUNISHMENT_HISTORY, getName(), getUuid(), getReason(), getOperator(), getType().name(), getStart(), getEnd(), getCalculation());

        if (getType() != PunishmentType.KICK) {
            try {
                DatabaseManager.get().executeStatement(SQLQuery.INSERT_PUNISHMENT, getName(), getUuid(), getReason(), getOperator(), getType().name(), getStart(), getEnd(), getCalculation());
                try (ResultSet rs = DatabaseManager.get().executeResultStatement(SQLQuery.SELECT_EXACT_PUNISHMENT, getUuid(), getStart(), getType().name())) {
                    if (rs.next()) {
                        id = rs.getInt("id");
                    } else {
                        Universal.get().log("!! Not able to update ID of punishment! Please restart the server to resolve this issue!");
                        Universal.get().log("!! Failed at: " + toString());
                    }
                }
            } catch (SQLException ex) {
                Universal.get().debugSqlException(ex);
            }
        }

        if (!silent) {
            announce(cWarnings);
        }

        if (mi.isOnline(getName())) {
            final Object p = mi.getPlayer(getName());

            if (getType().getBasic() == PunishmentType.BAN || getType() == PunishmentType.KICK) {
                mi.runSync(() -> mi.kickPlayer(getName(), getLayoutBSN()));
            } else {
                if (getType().getBasic() != PunishmentType.NOTE)
                    for (String str : getLayout()) {
                        mi.sendMessage(p, str);
                    }
                PunishmentManager.get().getLoadedPunishments(false).add(this);
            }
        }

        PunishmentManager.get().getLoadedHistory().add(this);

        mi.callPunishmentEvent(this);

        if (getType().getBasic() == PunishmentType.WARNING) {
            String cmd = null;
            for (int i = 1; i <= cWarnings; i++) {
                if (mi.contains(mi.getConfig(), "WarnActions." + i)) {
                    cmd = mi.getString(mi.getConfig(), "WarnActions." + i);
                }
            }
            if (cmd != null) {
                final String finalCmd = cmd.replaceAll("%PLAYER%", getName()).replaceAll("%COUNT%", cWarnings + "").replaceAll("%REASON%", getReason());
                mi.runSync(() -> {
                    mi.executeCommand(finalCmd);
                    Universal.get().log("Executing command: " + finalCmd);
                });
            }
        }
    }

    public void updateReason(String reason) {
        this.reason = reason;

        if (id != -1) {
            DatabaseManager.get().executeStatement(SQLQuery.UPDATE_PUNISHMENT_REASON, reason, id);
        }
    }

    private void announce(int cWarnings) {
        List<String> notification = MessageManager.getLayout(mi.getMessages(),
                getType().getName() + ".Notification",
                "OPERATOR", getOperator(),
                "PREFIX", mi.getBoolean(mi.getConfig(), "Disable Prefix", false) ? "" : MessageManager.getMessage("General.Prefix"),
                "DURATION", getDuration(true),
                "REASON", getReason(),
                "NAME", getName(),
                "ID", String.valueOf(id),
                "HEXID", getHexId(),
                "DATE", getDate(start),
                "COUNT", cWarnings + "");

        mi.notify("ab.notify." + getType().getName(), notification);
    }

    public void delete() {
        delete(null, false, true);
    }

    public void delete(String who, boolean massClear, boolean removeCache) {
        if (getType() == PunishmentType.KICK) {
            Universal.get().log("!! Failed deleting! You are not able to delete Kicks!");
            return;
        }

        if (id == -1) {
            Universal.get().log("!! Failed deleting! The Punishment is not created yet!");
            Universal.get().log("!! Failed at: " + toString());
            return;
        }

        DatabaseManager.get().executeStatement(SQLQuery.DELETE_PUNISHMENT, getId());

        if (removeCache) {
            PunishmentManager.get().getLoadedPunishments(false).remove(this);
        }

        if (who != null) {
            String message = MessageManager.getMessage("Un" + getType().getBasic().getConfSection("Notification"),
                    true, "OPERATOR", who, "NAME", getName());
            mi.notify("ab.undoNotify." + getType().getBasic().getName(), Collections.singletonList(message));

            Universal.get().debug(who + " is deleting a punishment");
        }

        Universal.get().debug("Deleted punishment " + getId() + " from " + getName() + " punishment reason: " + getReason());
        mi.callRevokePunishmentEvent(this, massClear);
    }

    public List<String> getLayout() {
        boolean isLayout = getReason().startsWith("@") || getReason().startsWith("~");

        return MessageManager.getLayout(
                isLayout ? mi.getLayouts() : mi.getMessages(),
                isLayout ? "Message." + getReason().split(" ")[0].substring(1) : getType().getName() + ".Layout",
                "OPERATOR", getOperator(),
                "PREFIX", mi.getBoolean(mi.getConfig(), "Disable Prefix", false) ? "" : MessageManager.getMessage("General.Prefix"),
                "DURATION", getDuration(false),
                "REASON", isLayout ? (getReason().split(" ").length < 2 ? "" : getReason().substring(getReason().split(" ")[0].length() + 1)) : getReason(),
                "HEXID", getHexId(),
                "ID", String.valueOf(id),
                "DATE", getDate(start),
                "COUNT", getType().getBasic() == PunishmentType.WARNING ? (PunishmentManager.get().getCurrentWarns(getUuid()) + 1) + "" : "0");
    }

    public String getDuration(boolean fromStart) {
        String duration = "permanent";
        if (getType().isTemp()) {
            long diff = ceilDiv(getEnd() - (fromStart ? start : TimeManager.getTime()), 1000L);
            if (diff > 60 * 60 * 24) {
                duration = MessageManager.getMessage("General.TimeLayoutD", getDurationParameter("D", diff / 60 / 60 / 24 + "", "H", diff / 60 / 60 % 24 + "", "M", diff / 60 % 60 + "", "S", diff % 60 + ""));
            } else if (diff > 60 * 60) {
                duration = MessageManager.getMessage("General.TimeLayoutH", getDurationParameter("H", diff / 60 / 60 + "", "M", diff / 60 % 60 + "", "S", diff % 60 + ""));
            } else if (diff > 60) {
                duration = MessageManager.getMessage("General.TimeLayoutM", getDurationParameter("M", diff / 60 + "", "S", diff % 60 + ""));
            } else {
                duration = MessageManager.getMessage("General.TimeLayoutS", getDurationParameter("S", diff + ""));
            }
        }
        return duration;
    }

    long ceilDiv(long x, long y) {
        return -Math.floorDiv(-x, y);
    }

    private String[] getDurationParameter(String... parameter) {
        int length = parameter.length;
        String[] newParameter = new String[length * 2];
        for (int i = 0; i < length; i += 2) {
            String name = parameter[i];
            String count = parameter[i + 1];

            newParameter[i] = name;
            newParameter[i + 1] = count;
            newParameter[length + i] = name + name;
            newParameter[length + i + 1] = (count.length() <= 1 ? "0" : "") + count;
        }

        return newParameter;
    }

    public String getLayoutBSN() {
        StringBuilder msg = new StringBuilder();
        for (String str : getLayout()) {
            msg.append("\n").append(str);
        }
        return msg.substring(1);
    }

    public boolean isExpired() {
        return getType().isTemp() && getEnd() <= TimeManager.getTime();
    }

    public String getName() {
        return this.name;
    }

    public String getUuid() {
        return this.uuid;
    }

    public String getOperator() {
        return this.operator;
    }

    public String getCalculation() {
        return this.calculation;
    }

    public long getStart() {
        return this.start;
    }

    public long getEnd() {
        return this.end;
    }

    public PunishmentType getType() {
        return this.type;
    }

    public int getId() {
        return this.id;
    }

    public String toString() {
        return "Punishment(name=" + this.getName() + ", uuid=" + this.getUuid() + ", operator=" + this.getOperator() + ", calculation=" + this.getCalculation() + ", start=" + this.getStart() + ", end=" + this.getEnd() + ", type=" + this.getType() + ", reason=" + this.getReason() + ", id=" + this.getId() + ")";
    }
} public enum PunishmentType {
    BAN("Ban", null, false, "ab.ban.perma"),
    TEMP_BAN("Tempban", BAN, true, "ab.ban.temp"),
    IP_BAN("Ipban", BAN, false, "ab.ipban.perma"),
    TEMP_IP_BAN("Tempipban", BAN, true, "ab.ipban.temp"),
    MUTE("Mute", null, false, "ab.mute.perma"),
    TEMP_MUTE("Tempmute", MUTE, true, "ab.mute.temp"),
    WARNING("Warn", null, false, "ab.warn.perma"),
    TEMP_WARNING("Tempwarn", WARNING, true, "ab.warn.temp"),
    KICK("Kick", null, false, "ab.kick.use"),
    NOTE("Note", null, false, "ab.note.use");

    private final String name;
    private final String perms;
    private final PunishmentType basic;
    private final boolean temp;

    PunishmentType(String name, PunishmentType basic, boolean temp, String perms) {
        this.name = name;
        this.basic = basic;
        this.temp = temp;
        this.perms = perms;
    }

    public static PunishmentType fromCommandName(String cmd) {
        switch (cmd) {
            case "ban":
                return BAN;
            case "tempban":
                return TEMP_BAN;
            case "ban-ip":
            case "banip":
            case "ipban":
                return IP_BAN;
            case "tempipban":
            case "tipban":
                return TEMP_IP_BAN;
            case "mute":
                return MUTE;
            case "tempmute":
                return TEMP_MUTE;
            case "warn":
                return WARNING;
            case "note":
                return NOTE;
            case "tempwarn":
                return TEMP_WARNING;
            case "kick":
                return KICK;
            default:
                return null;
        }
    }

    public String getName() {
        return name;
    }

    public String getPerms() {
        return perms;
    }

    public boolean isTemp() {
        return temp;
    }

    public String getConfSection(String path) {
        return name+"."+path;
    }

    public PunishmentType getBasic() {
        return basic == null ? this : basic;
    }

    public PunishmentType getPermanent() {
        if(this == IP_BAN || this == TEMP_IP_BAN)
            return IP_BAN;

        return getBasic();
    }

    public boolean isIpOrientated() {
        return this == IP_BAN || this == TEMP_IP_BAN;
    }
} public class CommandUtils {
    public static Punishment getPunishment(String target, PunishmentType type) {
        return type == PunishmentType.MUTE
                ? PunishmentManager.get().getMute(target)
                : PunishmentManager.get().getBan(target);
    }

    // Removes name argument and returns uuid (null if failed)
    public static String processName(Command.CommandInput input) {
        String name = input.getPrimary();
        input.next();
        String uuid = UUIDManager.get().getUUID(name.toLowerCase());

        if (uuid == null)
            MessageManager.sendMessage(input.getSender(), "General.FailedFetch",
                    true, "NAME", name);

        return uuid;
    }

    // Removes name/ip argument and returns ip (null if failed)
    public static String processIP(Command.CommandInput input) {
        String name = input.getPrimaryData();
        input.next();
        if (name.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")) {
            return name;
        }
		String ip = Universal.get().getIps().get(name);

		if (ip == null)
		    MessageManager.sendMessage(input.getSender(), "Ipban.IpNotCashed",
		            true, "NAME", name);

		return ip;
    }

    // Builds reason from remaining arguments (null if failed)
    public static String processReason(Command.CommandInput input) {
        MethodInterface mi = Universal.get().getMethods();
        String reason = String.join(" ", input.getArgs());

        if (reason.matches("[~@].+") && !mi.contains(mi.getLayouts(), "Message." + input.getPrimary().substring(1))) {
            MessageManager.sendMessage(input.getSender(), "General.LayoutNotFound",
                    true, "NAME", input.getPrimary().substring(1));
            return null;
        }

        return reason;
    }
}
import me.leoko.advancedban.MethodInterface;
import me.leoko.advancedban.Universal;
import me.leoko.advancedban.manager.MessageManager;
import me.leoko.advancedban.manager.PunishmentManager;
import me.leoko.advancedban.manager.TimeManager;
import me.leoko.advancedban.utils.Command;
import me.leoko.advancedban.utils.Permissionable;
import me.leoko.advancedban.utils.Punishment;
import me.leoko.advancedban.utils.PunishmentType;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import static me.leoko.advancedban.utils.CommandUtils.*;
package me.leoko.advancedban.utils.commands;




public class PunishmentProcessor implements Consumer<Command.CommandInput> {
    private PunishmentType type;

    public PunishmentProcessor(PunishmentType type) {
        this.type = type;
    }

    @Override
    public void accept(Command.CommandInput input) {
        boolean silent = processTag(input, "-s");
        String name = input.getPrimary();

        // extract target
        String target = type.isIpOrientated()
                ? processIP(input)
                : processName(input);
        if (target == null)
            return;

        // is exempted
        if (processExempt(name, target, input.getSender(), type))
            return;

        // calculate duration if necessary
        Long end = -1L;
        String timeTemplate = "";
        if (type.isTemp()) {
            TimeCalculation calculation = processTime(input, target, type);
            if (calculation == null)
                return;

            end = calculation.time;

            if (calculation.template != null)
                timeTemplate = calculation.template;
        }


        // build reason
        String reason = processReason(input);
        if (reason == null)
            return;
        else if (reason.isEmpty())
            reason = null;

        // check if punishment of this type is already active
        if (alreadyPunished(target, type)) {
            MessageManager.sendMessage(input.getSender(), type.getBasic().getName() + ".AlreadyDone",