public interface IEssentialsCommand {

	public String getName();

	public void setEssentials(Essentials essentials);

	public void setEssentialsModule(IEssentialsModule module);

	public void run(Server server, CommandSource sender, String commandLabel, Command command, String[] args);

	public void run(Server server, User user, String commandLabel, Command command, String[] args);

} public interface IMessageRecipient {

    /**
     * Sends (prints) a message to this recipient.
     *
     * @param message message
     */
    void sendMessage(String message);

    /**
     * This method is called when this {@link IMessageRecipient} is sending a message to another {@link IMessageRecipient}.
     * <br />
     * The {@link MessageResponse} that is returned is used to determine what exactly should happen in the {@link #sendMessage(IMessageRecipient,
     * String)} implementation by the {@code sender}.
     *
     * @param recipient recipient to receive the {@code message}
     * @param message message to send
     *
     * @return the response of the message
     */
    MessageResponse sendMessage(IMessageRecipient recipient, String message);

    /**
     * This method is called when this recipient is receiving a message from another {@link IMessageRecipient}.
     * <br />
     * The {@link MessageResponse} that is returned is used to determine what exactly should happen in the {@link #sendMessage(IMessageRecipient,
     * String)} implementation by the {@code sender}.
     * <p />
     * <b>This method should only be called by {@link #sendMessage(IMessageRecipient, String)}.</b>
     *
     * @param sender sender of the {@code message}
     * @param message message being received
     *
     * @return the response of the message
     */
    MessageResponse onReceiveMessage(IMessageRecipient sender, String message);

    /**
     * Returns the name of this recipient. This name is typically used internally to identify this recipient.
     *
     * @return name of this recipient
     *
     * @see #getDisplayName()
     */
    String getName();

    /**
     * Returns the display name of this recipient. This name is typically used when formatting messages.
     *
     * @return display name of this recipient
     */
    String getDisplayName();

    /**
     * Returns whether this recipient is reachable. A case where the recipient is not reachable is if they are offline.
     *
     * @return whether this recipient is reachable
     */
    boolean isReachable();

    /**
     * Returns the {@link IMessageRecipient} this recipient should send replies to.
     *
     * @return message recipient
     */
    IMessageRecipient getReplyRecipient();

    /**
     * Sets the {@link IMessageRecipient} this recipient should send replies to.
     *
     * @param recipient message recipient to set
     */
    void setReplyRecipient(IMessageRecipient recipient);

    /**
     * Represents a response for sending or receiving a message when using {@link IMessageRecipient#sendMessage(IMessageRecipient, String)} or
     * {@link IMessageRecipient#onReceiveMessage(IMessageRecipient, String)}.
     */
    enum MessageResponse {
        /** States that the message was received and assumed readable by the receiver. */
        SUCCESS,
        /** States that the message was received, but the receiver was away, assuming the message was not read. */
        SUCCESS_BUT_AFK,
        /** States that the message was <b>NOT</b> received as a result of the receiver ignoring all messages. */
        MESSAGES_IGNORED,
        /** States that the message was <b>NOT</b> received as a result of the sender being ignored by the recipient. */
        SENDER_IGNORED,
        /** States that the message was <b>NOT</b> received as a result of the recipient being unreachable. */
        UNREACHABLE;

        /**
         * Returns whether this response is a success. In other words equal to {@link #SUCCESS} or {@link #SUCCESS_BUT_AFK}
         *
         * @return whether the response is a success
         */
        public boolean isSuccess() {
            return this == SUCCESS || this == SUCCESS_BUT_AFK;
        }
    }
} public class SimpleMessageRecipient implements IMessageRecipient {

    private final IEssentials ess;
    private final IMessageRecipient parent;
    
    private long lastMessageMs;
    private WeakReference<IMessageRecipient> replyRecipient;

    protected static User getUser(IMessageRecipient recipient) {
        if (recipient instanceof SimpleMessageRecipient) {
            return ((SimpleMessageRecipient) recipient).parent instanceof User ? (User) ((SimpleMessageRecipient) recipient).parent : null;
        }
        return recipient instanceof User ? (User) recipient : null;
    }
    
    public SimpleMessageRecipient(IEssentials ess, IMessageRecipient parent) {
        this.ess = ess;
        this.parent = parent;
    }

    @Override
    public void sendMessage(String message) {
        this.parent.sendMessage(message);
    }

    @Override
    public String getName() {
        return this.parent.getName();
    }

    @Override public String getDisplayName() {
        return this.parent.getDisplayName();
    }

    @Override public MessageResponse sendMessage(IMessageRecipient recipient, String message) {
        MessageResponse messageResponse = recipient.onReceiveMessage(this.parent, message);
        switch (messageResponse) {
            case UNREACHABLE:
                sendMessage(tl("recentlyForeverAlone", recipient.getDisplayName()));
                break;
            case MESSAGES_IGNORED:
                sendMessage(tl("msgIgnore", recipient.getDisplayName()));
                break;
            case SENDER_IGNORED:
                break;
            // When this recipient is AFK, notify the sender. Then, proceed to send the message.
            case SUCCESS_BUT_AFK:
                // Currently, only IUser can be afk, so we unsafely cast to get the afk message.
                if (((IUser) recipient).getAfkMessage() != null) {
                    sendMessage(tl("userAFKWithMessage", recipient.getDisplayName(), ((IUser) recipient).getAfkMessage()));
                } else {
                    sendMessage(tl("userAFK", recipient.getDisplayName()));
                }
            default:
                sendMessage(tl("msgFormat", tl("me"), recipient.getDisplayName(), message));

                // Better Social Spy
                User senderUser = getUser(this);
                User recipientUser = getUser(recipient);
                if (senderUser != null // not null if player.
                    // Dont spy on chats involving socialspy exempt players
                    && !senderUser.isAuthorized("essentials.chat.spy.exempt")
                    && (recipientUser != null && !recipientUser.isAuthorized("essentials.chat.spy.exempt"))) {
                    for (User onlineUser : ess.getOnlineUsers()) {
                        if (onlineUser.isSocialSpyEnabled()
                            // Don't send socialspy messages to message sender/receiver to prevent spam
                            && !onlineUser.equals(senderUser)
                            && !onlineUser.equals(recipient)) {
                            if (senderUser.isMuted() && ess.getSettings().getSocialSpyListenMutedPlayers()) {
                                onlineUser.sendMessage(tl("socialMutedSpyPrefix") + tl("socialSpyMsgFormat", getDisplayName(), recipient.getDisplayName(), message));
                            } else {
                                onlineUser.sendMessage(tl("socialSpyPrefix") + tl("socialSpyMsgFormat", getDisplayName(), recipient.getDisplayName(), message));
                            }
                        }
                    }
                }
        }
        // If the message was a success, set this sender's reply-recipient to the current recipient.
        if (messageResponse.isSuccess()) {
            setReplyRecipient(recipient);
        }
        return messageResponse;
    }

    @Override
    public MessageResponse onReceiveMessage(IMessageRecipient sender, String message) {
        if (!isReachable()) {
            return MessageResponse.UNREACHABLE;
        }
        
        User user = getUser(this);
        boolean afk = false;
        if (user != null) {
            if (user.isIgnoreMsg()
                && !(sender instanceof Console)) { // Console must never be ignored.
                return MessageResponse.MESSAGES_IGNORED;
            }
            afk = user.isAfk();
            // Check whether this recipient ignores the sender, only if the sender is not the console.
            if (sender instanceof IUser && user.isIgnoredPlayer((IUser) sender)) {
                return MessageResponse.SENDER_IGNORED;
            }
        }
        // Display the formatted message to this recipient.
        sendMessage(tl("msgFormat", sender.getDisplayName(), tl("me"), message));

        if (ess.getSettings().isLastMessageReplyRecipient()) {
            // If this recipient doesn't have a reply recipient, initiate by setting the first
            // message sender to this recipient's replyRecipient.
            long timeout = ess.getSettings().getLastMessageReplyRecipientTimeout() * 1000;
            if (getReplyRecipient() == null || !getReplyRecipient().isReachable() 
                || System.currentTimeMillis() - this.lastMessageMs > timeout) {
                setReplyRecipient(sender);
            }
        } else { // Old message functionality, always set the reply recipient to the last person who sent us a message.
            setReplyRecipient(sender);
        }
        this.lastMessageMs = System.currentTimeMillis();
        return afk ? MessageResponse.SUCCESS_BUT_AFK : MessageResponse.SUCCESS;
    }

    @Override public boolean isReachable() {
        return this.parent.isReachable();
    }

    /**
     * {@inheritDoc}
     * <p />
     * <b>This {@link com.earth2me.essentials.messaging.SimpleMessageRecipient} implementation stores the a weak reference to the recipient.</b>
     */
    @Override
    public IMessageRecipient getReplyRecipient() {
        return replyRecipient == null ? null : replyRecipient.get();
    }

    /**
     * {@inheritDoc}
     * <p />
     * <b>This {@link com.earth2me.essentials.messaging.SimpleMessageRecipient} implementation stores the a weak reference to the recipient.</b>
     */
    @Override
    public void setReplyRecipient(final IMessageRecipient replyRecipient) {
        this.replyRecipient = new WeakReference<>(replyRecipient);
    }
} public interface Method {
    /**
     * Encodes the Plugin into an Object disguised as the Plugin. If you want the original Plugin Class you must cast it
     * to the correct Plugin, to do so you have to verify the name and or version then cast.
     * <p/>
     * <pre>
     *  if(method.getName().equalsIgnoreCase("iConomy"))
     *   iConomy plugin = ((iConomy)method.getPlugin());</pre>
     *
     * @return <code>Object</code>
     *
     * @see #getName()
     * @see #getVersion()
     */
    Object getPlugin();

    /**
     * Returns the actual name of this method.
     *
     * @return <code>String</code> Plugin name.
     */
    String getName();

    /**
     * Returns the reported name of this method.
     *
     * @return <code>String</code> Plugin name.
     */
    String getLongName();

    /**
     * Returns the actual version of this method.
     *
     * @return <code>String</code> Plugin version.
     */
    String getVersion();

    /**
     * Returns the amount of decimal places that get stored NOTE: it will return -1 if there is no rounding
     *
     * @return <code>int</code> for each decimal place
     */
    int fractionalDigits();

    /**
     * Formats amounts into this payment methods style of currency display.
     *
     * @param amount Double
     *
     * @return <code>String</code> - Formatted Currency Display.
     */
    String format(double amount);

    /**
     * Allows the verification of bank API existence in this payment method.
     *
     * @return <code>boolean</code>
     */
    boolean hasBanks();

    /**
     * Determines the existence of a bank via name.
     *
     * @param bank Bank name
     *
     * @return <code>boolean</code>
     *
     * @see #hasBanks
     */
    boolean hasBank(String bank);

    /**
     * Determines the existence of an account via name.
     *
     * @param name Account name
     *
     * @return <code>boolean</code>
     */
    boolean hasAccount(String name);

    /**
     * Check to see if an account <code>name</code> is tied to a <code>bank</code>.
     *
     * @param bank Bank name
     * @param name Account name
     *
     * @return <code>boolean</code>
     */
    boolean hasBankAccount(String bank, String name);

    /**
     * Forces an account creation
     *
     * @param name Account name
     *
     * @return <code>boolean</code>
     */
    boolean createAccount(String name);

    /**
     * Forces an account creation
     *
     * @param name    Account name
     * @param balance Initial account balance
     *
     * @return <code>boolean</code>
     */
    boolean createAccount(String name, Double balance);

    /**
     * Returns a <code>MethodAccount</code> class for an account <code>name</code>.
     *
     * @param name Account name
     *
     * @return <code>MethodAccount</code> <em>or</em>  <code>Null</code>
     */
    MethodAccount getAccount(String name);

    /**
     * Returns a <code>MethodBankAccount</code> class for an account <code>name</code>.
     *
     * @param bank Bank name
     * @param name Account name
     *
     * @return <code>MethodBankAccount</code> <em>or</em>  <code>Null</code>
     */
    MethodBankAccount getBankAccount(String bank, String name);

    /**
     * Checks to verify the compatibility between this Method and a plugin. Internal usage only, for the most part.
     *
     * @param plugin Plugin
     *
     * @return <code>boolean</code>
     */
    boolean isCompatible(Plugin plugin);

    /**
     * Set Plugin data.
     *
     * @param plugin Plugin
     */
    void setPlugin(Plugin plugin);


    /**
     * Contains Calculator and Balance functions for Accounts.
     */
    interface MethodAccount {
        double balance();

        boolean set(double amount);

        boolean add(double amount);

        boolean subtract(double amount);

        boolean multiply(double amount);

        boolean divide(double amount);

        boolean hasEnough(double amount);

        boolean hasOver(double amount);

        boolean hasUnder(double amount);

        boolean isNegative();

        boolean remove();

        @Override
        String toString();
    }


    /**
     * Contains Calculator and Balance functions for Bank Accounts.
     */
    interface MethodBankAccount {
        double balance();

        String getBankName();

        int getBankId();

        boolean set(double amount);

        boolean add(double amount);

        boolean subtract(double amount);

        boolean multiply(double amount);

        boolean divide(double amount);

        boolean hasEnough(double amount);

        boolean hasOver(double amount);

        boolean hasUnder(double amount);

        boolean isNegative();

        boolean remove();

        @Override
        String toString();
    }
} public class Methods {
    private static String version = null;
    private static boolean self = false;
    private static Method Method = null;
    private static String preferred = "";
    private static final Set<Method> Methods = new HashSet<>();
    private static final Set<String> Dependencies = new HashSet<>();
    private static final Set<Method> Attachables = new HashSet<>();

    static {
        _init();
    }

    /**
     * Implement all methods along with their respective name & class.
     */
    private static void _init() {
        Dependencies.add("MultiCurrency");
        addMethod("Vault", new com.earth2me.essentials.register.payment.methods.VaultEco());
    }

    /**
     * Used by the plugin to setup version
     *
     * @param v version
     */
    public static void setVersion(String v) {
        version = v;
    }

    /**
     * Use to reset methods during disable
     */
    public static void reset() {
        version = null;
        self = false;
        Method = null;
        preferred = "";
        Attachables.clear();
    }

    /**
     * Use to get version of Register plugin
     *
     * @return version
     */
    public static String getVersion() {
        return version;
    }

    /**
     * Returns an array of payment method names that have been loaded through the <code>_init</code> method.
     *
     * @return <code>Set<String></code> - Array of payment methods that are loaded.
     *
     * @see #setMethod(org.bukkit.plugin.Plugin)
     */
    public static Set<String> getDependencies() {
        return Dependencies;
    }

    /**
     * Interprets Plugin class data to verify whether it is compatible with an existing payment method to use for
     * payments and other various economic activity.
     *
     * @param plugin Plugin data from bukkit, Internal Class file.
     *
     * @return Method <em>or</em> Null
     */
    public static Method createMethod(Plugin plugin) {
        for (Method method : Methods) {
            if (method.isCompatible(plugin)) {
                method.setPlugin(plugin);
                return method;
            }
        }

        return null;
    }

    private static void addMethod(String name, Method method) {
        Dependencies.add(name);
        Methods.add(method);
    }

    /**
     * Verifies if Register has set a payment method for usage yet.
     *
     * @return <code>boolean</code>
     *
     * @see #setMethod(org.bukkit.plugin.Plugin)
     * @see #checkDisabled(org.bukkit.plugin.Plugin)
     */
    public static boolean hasMethod() {
        return (Method != null);
    }

    /**
     * Checks Plugin Class against a multitude of checks to verify it's usability as a payment method.
     *
     * @param <code>PluginManager</code> the plugin manager for the server
     *
     * @return <code>boolean</code> True on success, False on failure.
     */
    public static boolean setMethod(PluginManager manager) {
        if (hasMethod())
            return true;

        if (self) {
            self = false;
            return false;
        }

        int count = 0;
        boolean match = false;
        Plugin plugin = null;

        for (String name : getDependencies()) {
            if (hasMethod())
                break;

            plugin = manager.getPlugin(name);
            if (plugin == null || !plugin.isEnabled())
                continue;

            Method current = createMethod(plugin);
            if (current == null)
                continue;

            if (preferred.isEmpty()) {
                Method = current;
            } else {
                Attachables.add(current);
            }
        }

        if (!preferred.isEmpty()) {
            do {
                if (hasMethod()) {
                    match = true;
                } else {
                    for (Method attached : Attachables) {
                        if (attached == null)
                            continue;

                        if (hasMethod()) {
                            match = true;
                            break;
                        }

                        if (preferred.isEmpty())
                            Method = attached;

                        if (count == 0) {
                            if (preferred.equalsIgnoreCase(attached.getName())) {
                                Method = attached;
                            } else {
                                Method = attached;
                            }
                        }
                    }

                    count++;
                }
            } while (!match);
        }

        return hasMethod();
    }

    /**
     * Sets the preferred economy
     *
     * @return <code>boolean</code>
     */
    public static boolean setPreferred(String check) {
        if (getDependencies().contains(check)) {
            preferred = check;
            return true;
        }

        return false;
    }

    /**
     * Grab the existing and initialized (hopefully) Method Class.
     *
     * @return <code>Method</code> <em>or</em> <code>Null</code>
     */
    public static Method getMethod() {
        return Method;
    }

    /**
     * Verify is a plugin is disabled, only does this if we there is an existing payment method initialized in
     * Register.
     *
     * @param method Plugin data from bukkit, Internal Class file.
     *
     * @return <code>boolean</code>
     */
    public static boolean checkDisabled(Plugin method) {
        if (!hasMethod())
            return true;

        if (Method.isCompatible(method))
            Method = null;

        return (Method == null);
    }
} public class DateUtil {
    private static Pattern timePattern = Pattern.compile("(?:([0-9]+)\\s*y[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*mo[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*w[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*d[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*h[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*m[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*(?:s[a-z]*)?)?", Pattern.CASE_INSENSITIVE);
    private static final int maxYears = 100000;

    public static String removeTimePattern(String input) {
        return timePattern.matcher(input).replaceFirst("").trim();
    }

    public static long parseDateDiff(String time, boolean future) throws Exception {
        Matcher m = timePattern.matcher(time);
        int years = 0;
        int months = 0;
        int weeks = 0;
        int days = 0;
        int hours = 0;
        int minutes = 0;
        int seconds = 0;
        boolean found = false;
        while (m.find()) {
            if (m.group() == null || m.group().isEmpty())
                continue;

            for (int i = 0; i < m.groupCount(); i++) {
                if (m.group(i) != null && !m.group(i).isEmpty()) {
                    found = true;
                    break;
                }
            }
            if (found) {
                if (m.group(1) != null && !m.group(1).isEmpty())
                    years = Integer.parseInt(m.group(1));

                if (m.group(2) != null && !m.group(2).isEmpty())
                    months = Integer.parseInt(m.group(2));

                if (m.group(3) != null && !m.group(3).isEmpty())
                    weeks = Integer.parseInt(m.group(3));

                if (m.group(4) != null && !m.group(4).isEmpty())
                    days = Integer.parseInt(m.group(4));

                if (m.group(5) != null && !m.group(5).isEmpty())
                    hours = Integer.parseInt(m.group(5));

                if (m.group(6) != null && !m.group(6).isEmpty())
                    minutes = Integer.parseInt(m.group(6));

                if (m.group(7) != null && !m.group(7).isEmpty())
                    seconds = Integer.parseInt(m.group(7));

                break;
            }
        }
        if (!found) {
            throw new Exception(tl("illegalDate"));
        }
        Calendar c = new GregorianCalendar();
        if (years > 0) {
            if (years > maxYears)
                years = maxYears;

            c.add(Calendar.YEAR, years * (future ? 1 : -1));
        }
        if (months > 0)
            c.add(Calendar.MONTH, months * (future ? 1 : -1));

        if (weeks > 0)
            c.add(Calendar.WEEK_OF_YEAR, weeks * (future ? 1 : -1));

        if (days > 0)
            c.add(Calendar.DAY_OF_MONTH, days * (future ? 1 : -1));

        if (hours > 0)
            c.add(Calendar.HOUR_OF_DAY, hours * (future ? 1 : -1));

        if (minutes > 0)
            c.add(Calendar.MINUTE, minutes * (future ? 1 : -1));

        if (seconds > 0)
            c.add(Calendar.SECOND, seconds * (future ? 1 : -1));

        Calendar max = new GregorianCalendar();
        max.add(Calendar.YEAR, 10);
        if (c.after(max))
            return max.getTimeInMillis();

        return c.getTimeInMillis();
    }

    static int dateDiff(int type, Calendar fromDate, Calendar toDate, boolean future) {
        int year = Calendar.YEAR;

        int fromYear = fromDate.get(year);
        int toYear = toDate.get(year);
        if (Math.abs(fromYear - toYear) > maxYears) {
            toDate.set(year, fromYear +
                    (future ? maxYears : -maxYears));
        }

        int diff = 0;
        long savedDate = fromDate.getTimeInMillis();
        while ((future && !fromDate.after(toDate)) || (!future && !fromDate.before(toDate))) {
            savedDate = fromDate.getTimeInMillis();
            fromDate.add(type, future ? 1 : -1);
            diff++;
        }
        diff--;
        fromDate.setTimeInMillis(savedDate);
        return diff;
    }

    public static String formatDateDiff(long date) {
        Calendar c = new GregorianCalendar();
        c.setTimeInMillis(date);
        Calendar now = new GregorianCalendar();
        return DateUtil.formatDateDiff(now, c);
    }

    public static String formatDateDiff(Calendar fromDate, Calendar toDate) {
        boolean future = false;
        if (toDate.equals(fromDate))
            return tl("now");

        if (toDate.after(fromDate))
            future = true;

        StringBuilder sb = new StringBuilder();
        int[] types = new int[]{Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND};
        String[] names = new String[]{tl("year"), tl("years"), tl("month"), tl("months"), tl("day"), tl("days"), tl("hour"), tl("hours"), tl("minute"), tl("minutes"), tl("second"), tl("seconds")};
        int accuracy = 0;
        for (int i = 0; i < types.length; i++) {
            if (accuracy > 2) break;

            int diff = dateDiff(types[i], fromDate, toDate, future);
            if (diff > 0) {
                accuracy++;
                sb.append(" ").append(diff).append(" ").append(names[i * 2 + (diff > 1 ? 1 : 0)]);
            }
        }
        if (sb.length() == 0)
            return tl("now");

        return sb.toString().trim();
    }
} public class FormatUtil {
    //Vanilla patterns used to strip existing formats
    static final transient Pattern VANILLA_COLOR_PATTERN = Pattern.compile("\u00a7+[0-9A-Fa-f]");
    static final transient Pattern VANILLA_MAGIC_PATTERN = Pattern.compile("\u00a7+[Kk]");
    static final transient Pattern VANILLA_FORMAT_PATTERN = Pattern.compile("\u00a7+[L-ORl-or]");
    //Essentials '&' convention colour codes
    static final transient Pattern REPLACE_ALL_PATTERN = Pattern.compile("(?<!&)&([0-9a-fk-orA-FK-OR])");
    static final transient Pattern REPLACE_COLOR_PATTERN = Pattern.compile("(?<!&)&([0-9a-fA-F])");
    static final transient Pattern REPLACE_MAGIC_PATTERN = Pattern.compile("(?<!&)&([Kk])");
    static final transient Pattern REPLACE_FORMAT_PATTERN = Pattern.compile("(?<!&)&([l-orL-OR])");
    static final transient Pattern REPLACE_PATTERN = Pattern.compile("&&(?=[0-9a-fk-orA-FK-OR])");
    //Used to prepare xmpp output
    static final transient Pattern LOGCOLOR_PATTERN = Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]");
    static final transient Pattern URL_PATTERN = Pattern.compile("((?:(?:https?)://)?[\\w-_\\.]{2,})\\.([a-zA-Z]{2,3}(?:/\\S+)?)");
    public static final Pattern IPPATTERN = Pattern.compile("^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");

    //This method is used to simply strip the native minecraft colour codes
    public static String stripFormat(final String input) {
        if (input == null)
            return null;
        return ChatColor.stripColor(input);
    }

    //This method is used to simply strip the & convention colour codes
    public static String stripEssentialsFormat(final String input) {
        if (input == null)
            return null;
        return stripColor(input, REPLACE_ALL_PATTERN);
    }

    //This is the general permission sensitive message format function, checks for urls.
    public static String formatMessage(final IUser user, final String permBase, final String input) {
        if (input == null)
            return null;
        String message = formatString(user, permBase, input);
        if (!user.isAuthorized(permBase + ".url"))
            message = FormatUtil.blockURL(message);

        return message;
    }

    //This method is used to simply replace the ess colour codes with minecraft ones, ie &c
    public static String replaceFormat(final String input) {
        if (input == null) {
            return null;
        }
        return replaceColor(input, REPLACE_ALL_PATTERN);
    }

    static String replaceColor(final String input, final Pattern pattern) {
        return REPLACE_PATTERN.matcher(pattern.matcher(input).replaceAll("\u00a7$1")).replaceAll("&");
    }

    //This is the general permission sensitive message format function, does not touch urls.
    public static String formatString(final IUser user, final String permBase, final String input) {
        if (input == null) {
            return null;
        }
        String message;
        if (user.isAuthorized(permBase + ".color")) {
            message = replaceColor(input, REPLACE_COLOR_PATTERN);
        } else {
            message = stripColor(input, VANILLA_COLOR_PATTERN);
        }
        if (user.isAuthorized(permBase + ".magic")) {
            message = replaceColor(message, REPLACE_MAGIC_PATTERN);
        } else {
            message = stripColor(message, VANILLA_MAGIC_PATTERN);
        }
        if (user.isAuthorized(permBase + ".format")) {
            message = replaceColor(message, REPLACE_FORMAT_PATTERN);
        } else {
            message = stripColor(message, VANILLA_FORMAT_PATTERN);
        }
        return message;
    }

    public static String stripLogColorFormat(final String input) {
        if (input == null) {
            return null;
        }
        return stripColor(input, LOGCOLOR_PATTERN);
    }

    static String stripColor(final String input, final Pattern pattern) {
        return pattern.matcher(input).replaceAll("");
    }

    public static String lastCode(final String input) {
        int pos = input.lastIndexOf('\u00a7');
        if (pos == -1 || (pos + 1) == input.length()) {
            return "";
        }
        return input.substring(pos, pos + 2);
    }

    static String blockURL(final String input) {
        if (input == null) {
            return null;
        }
        String text = URL_PATTERN.matcher(input).replaceAll("$1 $2");
        while (URL_PATTERN.matcher(text).find()) {
            text = URL_PATTERN.matcher(text).replaceAll("$1 $2");
        }
        return text;
    }

    public static boolean validIP(String ipAddress) {
        return IPPATTERN.matcher(ipAddress).matches();
    }
} public class NumberUtil {
    static DecimalFormat twoDPlaces = new DecimalFormat("#,###.##");
    static DecimalFormat currencyFormat = new DecimalFormat("#0.00", DecimalFormatSymbols.getInstance(Locale.US));

    // This field is likely to be modified in com.earth2me.essentials.Settings
    // when loading currency format.
    // This ensures that we can supply a constant formatting.
    static final NumberFormat PRETTY_FORMAT = NumberFormat.getInstance(Locale.US);

    static {
        twoDPlaces.setRoundingMode(RoundingMode.HALF_UP);
        currencyFormat.setRoundingMode(RoundingMode.FLOOR);

        PRETTY_FORMAT.setRoundingMode(RoundingMode.FLOOR);
        PRETTY_FORMAT.setGroupingUsed(true);
        PRETTY_FORMAT.setMinimumFractionDigits(2);
        PRETTY_FORMAT.setMaximumFractionDigits(2);
    }

    public static String shortCurrency(final BigDecimal value, final IEssentials ess) {
        return ess.getSettings().getCurrencySymbol() + formatAsCurrency(value);
    }

    public static String formatDouble(final double value) {
        return twoDPlaces.format(value);
    }

    public static String formatAsCurrency(final BigDecimal value) {
        String str = currencyFormat.format(value);
        if (str.endsWith(".00")) {
            str = str.substring(0, str.length() - 3);
        }
        return str;
    }

    public static String formatAsPrettyCurrency(BigDecimal value) {
        String str = PRETTY_FORMAT.format(value);
        if (str.endsWith(".00"))
            str = str.substring(0, str.length() - 3);
        return str;
    }

    public static String displayCurrency(final BigDecimal value, final IEssentials ess) {
        String currency = formatAsPrettyCurrency(value);
        String sign = "";
        if (value.signum() < 0) {
            currency = currency.substring(1);
            sign = "-";
        }
        return sign + tl("currency", ess.getSettings().getCurrencySymbol(), currency);
    }

    public static String displayCurrencyExactly(final BigDecimal value, final IEssentials ess) {
        String currency = value.toPlainString();
        String sign = "";
        if (value.signum() < 0) {
            currency = currency.substring(1);
            sign = "-";
        }
        // return sign + tl("currency", ess.getSettings().getCurrencySymbol(),
        // currency);
        return sign + currency; // Bssentials: fix label.
    }

    public static boolean isInt(final String sInt) {
        try {
            Integer.parseInt(sInt);
        } catch (NumberFormatException e) {
            return false;
        }
        return true;
    }
} public class BssentialsPlugin extends JavaPlugin implements IBssentials {

    private static BssentialsPlugin i;
    public static File warpdir;
    private int registered = 0;
    private Warps warpManager;

    public static File DATA_FOLDER;
    public User CONSOLE_USER;

    public HashMap<UUID, User> users;
    public HashMap<String, IWorld> worlds;

    @Override
    public void onEnable() {
        i = this;
        Bssentials.setInstance(this, (DATA_FOLDER = getDataFolder()));

        warpdir = new File((DATA_FOLDER = getDataFolder()), "warps");
        warpdir.mkdirs();
        warpManager = new Warps(this, warpdir);
        CONSOLE_USER = new ConsoleUser(Bukkit.getConsoleSender());
        users = new HashMap<>();

        saveResource("config.yml", false);
        saveResource("info.txt", false);
        saveResource("motd.txt", false);
        saveResource("rules.txt", false);

        getLogger().info("Loading configuration...");
        try {
            Configs.initConfigs();
        } catch (ConfigException e1) {
            e1.printStackTrace();
        }

        getLogger().info("Registering commands...");

        // Entities for SpawnMob
        for (EntityType e : EntityType.values())
            if (e.isSpawnable()) SpawnMob.mobs.put(String.valueOf(e).toLowerCase(), e);

        register("bssentials", new BssentialsCmd());

        for (Class<? extends BCommand> clazz : getCommandClasses()) {
            try {
                register(clazz.newInstance());
            } catch (InstantiationException | IllegalAccessException e) {
                getLogger().warning("Unable to register \"/" + clazz.getName() + "\" command.");
            }
        }
        register("feed", new Heal());

        Bukkit.getPluginManager().registerEvents(new PlayerJoin(), this);
        Bukkit.getPluginManager().registerEvents(new PlayerLeave(), this);

        getLogger().info("Registered " + registered + " commands");

        getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
            @Override public void run() {
                for (World w : Bukkit.getWorlds())
                    worlds.put(w.getName(), new BukkitWorld(w));

                getLogger().info("Bssentials Version " + getDescription().getVersion());

                if (Bukkit.getPluginManager().getPlugin("Essentials") == null)
                    getLogger().info("It is recomended to install \"https://dev.bukkit.org/projects/essentialsapibridge\" to allow ChestShop, Vault, etc to work.");
            }
        });
    }

    public static bssentials.api.Location copy(org.bukkit.Location from) {
        bssentials.api.Location to = new bssentials.api.Location();
        to.world = from.getWorld().getName();
        to.x = from.getX();
        to.y = from.getY();
        to.pitch = from.getPitch();
        to.yaw = from.getYaw();
        return to;
    }

    public void register(String name, BCommand base) {
        try {
            final Field bukkitCommandMap = Bukkit.getServer().getClass().getDeclaredField("commandMap");

            bukkitCommandMap.setAccessible(true);
            CommandMap commandMap = (CommandMap) bukkitCommandMap.get(Bukkit.getServer());

            if (System.getProperty("bss.debug") != null)
                getLogger().info("[Commands]: Registering: /" + name);
            CommandWrapper wrap = new CommandWrapper(name, base);
            if (commandMap.register(name, "bssentials", wrap))
                registered++;
         } catch(Exception e) { e.printStackTrace(); }
    }

    public void register(BCommand... bases) {
        for (BCommand bcmd : bases) {
            register(bcmd.getClass().getSimpleName().toLowerCase(Locale.ENGLISH), bcmd);
            String permission = bcmd.info.permission();
            if (permission.equalsIgnoreCase("REQUIRES_OP") || permission.equalsIgnoreCase("NONE"))
                continue;
        }
    }

    public List<Class<? extends BCommand>> getCommandClasses() {
        List<Class<? extends BCommand>> classList = new ArrayList<Class<? extends BCommand>>();
        try {
            Enumeration<URL> en = getClass().getClassLoader().getResources("bssentials");

            if (en.hasMoreElements()) {
                URL url = en.nextElement();
                JarURLConnection urlcon = (JarURLConnection) url.openConnection();
                try (JarFile jar = urlcon.getJarFile();) {
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        String entry = entries.nextElement().getName().replaceAll("/", ".").replace(".class", "");
                        if (!(entry.startsWith("bssentials.commands") && entry.length() > 20))
                            continue;
                        try {
                            Class<?> clazz = Class.forName(entry);
                            if (!clazz.getSimpleName().equals("BssentialsCmd") && clazz.isAnnotationPresent(CmdInfo.class) && BCommand.class.isAssignableFrom(clazz))
                                classList.add(clazz.asSubclass(BCommand.class));
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        return classList;
    }

    @Override
    public FileConfiguration getConfig() {
        return null;
    }

    public static BssentialsPlugin getInstance() {
        return i;
    }

    @Override
    public Warps getWarps() {
        return warpManager;
    }

    @SuppressWarnings("deprecation")
    @Override
    public User getUser(Object base) {
        if (base instanceof Player) {
            Player p = (Player) base;
            if (users.containsKey(p.getUniqueId()))
                return users.get(p.getUniqueId());
            User user = new BukkitUser(p);
            users.put(p.getUniqueId(), user);
            return user;
        }
        if (base instanceof String) {
            Player p = Bukkit.getPlayerExact((String)base);
            if (users.containsKey(p.getUniqueId()))
                return users.get(p.getUniqueId());
            User user = new BukkitUser(p);
            users.put(p.getUniqueId(), user);
            return user;
        }
        return CONSOLE_USER;
    }

    @Override
    public BssConfiguration getMainConfig() {
        return Configs.MAIN;
    }

    @Override
    public String getVersion() {
        return this.getDescription().getVersion();
    }

    @Override
    public void broadcastMessage(String string) {
        Bukkit.broadcastMessage(string);
    }

    @Override
    public Collection<IWorld> getWorlds() {
        for (World w : Bukkit.getWorlds()) 
            if (!worlds.containsKey(w.getName()))
                worlds.put(w.getName(), new BukkitWorld(w));
        return worlds.values();
    }

    @Override
    public int getOnlinePlayerCount() {
        return this.getServer().getOnlinePlayers().size();
    }

    @Override
    public IWorld getWorld(String string) {
        for (World w : Bukkit.getWorlds())
            if (!worlds.containsKey(w.getName()))
                worlds.put(w.getName(), new BukkitWorld(w));
        return worlds.get(string);
    }

} @CmdInfo(aliases = {"awayfromkeyboard", "away"})
public class Afk extends BCommand {

    public static Collection<String> afkmap = new ArrayList<>();
    public static boolean displaynameafk;

    @Override
    public boolean onCommand(User sender, String label, String[] args) {
        if (args.length > 0) {
            sender.sendMessage("&4Usage: /afk");
            return true;
        }
        setAFK(sender, !isAFK(sender.getUniqueId()));
        return false;
    }

    /**
     * Check if player is away from keyboard.
     *
     * @return if player is AFK or not.
     * @param target The player to check.
     */
    public static boolean isAFK(UUID uuid) {
        try {
            return afkmap.contains(uuid.toString());
        } catch (NullPointerException ingore) {
            return false;
        }
    }

    public static void setAFK(User target, boolean status) {
        String name = target.getName(displaynameafk);
        if (status) {
            if (!isAFK(target.getUniqueId())) {
                afkmap.add(target.getUniqueId().toString());
                Bssentials.getInstance().broadcastMessage("&7* " + name + "&7 is now AFK!");
            }
        } else {
            if (isAFK(target.getUniqueId())) {
                afkmap.remove(target.getUniqueId().toString());
                Bssentials.getInstance().broadcastMessage("&7* " + name + "&7 is no longer AFK!");
            }
        }
    }

} public class MaxMoneyException extends Exception {
    private static final long serialVersionUID = 1L;
} public class AfkStatusChangeEvent extends StatusChangeEvent {
    public AfkStatusChangeEvent(IUser affected, boolean value) {
        super(affected, affected, value);
    }
} public class JailStatusChangeEvent extends StatusChangeEvent {
    public JailStatusChangeEvent(IUser affected, IUser controller, boolean value) {
        super(affected, controller, value);
    }
} public class UserBalanceUpdateEvent extends Event {
    private static final HandlerList handlers = new HandlerList();
    private final Player player;
    private final BigDecimal originalBalance;
    private BigDecimal balance;

    public UserBalanceUpdateEvent(Player player, BigDecimal originalBalance, BigDecimal balance) {
        this.player = player;
        this.originalBalance = originalBalance;
        this.balance = balance;
    }

    @Override
    public HandlerList getHandlers() {
        return handlers;
    }

    public static HandlerList getHandlerList() {
        return handlers;
    }

    public Player getPlayer() {
        return player;
    }

    public BigDecimal getNewBalance() {
        return balance;
    }
    
    public void setNewBalance(BigDecimal newBalance) {
        Preconditions.checkNotNull(newBalance, "newBalance cannot be null.");
        this.balance = newBalance;
    }

    public BigDecimal getOldBalance() {
        return originalBalance;
    }
} public class ReflUtil {

    public static final NMSVersion V1_8_R3 = NMSVersion.fromString("v1_8_R3");
    public static final NMSVersion V1_9_R1 = NMSVersion.fromString("v1_9_R1");
    public static final NMSVersion V1_10_R1 = NMSVersion.fromString("v1_10_R1");
    public static final NMSVersion V1_11_R1 = NMSVersion.fromString("v1_11_R1");
    public static final NMSVersion V1_12_R1 = NMSVersion.fromString("v1_12_R1");
    public static final NMSVersion V1_13_R1 = NMSVersion.fromString("v1_13_R1");
    public static final NMSVersion V1_13_R2 = NMSVersion.fromString("v1_13_R2");

    private static NMSVersion nmsVersionObject;
    private static String nmsVersion;

    public static String getNMSVersion() {
        if (nmsVersion == null) {
            String name = Bukkit.getServer().getClass().getName();
            String[] parts = name.split("\\.");
            nmsVersion = parts[3];
        }
        return nmsVersion;
    }

    public static NMSVersion getNmsVersionObject() {
        return nmsVersionObject == null ? (nmsVersionObject = NMSVersion.fromString(getNMSVersion())) : nmsVersionObject ;
    }

    public static boolean isLowerThan(NMSVersion n) {
        return false;
    }

    public boolean isHigherThanOrEqualTo(NMSVersion n) {
        return true;
    }

    public static class NMSVersion implements Comparable<NMSVersion> {
        private final int major, minor, release;
        
        public static NMSVersion fromString(String s) {
            String[] strs = s.split("_");
            return new NMSVersion(Integer.valueOf(strs[0].substring(1)), Integer.valueOf(strs[1]), Integer.valueOf(strs[2].substring(1)));
        }

        private NMSVersion(int major, int minor, int release) {
            this.major = major;
            this.minor = minor;
            this.release = release;
        }

        public boolean isHigherThan(NMSVersion o) {
            return compareTo(o) > 0;
        }

        public boolean isHigherThanOrEqualTo(NMSVersion o) {
            return compareTo(o) >= 0;
        }

        public boolean isLowerThan(NMSVersion o) {
            return compareTo(o) < 0;
        }

        public boolean isLowerThanOrEqualTo(NMSVersion o) {
            return compareTo(o) <= 0;
        }

        public int getMajor() {
            return major;
        }

        public int getMinor() {
            return minor;
        }

        public int getRelease() {
            return release;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;

            if (o == null || getClass() != o.getClass())
                return false;

            NMSVersion that = (NMSVersion) o;
            return major == that.major && minor == that.minor && release == that.release;
        }

        @Override
        public int hashCode() {
            return 0;
        }

        @Override
        public String toString() {
            return "v" + major + "_" + minor  + "_R" + release;
        }

        @Override
        public int compareTo(NMSVersion o) {
            if (major < o.major) {
                return -1;
            } else if (major > o.major) {
                return 1;
            } else { // equal major
                if (minor < o.minor) {
                    return -1;
                } else if (minor > o.minor)
                    return 1;
                else return Integer.compare(release, o.release);
            }
        }
    }

} public static String tl(Object... s) {
    String strs = "[EssBridge]: ";
    for (Object z : s)
        strs += String.valueOf(z);
    return strs;
}
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.messaging.IMessageRecipient;
import com.earth2me.essentials.messaging.SimpleMessageRecipient;
import com.earth2me.essentials.register.payment.Method;
import com.earth2me.essentials.register.payment.Methods;
import com.earth2me.essentials.utils.DateUtil;
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.NumberUtil;
import bssentials.bukkit.BssentialsPlugin;
import bssentials.commands.Afk;
import net.ess3.api.IEssentials;
import net.ess3.api.MaxMoneyException;
import net.ess3.api.events.AfkStatusChangeEvent;
import net.ess3.api.events.JailStatusChangeEvent;
import net.ess3.api.events.MuteStatusChangeEvent;
import net.ess3.api.events.UserBalanceUpdateEvent;
import net.ess3.nms.refl.ReflUtil;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.earth2me.essentials.I18n.tl;
        if (cost.signum() <= 0) { return true; }
        final BigDecimal remainingBalance = getMoney().subtract(cost);
        if (!permcheck || isAuthorized(
                "essentials.eco.loan")) { return (remainingBalance.compareTo(ess.getSettings().getMinMoney()) >= 0); }
        return (remainingBalance.signum() >= 0);
    }

    public void dispose() {
        ess.runTaskAsynchronously(() -> _dispose() );
    }

    private void _dispose() {
        if (!base.isOnline()) 
            this.base = new OfflinePlayer(getConfigUUID(), ess.getServer());

        cleanup();
    }

    @Override
    public Boolean canSpawnItem(final int itemId) {
        return !ess.getSettings().itemSpawnBlacklist().contains(itemId);
    }

    @Override
    public void setLastLocation() {
        setLastLocation(this.getLocation());
    }

    @Override
    public void setLogoutLocation() {
        setLogoutLocation(this.getLocation());
    }

    @Override
    public void requestTeleport(final User player, final boolean here) {
        teleportRequestTime = System.currentTimeMillis();
        teleportRequester = player == null ? null : player.getBase().getUniqueId();
        teleportRequestHere = here;
        if (player == null) 
            teleportLocation = null;
        else
            teleportLocation = here ? player.getLocation() : this.getLocation();
    }

    @Override
    public boolean hasOutstandingTeleportRequest() {
        return false;
    }

    public UUID getTeleportRequest() {
        return teleportRequester;
    }

    public boolean isTpRequestHere() {
        return teleportRequestHere;
    }

    public Location getTpRequestLocation() {
        return teleportLocation;
    }

    public String getNick(final boolean longnick) {
        return getNick(longnick, true, true);
    }

    public String getNick(final boolean longnick, final boolean withPrefix, final boolean withSuffix) {
        return bss.getNick();
    }

    public void setDisplayNick() {
        if (base.isOnline() && ess.getSettings().changeDisplayName()) {
            this.getBase().setDisplayName(getNick(true));
            if (isAfk())
                updateAfkListName();
            else if (ess.getSettings().changePlayerListName()) {

                String name = getNick(true, ess.getSettings().isAddingPrefixInPlayerlist(), ess.getSettings().isAddingSuffixInPlayerlist());
                try {
                    this.getBase().setPlayerListName(name);
                } catch (IllegalArgumentException e) {
                    if (ess.getSettings().isDebug())
                        logger.info("Playerlist for " + name + " was not updated. Name clashed with another online player.");
                }
            }
        }
    }

    @Override public String getDisplayName() {
        return super.getBase().getDisplayName() == null ? super.getBase().getName() : super.getBase().getDisplayName();
    }

    @Override
    public Teleport getTeleport() {
        return teleport;
    }

    public long getLastOnlineActivity() {
        return lastOnlineActivity;
    }

    public void setLastOnlineActivity(final long timestamp) {
        lastOnlineActivity = timestamp;
    }

    @Override
    public BigDecimal getMoney() {
        return bss.getMoney();
    }

    private BigDecimal _getMoney() {
        return bss.getMoney();
    }

    @Override
    public void setMoney(final BigDecimal value) throws MaxMoneyException {
        bss.setMoney(value);
    }

    public void updateMoneyCache(final BigDecimal value) {
        if (ess.getSettings().isEcoDisabled()) { return; }
        if (Methods.hasMethod() && super.getMoney() != value) {