src/main/java/io/github/flibio/economylite/impl/VirtualDataService.java
public class VirtualDataService extends VirtualServiceCommon {

    public VirtualDataService() {
        super(LocalSqlManager.createInstance(EconomyLite.getInstance(), "data", correctPath(EconomyLite.getInstance().getConfigDir().toString()))
                .get(), true);
    }

    private static String correctPath(String path) {
        if (new File(path).isAbsolute()) {
            return path;
        } else {
            return "./" + path;
        }
    }

}
src/main/java/io/github/flibio/economylite/commands/virtual/VirtualSetCommand.java
@AsyncCommand
@ParentCommand(parentCommand = VirtualEconCommand.class)
@Command(aliases = {"set"}, permission = "economylite.admin.virtual.set")
public class VirtualSetCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.string(Text.of("account")), GenericArguments.doubleNum(Text.of("amount")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("account").isPresent() && args.getOne("amount").isPresent()) {
            String target = args.<String>getOne("account").get();
            BigDecimal newBal = BigDecimal.valueOf(args.<Double>getOne("amount").get());
            Optional<Account> aOpt = EconomyLite.getEconomyService().getOrCreateAccount(target);
            if (aOpt.isPresent()) {
                Account targetAccount = aOpt.get();
                if (targetAccount.setBalance(EconomyLite.getCurrencyService().getCurrentCurrency(), newBal,
                        Cause.of(EventContext.empty(), (EconomyLite.getInstance()))).getResult().equals(ResultType.SUCCESS)) {
                    src.sendMessage(messageStorage.getMessage("command.econ.setsuccess", "name", targetAccount.getDisplayName().toPlain()));
                } else {
                    src.sendMessage(messageStorage.getMessage("command.econ.setfail", "name", targetAccount.getDisplayName().toPlain()));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.error"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

}
src/main/java/io/github/flibio/economylite/commands/admin/RemoveCommand.java
@AsyncCommand
@ParentCommand(parentCommand = EconCommand.class)
@Command(aliases = {"remove"}, permission = "economylite.admin.econ.remove")
public class RemoveCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.user(Text.of("player")), GenericArguments.doubleNum(Text.of("amount")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("player").isPresent() && args.getOne("amount").isPresent()) {
            User target = args.<User>getOne("player").get();
            String targetName = target.getName();
            BigDecimal toRemove = BigDecimal.valueOf(args.<Double>getOne("amount").get());
            Optional<UniqueAccount> uOpt = EconomyLite.getEconomyService().getOrCreateAccount(target.getUniqueId());
            if (uOpt.isPresent()) {
                UniqueAccount targetAccount = uOpt.get();
                if (targetAccount.withdraw(EconomyLite.getCurrencyService().getCurrentCurrency(), toRemove,
                        Cause.of(EventContext.empty(),(EconomyLite.getInstance()))).getResult().equals(ResultType.SUCCESS)) {
                    src.sendMessage(messageStorage.getMessage("command.econ.removesuccess", "name", targetName));
                    attemptNotify(target);
                } else {
                    src.sendMessage(messageStorage.getMessage("command.econ.removefail", "name", targetName));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.error"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

    private void attemptNotify(User target) {
        if (target instanceof Player) {
            ((Player) target).sendMessage(messageStorage.getMessage("command.econ.notify"));
        }
    }

}
src/main/java/io/github/flibio/economylite/impl/economy/LiteCurrency.java
public class LiteCurrency implements Currency {

    private String singular;
    private String plural;
    private String symbol;
    private boolean defaultCur;
    private int digits;

    public LiteCurrency(String singular, String plural, String symbol, boolean defaultCur, int digits) {
        this.singular = singular;
        this.plural = plural;
        this.symbol = symbol;
        this.defaultCur = defaultCur;
        this.digits = digits;
    }

    @Override
    public int getDefaultFractionDigits() {
        return digits;
    }

    @Override
    public Text getDisplayName() {
        return Text.of(singular);
    }

    @Override
    public Text getPluralDisplayName() {
        return Text.of(plural);
    }

    @Override
    public Text getSymbol() {
        return Text.of(symbol);
    }

    @Override
    public boolean isDefault() {
        return defaultCur;
    }

    @Override
    public Text format(BigDecimal amount, int digits) {
        Text label = getPluralDisplayName();
        if (amount.equals(BigDecimal.ONE)) {
            label = getDisplayName();
        }
        return Text.of(String.format(Locale.ENGLISH, "%,.2f", amount.setScale(digits, RoundingMode.HALF_UP)) + " ", label);
    }

    @Override
    public String getId() {
        return "economylite:" + singular.toLowerCase().replaceAll(" ", "_");
    }

    @Override
    public String getName() {
        return singular;
    }

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof Currency))
            return false;
        return this.getId().equals(((Currency) other).getId());
    }

}
src/main/java/io/github/flibio/economylite/commands/admin/EconCommand.java
@AsyncCommand
@Command(aliases = {"economy", "econ", "eco"}, permission = "economylite.admin.econ")
public class EconCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this);
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        src.sendMessage(messageStorage.getMessage("command.usage", "command", "/econ", "subcommands", "add | set | remove <player> <amount>"));
        src.sendMessage(messageStorage.getMessage("command.usage", "command", "/econ", "subcommands", "setall <amount>"));
    }

}
src/main/java/io/github/flibio/economylite/commands/admin/AddCommand.java
@AsyncCommand
@ParentCommand(parentCommand = EconCommand.class)
@Command(aliases = {"add"}, permission = "economylite.admin.econ.add")
public class AddCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.user(Text.of("player")), GenericArguments.doubleNum(Text.of("amount")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("player").isPresent() && args.getOne("amount").isPresent()) {
            User target = args.<User>getOne("player").get();
            String targetName = target.getName();
            BigDecimal toAdd = BigDecimal.valueOf(args.<Double>getOne("amount").get());
            Optional<UniqueAccount> uOpt = EconomyLite.getEconomyService().getOrCreateAccount(target.getUniqueId());
            if (uOpt.isPresent()) {
                UniqueAccount targetAccount = uOpt.get();
                if (targetAccount.deposit(EconomyLite.getCurrencyService().getCurrentCurrency(), toAdd,
                        Cause.of(EventContext.empty(),(EconomyLite.getInstance()))).getResult().equals(ResultType.SUCCESS)) {
                    src.sendMessage(messageStorage.getMessage("command.econ.addsuccess", "name", targetName));
                    attemptNotify(target);
                } else {
                    src.sendMessage(messageStorage.getMessage("command.econ.addfail", "name", targetName));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.error"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

    private void attemptNotify(User target) {
        if (target instanceof Player) {
            ((Player) target).sendMessage(messageStorage.getMessage("command.econ.notify"));
        }
    }

}
src/main/java/io/github/flibio/economylite/commands/admin/SetCommand.java
@AsyncCommand
@ParentCommand(parentCommand = EconCommand.class)
@Command(aliases = {"set"}, permission = "economylite.admin.econ.set")
public class SetCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.user(Text.of("player")), GenericArguments.doubleNum(Text.of("balance")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("player").isPresent() && args.getOne("balance").isPresent()) {
            User target = args.<User>getOne("player").get();
            String targetName = target.getName();
            BigDecimal newBal = BigDecimal.valueOf(args.<Double>getOne("balance").get());
            Optional<UniqueAccount> uOpt = EconomyLite.getEconomyService().getOrCreateAccount(target.getUniqueId());
            if (uOpt.isPresent()) {
                UniqueAccount targetAccount = uOpt.get();
                if (targetAccount.setBalance(EconomyLite.getCurrencyService().getCurrentCurrency(), newBal,
                        Cause.of(EventContext.empty(),(EconomyLite.getInstance()))).getResult().equals(ResultType.SUCCESS)) {
                    src.sendMessage(messageStorage.getMessage("command.econ.setsuccess", "name", targetName));
                    attemptNotify(target);
                } else {
                    src.sendMessage(messageStorage.getMessage("command.econ.setfail", "name", targetName));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.error"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

    private void attemptNotify(User target) {
        if (target instanceof Player) {
            ((Player) target).sendMessage(messageStorage.getMessage("command.econ.notify"));
        }
    }

}
src/main/java/io/github/flibio/economylite/commands/balance/BalanceCommand.java
@AsyncCommand
@Command(aliases = {"balance", "money", "bal"}, permission = "economylite.balance")
public class BalanceCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private CurrencyEconService currencyService = EconomyLite.getCurrencyService();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.optional(GenericArguments.user(Text.of("player"))));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        Currency currency = currencyService.getCurrentCurrency();
        if (args.getOne("player").isPresent()) {
            if (src.hasPermission("economylite.admin.balanceothers")) {
                User target = args.<User>getOne("player").get();
                String targetName = target.getName();
                Optional<UniqueAccount> uOpt = EconomyLite.getEconomyService().getOrCreateAccount(target.getUniqueId());
                if (uOpt.isPresent()) {
                    BigDecimal bal = uOpt.get().getBalance(currency);
                    Text label = currency.getPluralDisplayName();
                    if (bal.equals(BigDecimal.ONE)) {
                        label = currency.getDisplayName();
                    }
                    src.sendMessage(messageStorage
                            .getMessage("command.balanceother", "player", targetName, "balance", String.format(Locale.ENGLISH, "%,.2f", bal), "label",
                                    TextSerializers.FORMATTING_CODE.serialize(label)));
                } else {
                    src.sendMessage(messageStorage.getMessage("command.error"));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.noperm"));
            }
        } else {
            // Get the balance of the running player
            if (src instanceof Player) {
                Player player = (Player) src;
                Optional<UniqueAccount> uOpt = EconomyLite.getEconomyService().getOrCreateAccount(player.getUniqueId());
                if (uOpt.isPresent()) {
                    BigDecimal bal = uOpt.get().getBalance(currency);
                    Text label = currency.getPluralDisplayName();
                    if (bal.equals(BigDecimal.ONE)) {
                        label = currency.getDisplayName();
                    }
                    src.sendMessage(messageStorage.getMessage("command.balance", "balance", String.format(Locale.ENGLISH, "%,.2f", bal),
                            "label", TextSerializers.FORMATTING_CODE.serialize(label)));
                } else {
                    src.sendMessage(messageStorage.getMessage("command.error"));
                }
            } else {
                // If the there are no arguments the source must be a player
                src.sendMessage(messageStorage.getMessage("command.invalidsource", "sourcetype", "player"));
            }
        }
    }

}
src/main/java/io/github/flibio/economylite/modules/sql/SqlModule.java
public class SqlModule implements Module {

    private ConfigManager configManager = EconomyLite.getConfigManager();

    @Override
    public boolean initialize(Logger logger, Object plugin) {
        String hostname = getDetail("hostname");
        String port = getDetail("port");
        String database = getDetail("database");
        String username = getDetail("username");
        String password = getDetail("password");
        PlayerSqlService pService = new PlayerSqlService(hostname, port, database, username, password);
        VirtualSqlService vService = new VirtualSqlService(hostname, port, database, username, password);
        if (pService.isWorking() && vService.isWorking()) {
            EconomyLite.setPlayerService(pService);
            EconomyLite.setVirtualService(vService);
        } else {
            return false;
        }
        return true;
    }

    @Override
    public void initializeConfig() {
        configManager.setDefault(Boolean.class, false, "modules", "mysql", "enabled");
        configManager.setDefault(String.class, "hostname", "modules", "mysql", "hostname");
        configManager.setDefault(String.class, "3306", "modules", "mysql", "port");
        configManager.setDefault(String.class, "database", "modules", "mysql", "database");
        configManager.setDefault(String.class, "username", "modules", "mysql", "username");
        configManager.setDefault(String.class, "password", "modules", "mysql", "password");
    }

    @Override
    public String getName() {
        return "SQL";
    }

    @Override
    public boolean isEnabled() {
        return configManager.getValue(Boolean.class, false, "modules", "mysql", "enabled");
    }

    private String getDetail(String name) {
        return configManager.getValue("", String.class, "modules", "mysql", name);
    }

}
src/main/java/io/github/flibio/economylite/impl/CurrencyService.java
public class CurrencyService implements CurrencyEconService {

    private HashSet<Currency> currencies = new HashSet<Currency>();
    private Currency defaultCurrency;
    private Currency currentCurrency;

    public CurrencyService(Currency defaultCurrency) {
        this.defaultCurrency = defaultCurrency;
        this.currentCurrency = defaultCurrency;
    }

    @Override
    public void addCurrency(Currency currency) {
        currencies.add(currency);
    }

    @Override
    public Set<Currency> getCurrencies() {
        HashSet<Currency> set = currencies;
        set.add(defaultCurrency);
        return set;
    }

    @Override
    public Currency getDefaultCurrency() {
        return defaultCurrency;
    }

    @Override
    public void setCurrentCurrency(Currency currency) {
        this.currentCurrency = currency;
        EconomyLite.getCurrencyManager().forceValue(currency.getId().replaceAll("economylite:", ""), "current");
        EconomyLite.getCurrencyManager().save();
    }

    @Override
    public Currency getCurrentCurrency() {
        return currentCurrency;
    }

    @Override
    public void deleteCurrency(Currency currency) {
        if (currency != defaultCurrency) {
            EconomyLite.getPlayerService().clearCurrency(currency, CauseFactory.create("Currency deletion"));
            EconomyLite.getVirtualService().clearCurrency(currency, CauseFactory.create("Currency deletion"));
            currencies.remove(currency);
            EconomyLite.getCurrencyManager().forceValue(null, currency.getId().replaceAll("economylite:", ""));
            EconomyLite.getCurrencyManager().save();
        }
    }
}
src/main/java/io/github/flibio/economylite/commands/RefreshCommand.java
@AsyncCommand
@Command(aliases = {"ecorefresh"}, permission = "economylite.admin.refresh")
public class RefreshCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public CommandSpec.Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this);
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        PlayerEconService ps = EconomyLite.getPlayerService();
        if (!(ps instanceof PlayerServiceCommon)) {
            src.sendMessage(messageStorage.getMessage("command.refresh.fail"));
            return;
        }
        VirtualEconService vs = EconomyLite.getVirtualService();
        if (!(vs instanceof VirtualServiceCommon)) {
            src.sendMessage(messageStorage.getMessage("command.refresh.fail"));
            return;
        }

        PlayerServiceCommon playerService = (PlayerServiceCommon) ps;
        VirtualServiceCommon virtualService = (VirtualServiceCommon) vs;

        playerService.resetCache();
        virtualService.resetCache();

        src.sendMessage(messageStorage.getMessage("command.refresh.success"));
    }

}
src/main/java/io/github/flibio/economylite/api/VirtualEconService.java
public interface VirtualEconService {

    /**
     * Gets the balance of an account.
     *
     * @param id The name of the account to get the balance of.
     * @param currency The currency to use.
     * @param cause What is getting the balance.
     * @param cache Whether or not the cache will be used.
     * @return The balance of the account.
     */
    public BigDecimal getBalance(String id, Currency currency, Cause cause, boolean cache);

    /**
     * Gets the balance of an account. Caching will be used.
     *
     * @param id The name of the account to get the balance of.
     * @param currency The currency to use.
     * @param cause What is getting the balance.
     * @return The balance of the account.
     */
    default public BigDecimal getBalance(String id, Currency currency, Cause cause) {
        return getBalance(id, currency, cause, true);
    }

    /**
     * Sets the balance of an account.
     *
     * @param id The String of the player to set the balance of.
     * @param balance The new balance of the id.
     * @param currency The currency to use.
     * @param cause What caused the balance change.
     * @return If the player's balance was changed successfully.
     */
    public boolean setBalance(String id, BigDecimal balance, Currency currency, Cause cause);

    /**
     * Removes currency from an accounts's balance.
     *
     * @param id The name of the account to remove currency from.
     * @param amount The amount of currency to remove.
     * @param currency The currency to use.
     * @param cause What caused the balance change.
     * @return If the account's balance was changed successfully.
     */
    default public boolean withdraw(String id, BigDecimal amount, Currency currency, Cause cause) {
        return setBalance(id, getBalance(id, currency, cause, false).subtract(amount), currency, cause);
    }

    /**
     * Adds currency to an account's balance.
     *
     * @param id The name of the account to add currency to.
     * @param amount The amount of currency to add.
     * @param currency The currency to use.
     * @param cause What caused the balance change.
     * @return If the account's balance was changed successfully.
     */
    default public boolean deposit(String id, BigDecimal amount, Currency currency, Cause cause) {
        return setBalance(id, getBalance(id, currency, cause, false).add(amount), currency, cause);
    }

    /**
     * Checks if an account exists in the system for the specified currency.
     *
     * @param id The name of the account to check for.
     * @param currency The currency to check against.
     * @param cause What is checking if the account exists.
     * @return If the account exists or not.
     */
    public boolean accountExists(String id, Currency currency, Cause cause);

    /**
     * Clears a currency from the database.
     *
     * @param currency The currency to clear.
     * @param cause What is checking if the account exists.
     */
    public void clearCurrency(Currency currency, Cause cause);

    /**
     * Gets the top virtual accounts registered in the EconomyLite system.
     *
     * @param start The starting account to get.
     * @param end The ending account to get.
     * @param cause What is checking if the account exists.
     * @return The top virtual accounts registered in the EconomyLite system.
     */
    public List<VirtualAccount> getTopAccounts(int start, int end, Cause cause);

}
src/main/java/io/github/flibio/economylite/commands/MigrateCommand.java
@AsyncCommand
@Command(aliases = {"migrate"}, permission = "economylite.admin.migrate")
public class MigrateCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private PlayerEconService playerService = EconomyLite.getPlayerService();
    private VirtualEconService virtualService = EconomyLite.getVirtualService();
    private Logger logger = EconomyLite.getInstance().getLogger();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.string(Text.of("mode")), GenericArguments.optional(GenericArguments.bool(Text.of("confirm"))));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("mode").isPresent()) {
            // Check if migration is confirmed
            if (args.getOne("confirm").isPresent() && args.<Boolean>getOne("confirm").get()) {
                // Attempt to migrate
                String mode = args.<String>getOne("mode").get();
                if (mode.equalsIgnoreCase("totaleconomy")) {
                    try {
                        // Migrate from TotalEconomy
                        String configDir = EconomyLite.getInstance().getMainDir();
                        if (!configDir.substring(configDir.length() - 1).equalsIgnoreCase("/")) {
                            configDir += "/";
                        }
                        // Check if the file exists
                        File file = new File(configDir + "totaleconomy/accounts.conf");
                        if (!file.exists()) {
                            logger.error("Could not find TotalEconomy file!");
                            throw new FileNotFoundException();
                        }
                        // Attempt to load the file
                        ConfigurationLoader<?> manager = HoconConfigurationLoader.builder().setFile(file).build();
                        ConfigurationNode root = manager.load();
                        root.getChildrenMap().keySet().forEach(raw -> {
                            if (raw instanceof String) {
                                String uuid = (String) raw;
                                root.getNode(uuid).getChildrenMap().keySet().forEach(n -> {
                                    if (n.toString().contains("balance")) {
                                        ConfigurationNode sub = root.getNode(uuid).getNode(n.toString());
                                        if (!sub.isVirtual()) {
                                            playerService.setBalance(UUID.fromString(uuid), BigDecimal.valueOf(sub.getDouble()),
                                                    EconomyLite.getEconomyService().getDefaultCurrency(), CauseFactory.create("Migration"));
                                            logger.debug(uuid.toString() + ":migrate: " + sub.getDouble());
                                        }
                                    }
                                });
                            }
                        });
                        src.sendMessage(messageStorage.getMessage("command.migrate.completed"));
                    } catch (Exception e) {
                        logger.error(e.getMessage());
                        src.sendMessage(messageStorage.getMessage("command.migrate.fail"));
                    }
                } else if (mode.equalsIgnoreCase("tomysql")) {
                    try {
                        // Migrate from H2 to MySQL
                        if (EconomyLite.getPlayerService() instanceof PlayerDataService) {
                            // MySQL is not setup
                            src.sendMessage(messageStorage.getMessage("command.migrate.fail"));
                            return;
                        }
                        // Get current MySQL service
                        PlayerEconService s = EconomyLite.getPlayerService();
                        if (!(s instanceof PlayerServiceCommon)) {
                            src.sendMessage(messageStorage.getMessage("command.migrate.fail"));
                            return;
                        }
                        PlayerServiceCommon sqlService = (PlayerServiceCommon) s;
                        // Load the data service
                        PlayerDataService dataService = new PlayerDataService();
                        // Get Currency Data
                        Map<String, Currency> cIds = new HashMap<>();
                        EconomyLite.getCurrencyService().getCurrencies().forEach(c -> {
                            cIds.put(c.getId(), c);
                        });
                        // Insert new data
                        dataService.getAccountsMigration().forEach(r -> {
                            String[] d = r.split("%-%");
                            if (cIds.get(d[2]) != null) {
                                sqlService.setRawData(d[0], d[1], cIds.get(d[2]));
                            }
                        });
                        src.sendMessage(messageStorage.getMessage("command.migrate.completed"));
                    } catch (Exception e) {
                        logger.error(e.getMessage());
                        src.sendMessage(messageStorage.getMessage("command.migrate.fail"));
                    }
                } else {
                    // Invalid mode
                    src.sendMessage(messageStorage.getMessage("command.migrate.nomode"));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.migrate.confirm"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.migrate.nomode"));
        }
    }
}
src/main/java/io/github/flibio/economylite/api/PlayerEconService.java
public interface PlayerEconService {

    /**
     * Gets the balance of a player.
     *
     * @param uuid The UUID of the player to get the balance of.
     * @param currency The currency to use.
     * @param cause What is getting the balance.
     * @param cache Whether not to utilize the cache.
     * @return The balance of the player.
     */
    public BigDecimal getBalance(UUID uuid, Currency currency, Cause cause, boolean cache);

    /**
     * Gets the balance of a player. Caching is used.
     *
     * @param uuid The UUID of the player to get the balance of.
     * @param currency The currency to use.
     * @param cause What is getting the balance.
     * @return The balance of the player.
     */
    default public BigDecimal getBalance(UUID uuid, Currency currency, Cause cause) {
        return getBalance(uuid, currency, cause, true);
    }

    /**
     * Sets the balance of a player.
     *
     * @param uuid The UUID of the player to set the balance of.
     * @param balance The new balance of the uuid.
     * @param currency The currency to use.
     * @param cause What caused the balance change.
     * @return If the player's balance was changed successfully.
     */
    public boolean setBalance(UUID uuid, BigDecimal balance, Currency currency, Cause cause);

    /**
     * Removes currency from a player's balance.
     *
     * @param uuid The UUID of the player to remove currency from.
     * @param amount The amount of currency to remove.
     * @param currency The currency to use.
     * @param cause What caused the balance change.
     * @return If the player's balance was changed successfully.
     */
    default public boolean withdraw(UUID uuid, BigDecimal amount, Currency currency, Cause cause) {
        return setBalance(uuid, getBalance(uuid, currency, cause, false).subtract(amount), currency, cause);
    }

    /**
     * Adds currency to a player's balance.
     *
     * @param uuid The UUID of the player to add currency to.
     * @param amount The amount of currency to add.
     * @param currency The currency to use.
     * @param cause What caused the balance change.
     * @return If the player's balance was changed successfully.
     */
    default public boolean deposit(UUID uuid, BigDecimal amount, Currency currency, Cause cause) {
        return setBalance(uuid, getBalance(uuid, currency, cause, false).add(amount), currency, cause);
    }

    /**
     * Checks if a player exists in the system for the specified currency.
     *
     * @param uuid The UUID of the player to check for.
     * @param currency The currency to check against.
     * @param cause What is checking if the account exists.
     * @return If the player exists or not.
     */
    public boolean accountExists(UUID uuid, Currency currency, Cause cause);

    /**
     * Clears a currency from the database.
     *
     * @param currency The currency to clear.
     * @param cause What is clearing the balances.
     */
    public void clearCurrency(Currency currency, Cause cause);

    /**
     * Gets the top unique accounts registered in the EconomyLite system.
     *
     * @param start The starting account to get.
     * @param end The ending account to get.
     * @param cause What is getting the accounts.
     * @return The top unique accounts registered in the EconomyLite system.
     */
    public List<UniqueAccount> getTopAccounts(int start, int end, Cause cause);

    /**
     * Sets the balance of all players.
     *
     * @param balance The new balance.
     * @param currency The currency to use.
     * @param cause What caused the balance change.
     * @return If the balances were changed successfully.
     */
    public boolean setBalanceAll(BigDecimal balance, Currency currency, Cause cause);
}
src/main/java/io/github/flibio/economylite/modules/Module.java
public interface Module {

    /**
     * Initializes the module. The EconomyService is not available.
     * 
     * @param logger An instance of the plugin's logger.
     * @param plugin An instance of the main plugin class.
     * @return If the initialization was successful or not.
     */
    boolean initialize(Logger logger, Object plugin);

    /**
     * Post-initialization of the module. The EconomyService is now available.
     * 
     * @param logger An instance of the plugin's logger.
     * @param plugin An instance of the main plugin class.
     */
    default void postInitialization(Logger logger, Object plugin) {
    }

    /**
     * Sets all default config variables for the module.
     */
    void initializeConfig();

    /**
     * Gets the name of the module.
     * 
     * @return The name of the module.
     */
    String getName();

    /**
     * Checks if a module is enabled.
     * 
     * @return If the module is enabled or not.
     */
    boolean isEnabled();

}
src/main/java/io/github/flibio/economylite/commands/currency/CurrencyDeleteCommand.java
@AsyncCommand
@ParentCommand(parentCommand = CurrencyCommand.class)
@Command(aliases = {"delete"}, permission = "economylite.admin.currency.delete")
public class CurrencyDeleteCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private CurrencyEconService currencyService = EconomyLite.getCurrencyService();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.optional(GenericArguments.remainingJoinedStrings(Text.of("currency"))));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("currency").isPresent()) {
            String currencyName = args.<String>getOne("currency").get();
            boolean found = false;
            for (Currency c : currencyService.getCurrencies()) {
                if (c.getDisplayName().toPlain().equalsIgnoreCase(currencyName)) {
                    found = true;
                    if (currencyService.getDefaultCurrency().equals(c)) {
                        // You can not delete the default
                        src.sendMessage(messageStorage.getMessage("command.currency.deletedefault"));
                    } else {
                        if (currencyService.getCurrentCurrency().equals(c)) {
                            currencyService.setCurrentCurrency(currencyService.getDefaultCurrency());
                        }
                        currencyService.deleteCurrency(c);
                        src.sendMessage(messageStorage.getMessage("command.currency.deleted", "currency", c.getDisplayName().toPlain()));
                    }
                }
            }
            if (!found) {
                src.sendMessage(messageStorage.getMessage("command.currency.invalid"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.currency.delete"));
            currencyService.getCurrencies().forEach(currency -> {
                if (!currency.equals(currencyService.getDefaultCurrency())) {
                    src.sendMessage(Text.of(currency.getDisplayName()).toBuilder().onClick(TextActions.executeCallback(c -> {
                        src.sendMessage(messageStorage.getMessage("command.currency.deleteconfirm", "currency", currency.getDisplayName().toPlain())
                                .toBuilder().onClick(
                                        TextActions.executeCallback(c2 -> {
                                            if (currencyService.getCurrentCurrency().equals(currency)) {
                                                currencyService.setCurrentCurrency(currencyService.getDefaultCurrency());
                                            }
                                            currencyService.deleteCurrency(currency);
                                            src.sendMessage(messageStorage.getMessage("command.currency.deleted", "currency",
                                                    currency.getDisplayName().toPlain()));
                                        })).build());
                    })).build());
                }
            });
        }
    }
}
src/main/java/io/github/flibio/economylite/commands/currency/CurrencySetCommand.java
@AsyncCommand
@ParentCommand(parentCommand = CurrencyCommand.class)
@Command(aliases = {"set"}, permission = "economylite.admin.currency.set")
public class CurrencySetCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private CurrencyEconService currencyService = EconomyLite.getCurrencyService();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.optional(GenericArguments.remainingJoinedStrings(Text.of("currency"))));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("currency").isPresent()) {
            String currencyName = args.<String>getOne("currency").get();
            boolean found = false;
            for (Currency c : currencyService.getCurrencies()) {
                if (c.getDisplayName().toPlain().equalsIgnoreCase(currencyName)) {
                    currencyService.setCurrentCurrency(c);
                    found = true;
                    src.sendMessage(messageStorage.getMessage("command.currency.changed", "currency", c.getDisplayName().toPlain()));
                }
            }
            if (!found) {
                src.sendMessage(messageStorage.getMessage("command.currency.invalid"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.currency.current", "currency", currencyService.getCurrentCurrency().getDisplayName()
                    .toPlain()));
            src.sendMessage(messageStorage.getMessage("command.currency.selectnew"));
            currencyService.getCurrencies().forEach(currency -> {
                src.sendMessage(Text.of(currency.getDisplayName()).toBuilder().onClick(TextActions.executeCallback(c -> {
                    src.sendMessage(messageStorage.getMessage("command.currency.confirm", "currency", currency.getDisplayName().toPlain()).toBuilder()
                            .onClick(TextActions.executeCallback(c2 -> {
                                currencyService.setCurrentCurrency(currency);
                                src.sendMessage(
                                        messageStorage.getMessage("command.currency.changed", "currency", currency.getDisplayName().toPlain()));
                            })).build());
                })).build());
            });
        }
    }
}
src/main/java/io/github/flibio/economylite/commands/virtual/VirtualPayCommand.java
@AsyncCommand
@Command(aliases = {"vpay"}, permission = "economylite.admin.virtual.pay")
public class VirtualPayCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private EconomyService ecoService = EconomyLite.getEconomyService();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec
                .builder()
                .executor(this)
                .arguments(GenericArguments.string(Text.of("account")), GenericArguments.user(Text.of("target")),
                        GenericArguments.doubleNum(Text.of("amount")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("account").isPresent() && args.getOne("target").isPresent() && args.getOne("amount").isPresent()) {
            String account = args.<String>getOne("account").get();
            User target = args.<User>getOne("target").get();
            BigDecimal amount = BigDecimal.valueOf(args.<Double>getOne("amount").get());
            Optional<Account> aOpt = EconomyLite.getEconomyService().getOrCreateAccount(account);
            Optional<UniqueAccount> uOpt = EconomyLite.getEconomyService().getOrCreateAccount(target.getUniqueId());
            // Check for negative payments
            if (amount.compareTo(BigDecimal.ONE) == -1) {
                src.sendMessage(messageStorage.getMessage("command.pay.invalid"));
            } else {
                if (aOpt.isPresent() && uOpt.isPresent()) {
                    Account payer = aOpt.get();
                    UniqueAccount receiver = uOpt.get();
                    if (payer.transfer(receiver, ecoService.getDefaultCurrency(), amount, Cause.of(EventContext.empty(), EconomyLite.getInstance
                            ()))
                            .getResult().equals(ResultType.SUCCESS)) {
                        src.sendMessage(messageStorage.getMessage("command.pay.success", "target", target.getName()));
                        if (target instanceof Player) {
                            Text label = ecoService.getDefaultCurrency().getPluralDisplayName();
                            if (amount.equals(BigDecimal.ONE)) {
                                label = ecoService.getDefaultCurrency().getDisplayName();
                            }
                            ((Player) target).sendMessage(messageStorage.getMessage("command.pay.target", "amountandlabel",
                                    String.format(Locale.ENGLISH, "%,.2f", amount) + " " + label.toPlain(), "sender",
                                    payer.getDisplayName().toPlain()));
                        }
                    } else {
                        src.sendMessage(messageStorage.getMessage("command.pay.failed", "target", target.getName()));
                    }
                }
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

}
src/main/java/io/github/flibio/economylite/modules/loan/LoanModule.java
public class LoanModule implements Module {

    private MessageStorage messages = EconomyLite.getMessageStorage();
    private ConfigManager configManager = EconomyLite.getConfigManager();
    private LoanManager loanManager;
    private double interestRate = 1.0;
    private double maxLoan = 1000.0;

    public HashMap<UUID, Double> tableLoans = new HashMap<>();

    @Override
    public boolean initialize(Logger logger, Object plugin) {
        Optional<Double> iOpt = configManager.getValue(Double.class, "modules", "loan", "interest-rate");
        Optional<Double> mOpt = configManager.getValue(Double.class, "modules", "loan", "max-loan-balance");
        if (iOpt.isPresent() && mOpt.isPresent()) {
            interestRate = iOpt.get();
            maxLoan = mOpt.get();
            if (interestRate < 1) {
                logger.error("The interest rate must be greater than or equal to 1!");
                return false;
            }
            if (maxLoan <= 0) {
                logger.error("The interest rate must be greater than 0!");
                return false;
            }
            logger.info("Interest rate set to " + interestRate + "!");
            logger.info("Maximum loan balance set to " + maxLoan + "!");
        } else {
            return false;
        }
        return true;
    }

    @Override
    public void postInitialization(Logger logger, Object plugin) {
        // Create loan manager
        loanManager = new LoanManager(this);
        // Register listeners
        Sponge.getEventManager().registerListeners(plugin, new LoanListener(this));
        // Register commands
        CommandLoader.registerCommands(plugin, TextSerializers.FORMATTING_CODE.serialize(messages.getMessage("command.invalidsource")),
                new LoanCommand(),
                new LoanBalanceCommand(this),
                new LoanPayCommand(this),
                new LoanTakeCommand(this),
                new LoanAcceptCommand(this),
                new LoanDenyCommand(this)
        );
    }

    @Override
    public void initializeConfig() {
        configManager.setDefault(Boolean.class, false, "modules", "loan", "enabled");
        configManager.setDefault(Double.class, 1.0, "modules", "loan", "interest-rate");
        configManager.setDefault(Double.class, 1000.0, "modules", "loan", "max-loan-balance");

        CommentedConfigurationNode con = configManager.getNode();
        Map<String, Boolean> defaultPerms = ImmutableMap.of("reward.permission", true);
        if (con.getNode("modules").getNode("loan").getNode("debtor-perms").isVirtual()) {
            con.getNode("modules").getNode("loan").getNode("debtor-perms").setValue(defaultPerms);
            configManager.overwriteNode(con);
        }
    }

    @Override
    public String getName() {
        return "Loan";
    }

    @Override
    public boolean isEnabled() {
        return configManager.getValue(Boolean.class, false, "modules", "loan", "enabled");
    }

    public LoanManager getLoanManager() {
        return loanManager;
    }

    public double getMaxLoan() {
        return maxLoan;
    }

    public double getInterestRate() {
        return interestRate;
    }

    public Map<String, Boolean> getPermissions() {
        Map<String, Boolean> perms = new HashMap<>();
        CommentedConfigurationNode con = configManager.getNode();

        Map<Object, ? extends ConfigurationNode> map = con.getNode("modules").getNode("loan").getNode("debtor-perms").getChildrenMap();

        map.keySet().forEach(perm -> {
            perms.put(perm.toString(), map.get(perm).getBoolean());
        });
        return perms;
    }
}
src/main/java/io/github/flibio/economylite/commands/balance/BalTopCommand.java
@AsyncCommand
@Command(aliases = {"baltop"}, permission = "economylite.baltop")
public class BalTopCommand extends BaseCommandExecutor<CommandSource> {

    MessageStorage messages = EconomyLite.getMessageStorage();

    private int count;

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.optional(GenericArguments.integer(Text.of("page"))));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        Currency current = EconomyLite.getCurrencyService().getCurrentCurrency();
        TreeMap<String, BigDecimal> bals = new TreeMap<>();
        Optional<Integer> pOpt = args.<Integer>getOne("page");
        int pageNumber = 1;
        if (pOpt.isPresent()) {
            pageNumber = pOpt.get();
        }
        if (pageNumber < 1) {
            src.sendMessage(messages.getMessage("command.baltop.invalidpage"));
            return;
        }
        int start, end;
        if (pageNumber == 1) {
            start = 1;
            end = 5;
        } else {
            start = ((pageNumber - 1) * 5) + 1;
            end = pageNumber * 10;
        }
        for (UniqueAccount account : EconomyLite.getPlayerService().getTopAccounts(start, end + 1, CauseFactory.create("Baltop command."))) {
            bals.put(account.getDisplayName().toPlain(), account.getBalance(current));
        }
        boolean nextPage = true;
        if (bals.size() < 1) {
            src.sendMessage(messages.getMessage("command.baltop.nodata"));
            return;
        }
        if (bals.size() < 6) {
            nextPage = false;
        }
        src.sendMessage(messages.getMessage("command.baltop.head", "page", String.valueOf(pageNumber)));
        count = start;
        bals.entrySet().stream().sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed()).limit(5).forEachOrdered(e -> {
            Text label = current.getPluralDisplayName();
            if (e.getValue().equals(BigDecimal.ONE)) {
                label = current.getDisplayName();
            }
            src.sendMessage(messages.getMessage(
                    "command.baltop.data", "position", Integer.toString(count), "name", e.getKey(), "balance",
                    String.format(Locale.ENGLISH, "%,.2f", e.getValue()), "label", label.toPlain()));
            count++;
        });
        Text toSend = Text.of();
        if (pageNumber > 1) {
            toSend = toSend.toBuilder().append(
                    messages.getMessage("command.baltop.navigation", "button", "<-").toBuilder()
                            .onClick(TextActions.runCommand("/baltop " + (pageNumber - 1)))
                            .onHover(TextActions.showText(Text.of(TextColors.GREEN, "BACK"))).build()).build();
            toSend = toSend.toBuilder().append(Text.of(" ")).build();
        }
        toSend = toSend.toBuilder().append(messages.getMessage("command.baltop.navigation", "button", String.valueOf(pageNumber))).build();
        if (nextPage) {
            toSend = toSend.toBuilder().append(Text.of(" ")).build();
            toSend = toSend.toBuilder().append(
                    messages.getMessage("command.baltop.navigation", "button", "->").toBuilder()
                            .onClick(TextActions.runCommand("/baltop " + (pageNumber + 1)))
                            .onHover(TextActions.showText(Text.of(TextColors.GREEN, "NEXT"))).build()).build();
        }
        if (!toSend.toPlain().isEmpty()) {
            src.sendMessage(toSend);
        }
        count = 0;
    }
}
src/main/java/io/github/flibio/economylite/commands/admin/SetAllCommand.java
@AsyncCommand
@ParentCommand(parentCommand = EconCommand.class)
@Command(aliases = {"setall"}, permission = "economylite.admin.econ.setall")
public class SetAllCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.doubleNum(Text.of("balance")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("balance").isPresent()) {
            String targetName = "all players";
            BigDecimal newBal = BigDecimal.valueOf(args.<Double>getOne("balance").get());
            if (EconomyLite.getPlayerService().setBalanceAll(newBal, EconomyLite.getCurrencyService().getCurrentCurrency(),
                    Cause.of(EventContext.empty(),(EconomyLite.getInstance())))) {
                src.sendMessage(messageStorage.getMessage("command.econ.setsuccess", "name", targetName));
            } else {
                src.sendMessage(messageStorage.getMessage("command.econ.setfail", "name", targetName));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

}
src/main/java/io/github/flibio/economylite/commands/virtual/VirtualEconCommand.java
@AsyncCommand
@Command(aliases = {"veconomy", "vecon", "veco"}, permission = "economylite.admin.virtual")
public class VirtualEconCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this);
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        src.sendMessage(messageStorage.getMessage("command.usage", "command", "/vecon", "subcommands", "add | set | remove <account> <amount>"));
    }

}
src/main/java/io/github/flibio/economylite/commands/virtual/VirtualRemoveCommand.java
@AsyncCommand
@ParentCommand(parentCommand = VirtualEconCommand.class)
@Command(aliases = {"remove"}, permission = "economylite.admin.virtual.remove")
public class VirtualRemoveCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.string(Text.of("account")), GenericArguments.doubleNum(Text.of("amount")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("account").isPresent() && args.getOne("amount").isPresent()) {
            String target = args.<String>getOne("account").get();
            BigDecimal toRemove = BigDecimal.valueOf(args.<Double>getOne("amount").get());
            Optional<Account> aOpt = EconomyLite.getEconomyService().getOrCreateAccount(target);
            if (aOpt.isPresent()) {
                Account targetAccount = aOpt.get();
                if (targetAccount.withdraw(EconomyLite.getCurrencyService().getCurrentCurrency(), toRemove,
                        Cause.of(EventContext.empty(),(EconomyLite.getInstance()))).getResult().equals(ResultType.SUCCESS)) {
                    src.sendMessage(messageStorage.getMessage("command.econ.removesuccess", "name", target));
                } else {
                    src.sendMessage(messageStorage.getMessage("command.econ.removefail", "name", target));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.error"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

}
src/main/java/io/github/flibio/economylite/commands/currency/CurrencyCommand.java
@AsyncCommand
@Command(aliases = {"currency", "cur"}, permission = "economylite.admin.currency")
public class CurrencyCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this);
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        src.sendMessage(messageStorage.getMessage("command.currency.current", "currency", EconomyLite.getEconomyService().getDefaultCurrency()
                .getDisplayName().toPlain()));
        src.sendMessage(messageStorage.getMessage("command.usage", "command", "/currency", "subcommands", "set | create | delete"));
    }
}
src/main/java/io/github/flibio/economylite/bstats/Metrics.java
public class Metrics {

    // The version of this bStats class
    public static final int B_STATS_VERSION = 1;

    // The url to which the data is sent
    private static final String URL = "https://bStats.org/submitData/sponge";

    // We use this flag to ensure only one instance of this class exist
    private static boolean created = false;

    // The logger
    private Logger logger;

    // The plugin
    private final PluginContainer plugin;

    // Is bStats enabled on this server?
    private boolean enabled;

    // The uuid of the server
    private String serverUUID;

    // Should failed requests be logged?
    private boolean logFailedRequests = false;

    // A list with all known metrics class objects including this one
    private static final List<Object> knownMetricsInstances = new ArrayList<>();

    // A list with all custom charts
    private final List<CustomChart> charts = new ArrayList<>();

    // The config path
    private Path configDir;

    // The constructor is not meant to be called by the user himself.
    // The instance is created using Dependency Injection
    // (https://docs.spongepowered.org/master/en/plugin/injection.html)
    @Inject
    private Metrics(PluginContainer plugin, Logger logger, @ConfigDir(sharedRoot = true) Path configDir) {
        if (created) {
            // We don't want more than one instance of this class
            throw new IllegalStateException("There's already an instance of this Metrics class!");
        } else {
            created = true;
        }

        this.plugin = plugin;
        this.logger = logger;
        this.configDir = configDir;

        try {
            loadConfig();
        } catch (IOException e) {
            // Failed to load configuration
            logger.warn("Failed to load bStats config!", e);
            return;
        }

        // We are not allowed to send data about this server :(
        if (!enabled) {
            return;
        }

        Class<?> usedMetricsClass = getFirstBStatsClass();
        if (usedMetricsClass == null) {
            // Failed to get first metrics class
            return;
        }
        if (usedMetricsClass == getClass()) {
            // We are the first! :)
            linkMetrics(this);
            startSubmitting();
        } else {
            // We aren't the first so we link to the first metrics class
            try {
                usedMetricsClass.getMethod("linkMetrics", Object.class).invoke(null, this);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                if (logFailedRequests) {
                    logger.warn("Failed to link to first metrics class {}!", usedMetricsClass.getName(), e);
                }
            }
        }
    }

    /**
     * Adds a custom chart.
     *
     * @param chart The chart to add.
     */
    public void addCustomChart(CustomChart chart) {
        Validate.notNull(chart, "Chart cannot be null");
        charts.add(chart);
    }

    /**
     * Links an other metrics class with this class. This method is called using
     * Reflection.
     *
     * @param metrics An object of the metrics class to link.
     */
    public static void linkMetrics(Object metrics) {
        knownMetricsInstances.add(metrics);
    }

    /**
     * Gets the plugin specific data. This method is called using Reflection.
     *
     * @return The plugin specific data.
     */
    public JsonObject getPluginData() {
        JsonObject data = new JsonObject();

        String pluginName = plugin.getName();
        String pluginVersion = plugin.getVersion().orElse("unknown");

        data.addProperty("pluginName", pluginName);
        data.addProperty("pluginVersion", pluginVersion);

        JsonArray customCharts = new JsonArray();
        for (CustomChart customChart : charts) {
            // Add the data of the custom charts
            JsonObject chart = customChart.getRequestJsonObject(logger, logFailedRequests);
            if (chart == null) { // If the chart is null, we skip it
                continue;
            }
            customCharts.add(chart);
        }
        data.add("customCharts", customCharts);

        return data;
    }

    private void startSubmitting() {
        // We use a timer cause want to be independent from the server tps
        final Timer timer = new Timer(true);
        timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                // Plugin was disabled, e.g. because of a reload (is this even
                // possible in Sponge?)
                if (!Sponge.getPluginManager().isLoaded(plugin.getId())) {
                    timer.cancel();
                    return;
                }
                // The data collection (e.g. for custom graphs) is done sync
                // Don't be afraid! The connection to the bStats server is still
                // async, only the stats collection is sync ;)
                Scheduler scheduler = Sponge.getScheduler();
                Task.Builder taskBuilder = scheduler.createTaskBuilder();
                taskBuilder.execute(() -> submitData()).submit(plugin);
            }
        }, 1000 * 60 * 5, 1000 * 60 * 30);
        // Submit the data every 30 minutes, first time after 5 minutes to give
        // other plugins enough time to start
        // WARNING: Changing the frequency has no effect but your plugin WILL be
        // blocked/deleted!
        // WARNING: Just don't do it!
    }

    /**
     * Gets the server specific data.
     *
     * @return The server specific data.
     */
    private JsonObject getServerData() {
        // Minecraft specific data
        int playerAmount = Sponge.getServer().getOnlinePlayers().size();
        playerAmount = playerAmount > 200 ? 200 : playerAmount;
        int onlineMode = Sponge.getServer().getOnlineMode() ? 1 : 0;
        String minecraftVersion = Sponge.getGame().getPlatform().getMinecraftVersion().getName();

        // OS/Java specific data
        String javaVersion = System.getProperty("java.version");
        String osName = System.getProperty("os.name");
        String osArch = System.getProperty("os.arch");
        String osVersion = System.getProperty("os.version");
        int coreCount = Runtime.getRuntime().availableProcessors();

        JsonObject data = new JsonObject();

        data.addProperty("serverUUID", serverUUID);

        data.addProperty("playerAmount", playerAmount);
        data.addProperty("onlineMode", onlineMode);
        data.addProperty("minecraftVersion", minecraftVersion);

        data.addProperty("javaVersion", javaVersion);
        data.addProperty("osName", osName);
        data.addProperty("osArch", osArch);
        data.addProperty("osVersion", osVersion);
        data.addProperty("coreCount", coreCount);

        return data;
    }

    /**
     * Collects the data and sends it afterwards.
     */
    private void submitData() {
        final JsonObject data = getServerData();

        JsonArray pluginData = new JsonArray();
        // Search for all other bStats Metrics classes to get their plugin data
        for (Object metrics : knownMetricsInstances) {
            try {
                Object plugin = metrics.getClass().getMethod("getPluginData").invoke(metrics);
                if (plugin instanceof JsonObject) {
                    pluginData.add((JsonObject) plugin);
                }
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
            }
        }

        data.add("plugins", pluginData);

        // Create a new thread for the connection to the bStats server
        new Thread(() -> {
            try {
                // Send the data
                sendData(data);
            } catch (Exception e) {
                // Something went wrong! :(
                if (logFailedRequests) {
                    logger.warn("Could not submit plugin stats!", e);
                }
            }
        }).start();
    }

    /**
     * Loads the bStats configuration.
     *
     * @throws IOException If something did not work :(
     */
    private void loadConfig() throws IOException {
        Path configPath = configDir.resolve("bStats");
        configPath.toFile().mkdirs();
        File configFile = new File(configPath.toFile(), "config.conf");
        HoconConfigurationLoader configurationLoader = HoconConfigurationLoader.builder().setFile(configFile).build();
        CommentedConfigurationNode node;
        if (!configFile.exists()) {
            configFile.createNewFile();
            node = configurationLoader.load();

            // Add default values
            node.getNode("enabled").setValue(true);
            // Every server gets it's unique random id.
            node.getNode("serverUuid").setValue(UUID.randomUUID().toString());
            // Should failed request be logged?
            node.getNode("logFailedRequests").setValue(false);

            // Add information about bStats
            node.getNode("enabled").setComment(
                    "bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
                            "To honor their work, you should not disable it.\n" +
                            "This has nearly no effect on the server performance!\n" +
                            "Check out https://bStats.org/ to learn more :)"
                    );

            configurationLoader.save(node);
        } else {
            node = configurationLoader.load();
        }

        // Load configuration
        enabled = node.getNode("enabled").getBoolean(true);
        serverUUID = node.getNode("serverUuid").getString();
        logFailedRequests = node.getNode("logFailedRequests").getBoolean(false);
    }

    /**
     * Gets the first bStat Metrics class.
     *
     * @return The first bStats metrics class.
     */
    private Class<?> getFirstBStatsClass() {
        Path configPath = configDir.resolve("bStats");
        configPath.toFile().mkdirs();
        File tempFile = new File(configPath.toFile(), "temp.txt");

        try {
            String className = readFile(tempFile);
            if (className != null) {
                try {
                    // Let's check if a class with the given name exists.
                    return Class.forName(className);
                } catch (ClassNotFoundException ignored) {
                }
            }
            writeFile(tempFile, getClass().getName());
            return getClass();
        } catch (IOException e) {
            if (logFailedRequests) {
                logger.warn("Failed to get first bStats class!", e);
            }
            return null;
        }
    }

    /**
     * Reads the first line of the file.
     *
     * @param file The file to read. Cannot be null.
     * @return The first line of the file or <code>null</code> if the file does
     *         not exist or is empty.
     * @throws IOException If something did not work :(
     */
    private String readFile(File file) throws IOException {
        if (!file.exists()) {
            return null;
        }
        try (
                FileReader fileReader = new FileReader(file);
                BufferedReader bufferedReader = new BufferedReader(fileReader);) {
            return bufferedReader.readLine();
        }
    }

    /**
     * Writes a String to a file. It also adds a note for the user,
     *
     * @param file The file to write to. Cannot be null.
     * @param text The text to write.
     * @throws IOException If something did not work :(
     */
    private void writeFile(File file, String text) throws IOException {
        if (!file.exists()) {
            file.createNewFile();
        }
        try (
                FileWriter fileWriter = new FileWriter(file);
                BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
            bufferedWriter.write(text);
            bufferedWriter.newLine();
            bufferedWriter.write("Note: This class only exists for internal purpose. You can ignore it :)");
        }
    }

    /**
     * Sends the data to the bStats server.
     *
     * @param data The data to send.
     * @throws Exception If the request failed.
     */
    private static void sendData(JsonObject data) throws Exception {
        Validate.notNull(data, "Data cannot be null");
        HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();

        // Compress the data to save bandwidth
        byte[] compressedData = compress(data.toString());

        // Add headers
        connection.setRequestMethod("POST");
        connection.addRequestProperty("Accept", "application/json");
        connection.addRequestProperty("Connection", "close");
        connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip
                                                                   // our
                                                                   // request
        connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
        connection.setRequestProperty("Content-Type", "application/json"); // We
                                                                           // send
                                                                           // our
                                                                           // data
                                                                           // in
                                                                           // JSON
                                                                           // format
        connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);

        // Send data
        connection.setDoOutput(true);
        DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
        outputStream.write(compressedData);
        outputStream.flush();
        outputStream.close();

        connection.getInputStream().close(); // We don't care about the response
                                             // - Just send our data :)
    }

    /**
     * Gzips the given String.
     *
     * @param str The string to gzip.
     * @return The gzipped String.
     * @throws IOException If the compression failed.
     */
    private static byte[] compress(final String str) throws IOException {
        if (str == null) {
            return null;
        }
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
        gzip.write(str.getBytes("UTF-8"));
        gzip.close();
        return outputStream.toByteArray();
    }

    /**
     * Represents a custom chart.
     */
    public static abstract class CustomChart {

        // The id of the chart
        protected final String chartId;

        /**
         * Class constructor.
         *
         * @param chartId The id of the chart.
         */
        public CustomChart(String chartId) {
            if (chartId == null || chartId.isEmpty()) {
                throw new IllegalArgumentException("ChartId cannot be null or empty!");
            }
            this.chartId = chartId;
        }

        protected JsonObject getRequestJsonObject(Logger logger, boolean logFailedRequests) {
            JsonObject chart = new JsonObject();
            chart.addProperty("chartId", chartId);
            try {
                JsonObject data = getChartData();
                if (data == null) {
                    // If the data is null we don't send the chart.
                    return null;
                }
                chart.add("data", data);
            } catch (Throwable t) {
                if (logFailedRequests) {
                    logger.warn("Failed to get data for custom chart with id {}", chartId, t);
                }
                return null;
            }
            return chart;
        }

        protected abstract JsonObject getChartData();

    }

    /**
     * Represents a custom simple pie.
     */
    public static abstract class SimplePie extends CustomChart {

        /**
         * Class constructor.
         *
         * @param chartId The id of the chart.
         */
        public SimplePie(String chartId) {
            super(chartId);
        }

        /**
         * Gets the value of the pie.
         *
         * @return The value of the pie.
         */
        public abstract String getValue();

        @Override
        protected JsonObject getChartData() {
            JsonObject data = new JsonObject();
            String value = getValue();
            if (value == null || value.isEmpty()) {
                // Null = skip the chart
                return null;
            }
            data.addProperty("value", value);
            return data;
        }
    }

    /**
     * Represents a custom advanced pie.
     */
    public static abstract class AdvancedPie extends CustomChart {

        /**
         * Class constructor.
         *
         * @param chartId The id of the chart.
         */
        public AdvancedPie(String chartId) {
            super(chartId);
        }

        /**
         * Gets the values of the pie.
         *
         * @param valueMap Just an empty map. The only reason it exists is to
         *        make your life easier. You don't have to create a map
         *        yourself!
         * @return The values of the pie.
         */
        public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap);

        @Override
        protected JsonObject getChartData() {
            JsonObject data = new JsonObject();
            JsonObject values = new JsonObject();
            HashMap<String, Integer> map = getValues(new HashMap<String, Integer>());
            if (map == null || map.isEmpty()) {
                // Null = skip the chart
                return null;
            }
            boolean allSkipped = true;
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue() == 0) {
                    continue; // Skip this invalid
                }
                allSkipped = false;
                values.addProperty(entry.getKey(), entry.getValue());
            }
            if (allSkipped) {
                // Null = skip the chart
                return null;
            }
            data.add("values", values);
            return data;
        }
    }

    /**
     * Represents a custom single line chart.
     */
    public static abstract class SingleLineChart extends CustomChart {

        /**
         * Class constructor.
         *
         * @param chartId The id of the chart.
         */
        public SingleLineChart(String chartId) {
            super(chartId);
        }

        /**
         * Gets the value of the chart.
         *
         * @return The value of the chart.
         */
        public abstract int getValue();

        @Override
        protected JsonObject getChartData() {
            JsonObject data = new JsonObject();
            int value = getValue();
            if (value == 0) {
                // Null = skip the chart
                return null;
            }
            data.addProperty("value", value);
            return data;
        }

    }

    /**
     * Represents a custom multi line chart.
     */
    public static abstract class MultiLineChart extends CustomChart {

        /**
         * Class constructor.
         *
         * @param chartId The id of the chart.
         */
        public MultiLineChart(String chartId) {
            super(chartId);
        }

        /**
         * Gets the values of the chart.
         *
         * @param valueMap Just an empty map. The only reason it exists is to
         *        make your life easier. You don't have to create a map
         *        yourself!
         * @return The values of the chart.
         */
        public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap);

        @Override
        protected JsonObject getChartData() {
            JsonObject data = new JsonObject();
            JsonObject values = new JsonObject();
            HashMap<String, Integer> map = getValues(new HashMap<String, Integer>());
            if (map == null || map.isEmpty()) {
                // Null = skip the chart
                return null;
            }
            boolean allSkipped = true;
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue() == 0) {
                    continue; // Skip this invalid
                }
                allSkipped = false;
                values.addProperty(entry.getKey(), entry.getValue());
            }
            if (allSkipped) {
                // Null = skip the chart
                return null;
            }
            data.add("values", values);
            return data;
        }

    }

    /**
     * Represents a custom simple map chart.
     */
    public static abstract class SimpleMapChart extends CustomChart {

        /**
         * Class constructor.
         *
         * @param chartId The id of the chart.
         */
        public SimpleMapChart(String chartId) {
            super(chartId);
        }

        /**
         * Gets the value of the chart.
         *
         * @return The value of the chart.
         */
        public abstract Country getValue();

        @Override
        protected JsonObject getChartData() {
            JsonObject data = new JsonObject();
            Country value = getValue();

            if (value == null) {
                // Null = skip the chart
                return null;
            }
            data.addProperty("value", value.getCountryIsoTag());
            return data;
        }

    }

    /**
     * Represents a custom advanced map chart.
     */
    public static abstract class AdvancedMapChart extends CustomChart {

        /**
         * Class constructor.
         *
         * @param chartId The id of the chart.
         */
        public AdvancedMapChart(String chartId) {
            super(chartId);
        }

        /**
         * Gets the value of the chart.
         *
         * @param valueMap Just an empty map. The only reason it exists is to
         *        make your life easier. You don't have to create a map
         *        yourself!
         * @return The value of the chart.
         */
        public abstract HashMap<Country, Integer> getValues(HashMap<Country, Integer> valueMap);

        @Override
        protected JsonObject getChartData() {
            JsonObject data = new JsonObject();
            JsonObject values = new JsonObject();
            HashMap<Country, Integer> map = getValues(new HashMap<Country, Integer>());
            if (map == null || map.isEmpty()) {
                // Null = skip the chart
                return null;
            }
            boolean allSkipped = true;
            for (Map.Entry<Country, Integer> entry : map.entrySet()) {
                if (entry.getValue() == 0) {
                    continue; // Skip this invalid
                }
                allSkipped = false;
                values.addProperty(entry.getKey().getCountryIsoTag(), entry.getValue());
            }
            if (allSkipped) {
                // Null = skip the chart
                return null;
            }
            data.add("values", values);
            return data;
        }

    }

    /**
     * A enum which is used for custom maps.
     */
    public enum Country {

        /**
         * bStats will use the country of the server.
         */
        AUTO_DETECT("AUTO", "Auto Detected"),

        ANDORRA("AD", "Andorra"),
        UNITED_ARAB_EMIRATES("AE", "United Arab Emirates"),
        AFGHANISTAN("AF", "Afghanistan"),
        ANTIGUA_AND_BARBUDA("AG", "Antigua and Barbuda"),
        ANGUILLA("AI", "Anguilla"),
        ALBANIA("AL", "Albania"),
        ARMENIA("AM", "Armenia"),
        NETHERLANDS_ANTILLES("AN", "Netherlands Antilles"),
        ANGOLA("AO", "Angola"),
        ANTARCTICA("AQ", "Antarctica"),
        ARGENTINA("AR", "Argentina"),
        AMERICAN_SAMOA("AS", "American Samoa"),
        AUSTRIA("AT", "Austria"),
        AUSTRALIA("AU", "Australia"),
        ARUBA("AW", "Aruba"),
        ALAND_ISLANDS("AX", "Aland Islands"),
        AZERBAIJAN("AZ", "Azerbaijan"),
        BOSNIA_AND_HERZEGOVINA("BA", "Bosnia and Herzegovina"),
        BARBADOS("BB", "Barbados"),
        BANGLADESH("BD", "Bangladesh"),
        BELGIUM("BE", "Belgium"),
        BURKINA_FASO("BF", "Burkina Faso"),
        BULGARIA("BG", "Bulgaria"),
        BAHRAIN("BH", "Bahrain"),
        BURUNDI("BI", "Burundi"),
        BENIN("BJ", "Benin"),
        SAINT_BARTHELEMY("BL", "Saint Barthelemy"),
        BERMUDA("BM", "Bermuda"),
        BRUNEI("BN", "Brunei"),
        BOLIVIA("BO", "Bolivia"),
        BONAIRE_SINT_EUSTATIUS_AND_SABA("BQ", "Bonaire, Sint Eustatius and Saba"),
        BRAZIL("BR", "Brazil"),
        BAHAMAS("BS", "Bahamas"),
        BHUTAN("BT", "Bhutan"),
        BOUVET_ISLAND("BV", "Bouvet Island"),
        BOTSWANA("BW", "Botswana"),
        BELARUS("BY", "Belarus"),
        BELIZE("BZ", "Belize"),
        CANADA("CA", "Canada"),
        COCOS_ISLANDS("CC", "Cocos Islands"),
        THE_DEMOCRATIC_REPUBLIC_OF_CONGO("CD", "The Democratic Republic Of Congo"),
        CENTRAL_AFRICAN_REPUBLIC("CF", "Central African Republic"),
        CONGO("CG", "Congo"),
        SWITZERLAND("CH", "Switzerland"),
        COTE_D_IVOIRE("CI", "Cote d'Ivoire"),
        COOK_ISLANDS("CK", "Cook Islands"),
        CHILE("CL", "Chile"),
        CAMEROON("CM", "Cameroon"),
        CHINA("CN", "China"),
        COLOMBIA("CO", "Colombia"),
        COSTA_RICA("CR", "Costa Rica"),
        CUBA("CU", "Cuba"),
        CAPE_VERDE("CV", "Cape Verde"),
        CURACAO("CW", "Curacao"),
        CHRISTMAS_ISLAND("CX", "Christmas Island"),
        CYPRUS("CY", "Cyprus"),
        CZECH_REPUBLIC("CZ", "Czech Republic"),
        GERMANY("DE", "Germany"),
        DJIBOUTI("DJ", "Djibouti"),
        DENMARK("DK", "Denmark"),
        DOMINICA("DM", "Dominica"),
        DOMINICAN_REPUBLIC("DO", "Dominican Republic"),
        ALGERIA("DZ", "Algeria"),
        ECUADOR("EC", "Ecuador"),
        ESTONIA("EE", "Estonia"),
        EGYPT("EG", "Egypt"),
        WESTERN_SAHARA("EH", "Western Sahara"),
        ERITREA("ER", "Eritrea"),
        SPAIN("ES", "Spain"),
        ETHIOPIA("ET", "Ethiopia"),
        FINLAND("FI", "Finland"),
        FIJI("FJ", "Fiji"),
        FALKLAND_ISLANDS("FK", "Falkland Islands"),
        MICRONESIA("FM", "Micronesia"),
        FAROE_ISLANDS("FO", "Faroe Islands"),
        FRANCE("FR", "France"),
        GABON("GA", "Gabon"),
        UNITED_KINGDOM("GB", "United Kingdom"),
        GRENADA("GD", "Grenada"),
        GEORGIA("GE", "Georgia"),
        FRENCH_GUIANA("GF", "French Guiana"),
        GUERNSEY("GG", "Guernsey"),
        GHANA("GH", "Ghana"),
        GIBRALTAR("GI", "Gibraltar"),
        GREENLAND("GL", "Greenland"),
        GAMBIA("GM", "Gambia"),
        GUINEA("GN", "Guinea"),
        GUADELOUPE("GP", "Guadeloupe"),
        EQUATORIAL_GUINEA("GQ", "Equatorial Guinea"),
        GREECE("GR", "Greece"),
        SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS("GS", "South Georgia And The South Sandwich Islands"),
        GUATEMALA("GT", "Guatemala"),
        GUAM("GU", "Guam"),
        GUINEA_BISSAU("GW", "Guinea-Bissau"),
        GUYANA("GY", "Guyana"),
        HONG_KONG("HK", "Hong Kong"),
        HEARD_ISLAND_AND_MCDONALD_ISLANDS("HM", "Heard Island And McDonald Islands"),
        HONDURAS("HN", "Honduras"),
        CROATIA("HR", "Croatia"),
        HAITI("HT", "Haiti"),
        HUNGARY("HU", "Hungary"),
        INDONESIA("ID", "Indonesia"),
        IRELAND("IE", "Ireland"),
        ISRAEL("IL", "Israel"),
        ISLE_OF_MAN("IM", "Isle Of Man"),
        INDIA("IN", "India"),
        BRITISH_INDIAN_OCEAN_TERRITORY("IO", "British Indian Ocean Territory"),
        IRAQ("IQ", "Iraq"),
        IRAN("IR", "Iran"),
        ICELAND("IS", "Iceland"),
        ITALY("IT", "Italy"),
        JERSEY("JE", "Jersey"),
        JAMAICA("JM", "Jamaica"),
        JORDAN("JO", "Jordan"),
        JAPAN("JP", "Japan"),
        KENYA("KE", "Kenya"),
        KYRGYZSTAN("KG", "Kyrgyzstan"),
        CAMBODIA("KH", "Cambodia"),
        KIRIBATI("KI", "Kiribati"),
        COMOROS("KM", "Comoros"),
        SAINT_KITTS_AND_NEVIS("KN", "Saint Kitts And Nevis"),
        NORTH_KOREA("KP", "North Korea"),
        SOUTH_KOREA("KR", "South Korea"),
        KUWAIT("KW", "Kuwait"),
        CAYMAN_ISLANDS("KY", "Cayman Islands"),
        KAZAKHSTAN("KZ", "Kazakhstan"),
        LAOS("LA", "Laos"),
        LEBANON("LB", "Lebanon"),
        SAINT_LUCIA("LC", "Saint Lucia"),
        LIECHTENSTEIN("LI", "Liechtenstein"),
        SRI_LANKA("LK", "Sri Lanka"),
        LIBERIA("LR", "Liberia"),
        LESOTHO("LS", "Lesotho"),
        LITHUANIA("LT", "Lithuania"),
        LUXEMBOURG("LU", "Luxembourg"),
        LATVIA("LV", "Latvia"),
        LIBYA("LY", "Libya"),
        MOROCCO("MA", "Morocco"),
        MONACO("MC", "Monaco"),
        MOLDOVA("MD", "Moldova"),
        MONTENEGRO("ME", "Montenegro"),
        SAINT_MARTIN("MF", "Saint Martin"),
        MADAGASCAR("MG", "Madagascar"),
        MARSHALL_ISLANDS("MH", "Marshall Islands"),
        MACEDONIA("MK", "Macedonia"),
        MALI("ML", "Mali"),
        MYANMAR("MM", "Myanmar"),
        MONGOLIA("MN", "Mongolia"),
        MACAO("MO", "Macao"),
        NORTHERN_MARIANA_ISLANDS("MP", "Northern Mariana Islands"),
        MARTINIQUE("MQ", "Martinique"),
        MAURITANIA("MR", "Mauritania"),
        MONTSERRAT("MS", "Montserrat"),
        MALTA("MT", "Malta"),
        MAURITIUS("MU", "Mauritius"),
        MALDIVES("MV", "Maldives"),
        MALAWI("MW", "Malawi"),
        MEXICO("MX", "Mexico"),
        MALAYSIA("MY", "Malaysia"),
        MOZAMBIQUE("MZ", "Mozambique"),
        NAMIBIA("NA", "Namibia"),
        NEW_CALEDONIA("NC", "New Caledonia"),
        NIGER("NE", "Niger"),
        NORFOLK_ISLAND("NF", "Norfolk Island"),
        NIGERIA("NG", "Nigeria"),
        NICARAGUA("NI", "Nicaragua"),
        NETHERLANDS("NL", "Netherlands"),
        NORWAY("NO", "Norway"),
        NEPAL("NP", "Nepal"),
        NAURU("NR", "Nauru"),
        NIUE("NU", "Niue"),
        NEW_ZEALAND("NZ", "New Zealand"),
        OMAN("OM", "Oman"),
        PANAMA("PA", "Panama"),
        PERU("PE", "Peru"),
        FRENCH_POLYNESIA("PF", "French Polynesia"),
        PAPUA_NEW_GUINEA("PG", "Papua New Guinea"),
        PHILIPPINES("PH", "Philippines"),
        PAKISTAN("PK", "Pakistan"),
        POLAND("PL", "Poland"),
        SAINT_PIERRE_AND_MIQUELON("PM", "Saint Pierre And Miquelon"),
        PITCAIRN("PN", "Pitcairn"),
        PUERTO_RICO("PR", "Puerto Rico"),
        PALESTINE("PS", "Palestine"),
        PORTUGAL("PT", "Portugal"),
        PALAU("PW", "Palau"),
        PARAGUAY("PY", "Paraguay"),
        QATAR("QA", "Qatar"),
        REUNION("RE", "Reunion"),
        ROMANIA("RO", "Romania"),
        SERBIA("RS", "Serbia"),
        RUSSIA("RU", "Russia"),
        RWANDA("RW", "Rwanda"),
        SAUDI_ARABIA("SA", "Saudi Arabia"),
        SOLOMON_ISLANDS("SB", "Solomon Islands"),
        SEYCHELLES("SC", "Seychelles"),
        SUDAN("SD", "Sudan"),
        SWEDEN("SE", "Sweden"),
        SINGAPORE("SG", "Singapore"),
        SAINT_HELENA("SH", "Saint Helena"),
        SLOVENIA("SI", "Slovenia"),
        SVALBARD_AND_JAN_MAYEN("SJ", "Svalbard And Jan Mayen"),
        SLOVAKIA("SK", "Slovakia"),
        SIERRA_LEONE("SL", "Sierra Leone"),
        SAN_MARINO("SM", "San Marino"),
        SENEGAL("SN", "Senegal"),
        SOMALIA("SO", "Somalia"),
        SURINAME("SR", "Suriname"),
        SOUTH_SUDAN("SS", "South Sudan"),
        SAO_TOME_AND_PRINCIPE("ST", "Sao Tome And Principe"),
        EL_SALVADOR("SV", "El Salvador"),
        SINT_MAARTEN_DUTCH_PART("SX", "Sint Maarten (Dutch part)"),
        SYRIA("SY", "Syria"),
        SWAZILAND("SZ", "Swaziland"),
        TURKS_AND_CAICOS_ISLANDS("TC", "Turks And Caicos Islands"),
        CHAD("TD", "Chad"),
        FRENCH_SOUTHERN_TERRITORIES("TF", "French Southern Territories"),
        TOGO("TG", "Togo"),
        THAILAND("TH", "Thailand"),
        TAJIKISTAN("TJ", "Tajikistan"),
        TOKELAU("TK", "Tokelau"),
        TIMOR_LESTE("TL", "Timor-Leste"),
        TURKMENISTAN("TM", "Turkmenistan"),
        TUNISIA("TN", "Tunisia"),
        TONGA("TO", "Tonga"),
        TURKEY("TR", "Turkey"),
        TRINIDAD_AND_TOBAGO("TT", "Trinidad and Tobago"),
        TUVALU("TV", "Tuvalu"),
        TAIWAN("TW", "Taiwan"),
        TANZANIA("TZ", "Tanzania"),
        UKRAINE("UA", "Ukraine"),
        UGANDA("UG", "Uganda"),
        UNITED_STATES_MINOR_OUTLYING_ISLANDS("UM", "United States Minor Outlying Islands"),
        UNITED_STATES("US", "United States"),
        URUGUAY("UY", "Uruguay"),
        UZBEKISTAN("UZ", "Uzbekistan"),
        VATICAN("VA", "Vatican"),
        SAINT_VINCENT_AND_THE_GRENADINES("VC", "Saint Vincent And The Grenadines"),
        VENEZUELA("VE", "Venezuela"),
        BRITISH_VIRGIN_ISLANDS("VG", "British Virgin Islands"),
        U_S__VIRGIN_ISLANDS("VI", "U.S. Virgin Islands"),
        VIETNAM("VN", "Vietnam"),
        VANUATU("VU", "Vanuatu"),
        WALLIS_AND_FUTUNA("WF", "Wallis And Futuna"),
        SAMOA("WS", "Samoa"),
        YEMEN("YE", "Yemen"),
        MAYOTTE("YT", "Mayotte"),
        SOUTH_AFRICA("ZA", "South Africa"),
        ZAMBIA("ZM", "Zambia"),
        ZIMBABWE("ZW", "Zimbabwe");

        private String isoTag;
        private String name;

        Country(String isoTag, String name) {
            this.isoTag = isoTag;
            this.name = name;
        }

        /**
         * Gets the name of the country.
         *
         * @return The name of the country.
         */
        public String getCountryName() {
            return name;
        }

        /**
         * Gets the iso tag of the country.
         *
         * @return The iso tag of the country.
         */
        public String getCountryIsoTag() {
            return isoTag;
        }

        /**
         * Gets a country by it's iso tag.
         *
         * @param isoTag The iso tag of the county.
         * @return The country with the given iso tag or <code>null</code> if
         *         unknown.
         */
        public static Country byIsoTag(String isoTag) {
            for (Country country : Country.values()) {
                if (country.getCountryIsoTag().equals(isoTag)) {
                    return country;
                }
            }
            return null;
        }

        /**
         * Gets a country by a locale.
         *
         * @param locale The locale.
         * @return The country from the giben locale or <code>null</code> if
         *         unknown country or if the locale does not contain a country.
         */
        public static Country byLocale(Locale locale) {
            return byIsoTag(locale.getCountry());
        }

    }

}
src/main/java/io/github/flibio/economylite/impl/economy/LiteEconomyService.java
public class LiteEconomyService implements EconomyService {

    private PlayerEconService playerService = EconomyLite.getPlayerService();
    private VirtualEconService virtualService = EconomyLite.getVirtualService();
    private CurrencyEconService currencyService = EconomyLite.getCurrencyService();

    @Override
    public void registerContextCalculator(ContextCalculator<Account> arg0) {
        return;
    }

    @Override
    public Optional<UniqueAccount> getOrCreateAccount(UUID uuid) {
        if (playerService.accountExists(uuid, getDefaultCurrency(), CauseFactory.create("New account check"))) {
            // Return the account
            return Optional.of(new LiteUniqueAccount(uuid));
        } else {
            // Make a new account
            UniqueAccount account = new LiteUniqueAccount(uuid);
            if (playerService.setBalance(uuid, account.getDefaultBalance(getDefaultCurrency()), getDefaultCurrency(),
                    CauseFactory.create("Creating account"))) {
                return Optional.of(account);
            } else {
                return Optional.empty();
            }
        }
    }

    @Override
    public Optional<Account> getOrCreateAccount(String id) {
        if (virtualService.accountExists(id, getDefaultCurrency(), CauseFactory.create("New account check"))) {
            // Return the account
            return Optional.of(new LiteVirtualAccount(id));
        } else {
            // Make a new account
            VirtualAccount account = new LiteVirtualAccount(id);
            if (virtualService.setBalance(id, account.getDefaultBalance(getDefaultCurrency()), getDefaultCurrency(),
                    CauseFactory.create("Creating account"))) {
                return Optional.of(account);
            } else {
                return Optional.empty();
            }
        }
    }

    @Override
    public Set<Currency> getCurrencies() {
        return currencyService.getCurrencies();
    }

    @Override
    public Currency getDefaultCurrency() {
        return currencyService.getCurrentCurrency();
    }

    @Override
    public boolean hasAccount(UUID uuid) {
        return playerService.accountExists(uuid, getDefaultCurrency(), CauseFactory.create("Checking account existance"));
    }

    @Override
    public boolean hasAccount(String identifier) {
        return virtualService.accountExists(identifier, getDefaultCurrency(), CauseFactory.create("Checking account existance"));
    }

}
src/main/java/io/github/flibio/economylite/commands/virtual/VirtualAddCommand.java
@AsyncCommand
@ParentCommand(parentCommand = VirtualEconCommand.class)
@Command(aliases = {"add"}, permission = "economylite.admin.virtual.add")
public class VirtualAddCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.string(Text.of("account")), GenericArguments.doubleNum(Text.of("amount")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("account").isPresent() && args.getOne("amount").isPresent()) {
            String target = args.<String>getOne("account").get();
            BigDecimal toAdd = BigDecimal.valueOf(args.<Double>getOne("amount").get());
            Optional<Account> aOpt = EconomyLite.getEconomyService().getOrCreateAccount(target);
            if (aOpt.isPresent()) {
                Account targetAccount = aOpt.get();
                if (targetAccount.deposit(EconomyLite.getCurrencyService().getCurrentCurrency(), toAdd,
                        Cause.of(EventContext.empty(),(EconomyLite.getInstance()))).getResult().equals(ResultType.SUCCESS)) {
                    src.sendMessage(messageStorage.getMessage("command.econ.addsuccess", "name", target));
                } else {
                    src.sendMessage(messageStorage.getMessage("command.econ.addfail", "name", target));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.error"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

}
src/main/java/io/github/flibio/economylite/api/CurrencyEconService.java
public interface CurrencyEconService {

    /**
     * Gets all currencies.
     * 
     * @return A set of all currencies.
     */
    public Set<Currency> getCurrencies();

    /**
     * Adds a currency to the currencies.
     * 
     * @param currency The currency to add.
     */
    public void addCurrency(Currency currency);

    /**
     * Gets the default currency.
     * 
     * @return The default currency.
     */
    public Currency getDefaultCurrency();

    /**
     * Sets the currency in use by the server.
     * 
     * @param currency The currency in use.
     */
    public void setCurrentCurrency(Currency currency);

    /**
     * Deletes a currency.
     * 
     * @param currency The currency to delete.
     */
    public void deleteCurrency(Currency currency);

    /**
     * Gets the currency in use by the server.
     * 
     * @return The currency in use.
     */
    public Currency getCurrentCurrency();

}
src/main/java/io/github/flibio/economylite/commands/currency/CurrencyCreateCommand.java
@AsyncCommand
@ParentCommand(parentCommand = CurrencyCommand.class)
@Command(aliases = {"create"}, permission = "economylite.admin.currency.create")
public class CurrencyCreateCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private CurrencyEconService currencyService = EconomyLite.getCurrencyService();
    private ConfigManager manager = EconomyLite.getCurrencyManager();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec
                .builder()
                .executor(this)
                .arguments(GenericArguments.string(Text.of("singular")), GenericArguments.string(Text.of("plural")),
                        GenericArguments.string(Text.of("symbol")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        if (args.getOne("singular").isPresent() && args.getOne("plural").isPresent() && args.getOne("symbol").isPresent()) {
            String singular = args.<String>getOne("singular").get();
            String plural = args.<String>getOne("plural").get();
            String symbol = args.<String>getOne("symbol").get();
            boolean found = false;
            for (Currency c : currencyService.getCurrencies()) {
                if (c.getDisplayName().toPlain().equalsIgnoreCase(singular)) {
                    found = true;
                }
            }
            if (found) {
                src.sendMessage(messageStorage.getMessage("command.currency.exists"));
            } else {
                Currency currency = new LiteCurrency(singular, plural, symbol, false, 2);
                currencyService.addCurrency(currency);
                String configId = currency.getId().replaceAll("economylite:", "");
                manager.forceValue(currency.getDisplayName().toPlain(), configId, ".singular");
                manager.forceValue(currency.getPluralDisplayName().toPlain(), configId, ".plural");
                manager.forceValue(currency.getSymbol().toPlain(), configId, ".symbol");
                manager.save();
                src.sendMessage(messageStorage.getMessage("command.currency.created", "currency", currency.getDisplayName().toPlain()));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }
}
src/main/java/io/github/flibio/economylite/commands/PayCommand.java
@AsyncCommand
@Command(aliases = {"pay"}, permission = "economylite.pay")
public class PayCommand extends BaseCommandExecutor<Player> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private EconomyService ecoService = EconomyLite.getEconomyService();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.user(Text.of("player")), GenericArguments.doubleNum(Text.of("amount")));
    }

    @Override
    public void run(Player src, CommandContext args) {
        if (args.getOne("player").isPresent() && args.getOne("amount").isPresent()) {
            BigDecimal amount = BigDecimal.valueOf(args.<Double>getOne("amount").get());
            if (amount.doubleValue() <= 0) {
                src.sendMessage(messageStorage.getMessage("command.pay.invalid"));
            } else {
                User target = args.<User>getOne("player").get();
                if (!EconomyLite.getConfigManager().getValue(Boolean.class, false, "confirm-offline-payments") || target.isOnline()) {
                    // Complete the payment
                    pay(target, amount, src);
                } else {
                    src.sendMessage(messageStorage.getMessage("command.pay.confirm", "player", target.getName()));
                    // Check if they want to still pay
                    src.sendMessage(TextUtils.yesOrNo(c -> {
                        pay(target, amount, src);
                    }, c -> {
                        src.sendMessage(messageStorage.getMessage("command.pay.confirmno", "player", target.getName()));
                    }));
                }

            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

    private void pay(User target, BigDecimal amount, Player src) {
        String targetName = target.getName();
        if (!target.getUniqueId().equals(src.getUniqueId())) {
            Optional<UniqueAccount> uOpt = ecoService.getOrCreateAccount(src.getUniqueId());
            Optional<UniqueAccount> tOpt = ecoService.getOrCreateAccount(target.getUniqueId());
            if (uOpt.isPresent() && tOpt.isPresent()) {
                if (uOpt.get()
                        .transfer(tOpt.get(), ecoService.getDefaultCurrency(), amount, Cause.of(EventContext.empty(), (EconomyLite.getInstance())))
                        .getResult().equals(ResultType.SUCCESS)) {
                    Text label = ecoService.getDefaultCurrency().getPluralDisplayName();
                    if (amount.equals(BigDecimal.ONE)) {
                        label = ecoService.getDefaultCurrency().getDisplayName();
                    }
                    src.sendMessage(messageStorage.getMessage("command.pay.success", "target", targetName, "amountandlabel",
                            String.format(Locale.ENGLISH, "%,.2f", amount) + " " + label.toPlain()));
                    final Text curLabel = label;
                    Sponge.getServer().getPlayer(target.getUniqueId()).ifPresent(p -> {
                        p.sendMessage(messageStorage.getMessage("command.pay.target", "amountandlabel",
                                String.format(Locale.ENGLISH, "%,.2f", amount) + " " + curLabel.toPlain(), "sender",
                                uOpt.get().getDisplayName().toPlain()));
                    });
                } else {
                    src.sendMessage(messageStorage.getMessage("command.pay.failed", "target", targetName));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.error"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.pay.notyou"));
        }
    }

}
src/main/java/io/github/flibio/economylite/commands/virtual/PayVirtualCommand.java
@AsyncCommand
@Command(aliases = {"payvirtual", "payv"}, permission = "economylite.virtual.pay")
public class PayVirtualCommand extends BaseCommandExecutor<Player> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private EconomyService ecoService = EconomyLite.getEconomyService();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec
                .builder()
                .executor(this)
                .arguments(GenericArguments.string(Text.of("target")), GenericArguments.doubleNum(Text.of("amount")));
    }

    @Override
    public void run(Player src, CommandContext args) {
        if (args.getOne("target").isPresent() && args.getOne("amount").isPresent()) {
            String target = args.<String>getOne("target").get();
            BigDecimal amount = BigDecimal.valueOf(args.<Double>getOne("amount").get());
            Optional<Account> aOpt = EconomyLite.getEconomyService().getOrCreateAccount(target);
            Optional<UniqueAccount> uOpt = EconomyLite.getEconomyService().getOrCreateAccount(src.getUniqueId());
            // Check for negative payments
            if (amount.doubleValue() <= 0) {
                src.sendMessage(messageStorage.getMessage("command.pay.invalid"));
            } else {
                if (aOpt.isPresent() && uOpt.isPresent()) {
                    Account receiver = aOpt.get();
                    UniqueAccount payer = uOpt.get();
                    if (payer.transfer(receiver, ecoService.getDefaultCurrency(), amount, Cause.of(EventContext.empty(), (EconomyLite.getInstance())))
                            .getResult().equals(ResultType.SUCCESS)) {
                        src.sendMessage(messageStorage.getMessage("command.pay.success", "target", receiver.getDisplayName().toPlain()));
                    } else {
                        src.sendMessage(messageStorage.getMessage("command.pay.failed", "target", receiver.getDisplayName().toPlain()));
                    }
                }
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }

}
src/main/java/io/github/flibio/economylite/impl/PlayerDataService.java
public class PlayerDataService extends PlayerServiceCommon {

    public PlayerDataService() {
        super(LocalSqlManager.createInstance(EconomyLite.getInstance(), "data", correctPath(EconomyLite.getInstance().getConfigDir().toString()))
                .get(), true);
    }

    private static String correctPath(String path) {
        if (new File(path).isAbsolute()) {
            return path;
        } else {
            return "./" + path;
        }
    }
}
src/main/java/io/github/flibio/economylite/commands/virtual/VirtualBalanceCommand.java
@AsyncCommand
@Command(aliases = {"vbalance", "vbal"}, permission = "economylite.admin.virtual.balance")
public class VirtualBalanceCommand extends BaseCommandExecutor<CommandSource> {

    private MessageStorage messageStorage = EconomyLite.getMessageStorage();
    private CurrencyEconService currencyService = EconomyLite.getCurrencyService();

    @Override
    public Builder getCommandSpecBuilder() {
        return CommandSpec.builder()
                .executor(this)
                .arguments(GenericArguments.remainingJoinedStrings(Text.of("account")));
    }

    @Override
    public void run(CommandSource src, CommandContext args) {
        Currency currency = currencyService.getCurrentCurrency();
        if (args.getOne("account").isPresent()) {
            if (src.hasPermission("economylite.admin.virtualbalance")) {
                String target = args.<String>getOne("account").get();
                Optional<Account> aOpt = EconomyLite.getEconomyService().getOrCreateAccount(target);
                if (aOpt.isPresent()) {
                    BigDecimal bal = aOpt.get().getBalance(currency);
                    Text label = currency.getPluralDisplayName();
                    if (bal.equals(BigDecimal.ONE)) {
                        label = currency.getDisplayName();
                    }
                    src.sendMessage(messageStorage.getMessage("command.balanceother", "player", aOpt.get().getDisplayName().toPlain(), "balance",
                            String.format(Locale.ENGLISH, "%,.2f", bal), "label", label.toPlain()));
                } else {
                    src.sendMessage(messageStorage.getMessage("command.error"));
                }
            } else {
                src.sendMessage(messageStorage.getMessage("command.noperm"));
            }
        } else {
            src.sendMessage(messageStorage.getMessage("command.error"));
        }
    }
}
src/main/java/io/github/flibio/economylite/impl/economy/registry/CurrencyRegistryModule.java
public class CurrencyRegistryModule implements CatalogRegistryModule<Currency> {

    @Override
    public Optional<Currency> getById(String id) {
        for (Currency cur : EconomyLite.getCurrencyService().getCurrencies()) {
            if (cur.getId().equals(id)) {
                return Optional.of(cur);
            }
        }
        return Optional.empty();
    }

    @Override
    public Collection<Currency> getAll() {
        return EconomyLite.getCurrencyService().getCurrencies();
    }
}


Question: import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import io.github.flibio.economylite.api.CurrencyEconService;
import io.github.flibio.economylite.api.PlayerEconService;
import io.github.flibio.economylite.api.VirtualEconService;
import io.github.flibio.economylite.bstats.Metrics;
import io.github.flibio.economylite.commands.MigrateCommand;
import io.github.flibio.economylite.commands.PayCommand;
import io.github.flibio.economylite.commands.admin.AddCommand;
import io.github.flibio.economylite.commands.admin.EconCommand;
import io.github.flibio.economylite.commands.RefreshCommand;
import io.github.flibio.economylite.commands.admin.RemoveCommand;
import io.github.flibio.economylite.commands.admin.SetAllCommand;
import io.github.flibio.economylite.commands.admin.SetCommand;
import io.github.flibio.economylite.commands.balance.BalTopCommand;
import io.github.flibio.economylite.commands.balance.BalanceCommand;
import io.github.flibio.economylite.commands.currency.CurrencyCommand;
import io.github.flibio.economylite.commands.currency.CurrencyCreateCommand;
import io.github.flibio.economylite.commands.currency.CurrencyDeleteCommand;
import io.github.flibio.economylite.commands.currency.CurrencySetCommand;
import io.github.flibio.economylite.commands.virtual.PayVirtualCommand;
import io.github.flibio.economylite.commands.virtual.VirtualAddCommand;
import io.github.flibio.economylite.commands.virtual.VirtualBalanceCommand;
import io.github.flibio.economylite.commands.virtual.VirtualEconCommand;
import io.github.flibio.economylite.commands.virtual.VirtualPayCommand;
import io.github.flibio.economylite.commands.virtual.VirtualRemoveCommand;
import io.github.flibio.economylite.commands.virtual.VirtualSetCommand;
import io.github.flibio.economylite.impl.CurrencyService;
import io.github.flibio.economylite.impl.PlayerDataService;
import io.github.flibio.economylite.impl.VirtualDataService;
import io.github.flibio.economylite.impl.economy.LiteCurrency;
import io.github.flibio.economylite.impl.economy.LiteEconomyService;
import io.github.flibio.economylite.impl.economy.registry.CurrencyRegistryModule;
import io.github.flibio.economylite.modules.Module;
import io.github.flibio.economylite.modules.loan.LoanModule;
import io.github.flibio.economylite.modules.sql.SqlModule;
import io.github.flibio.utils.commands.CommandLoader;
import io.github.flibio.utils.config.ConfigManager;
import io.github.flibio.utils.message.MessageStorage;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import org.slf4j.Logger;
import org.spongepowered.api.Game;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameConstructionEvent;
import org.spongepowered.api.event.game.state.GamePreInitializationEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.service.economy.Currency;
import org.spongepowered.api.service.economy.EconomyService;
import org.spongepowered.api.text.serializer.TextSerializers;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/*
 * This file is part of EconomyLite, licensed under the MIT License (MIT). See the LICENSE file at the root of this project for more information.
 */
package io.github.flibio.economylite;



@Plugin(id = PluginInfo.ID, name = PluginInfo.NAME, version = PluginInfo.VERSION, description = PluginInfo.DESCRIPTION)
public class EconomyLite {

    @Inject @ConfigDir(sharedRoot = false) private Path configDir;

    @Inject @ConfigDir(sharedRoot = true) private Path mainDir;

    @SuppressWarnings("unused") @Inject private Metrics metrics;

    @Inject private Logger logger;

    @Inject private Game game;

    @Inject private PluginContainer container;

    private static ConfigManager currencyManager;
    private static ConfigManager configManager;
    private static MessageStorage messageStorage;
    private static EconomyLite instance;

    public static LiteEconomyService economyService;
    private static VirtualEconService virtualEconService;
    private static PlayerEconService playerEconService;
    public static CurrencyEconService currencyEconService;

    @Listener
    public void onConstruct(GameConstructionEvent event) {
        System.getProperties().setProperty("h2.bindAddress", "127.0.0.1");
    }

    @Listener
    public void onServerInitialize(GamePreInitializationEvent event) {
        logger.info("EconomyLite " + PluginInfo.VERSION + " is initializing!");
        instance = this;
        // File setup
        configManager = ConfigManager.create(configDir, "config.conf", logger);
        currencyManager = ConfigManager.create(configDir, "currencies.conf", logger);
        initializeFiles();
        initializeCurrencies();
        // Load Message Storage
        messageStorage = MessageStorage.create(configDir, "messages", logger);
        // Load modules
        List<Module> postInitModules = new ArrayList<>();
        getModules().forEach(m -> {
            m.initializeConfig();
            if (m.isEnabled()) {
                if (m.initialize(logger, instance)) {
                    logger.info("Loaded the " + m.getName() + " module!");
                    postInitModules.add(m);
                } else {
                    logger.error("Failed to load the " + m.getName() + " module!");
                }
            } else {
                logger.info("The " + m.getName() + " module is disabled!");
            }
        });
        // If the services have not been set, set them to default.
        if (playerEconService == null || virtualEconService == null) {
            playerEconService = new PlayerDataService();
            virtualEconService = new VirtualDataService();
        }
        // Load the economy service
        economyService = new LiteEconomyService();
        // Register the Economy Service
        game.getServiceManager().setProvider(this, EconomyService.class, economyService);
        // Post-initialize modules
        postInitModules.forEach(module -> {
            module.postInitialization(logger, instance);
        });
        // Register commands
        CommandLoader.registerCommands(this, TextSerializers.FORMATTING_CODE.serialize(messageStorage.getMessage("command.invalidsource")),
                new CurrencyCommand(),
                new CurrencySetCommand(),
                new CurrencyDeleteCommand(),
                new CurrencyCreateCommand(),
                new BalanceCommand(),
                new EconCommand(),
                new SetCommand(),
                new SetAllCommand(),
                new RemoveCommand(),
                new AddCommand(),
                new PayCommand(),
                new BalTopCommand(),
                new VirtualBalanceCommand(),
                new VirtualEconCommand(),
                new VirtualAddCommand(),
                new VirtualSetCommand(),
                new VirtualRemoveCommand(),
                new VirtualPayCommand(),
                new PayVirtualCommand(),
Answer:                 new MigrateCommand(),