the_big_one/src/main/java/jesse/keeblarcraft/BankMgr/IndividualBank.java

399 lines
19 KiB
Java

package jesse.keeblarcraft.BankMgr;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import static java.util.Map.entry;
import java.io.File;
import jesse.keeblarcraft.ConfigMgr.ConfigManager;
import jesse.keeblarcraft.Utils.ChatUtil;
import jesse.keeblarcraft.Utils.ChatUtil.CONSOLE_COLOR;
import jesse.keeblarcraft.Utils.CustomExceptions.FILE_WRITE_EXCEPTION;
// Contains the information of an individual bank
//
// The bank will keep track of all accounts within its facilities. In addition to accounts, the bank
// maintains its own identifier which is unique and other misc things.
public class IndividualBank {
private Map<String, Integer> ACCOUNT_TYPES = Map.ofEntries(
entry("checking", 0),
entry("savings", 1)
);
private ConfigManager config = new ConfigManager();
private Integer routingNumber; // this is the banks unique identifier
private Integer numberOfAccounts; // Total number of accounts the bank has. This includes only active accounts inside accountsList.
private Integer maxBankAccounts = 100_000_000; // Making this simple for myself any one type of account has 8 random numbers genereated so 10^8 possible accounts
private String bankFourLetterIdentifier;
private String registeredBankName;
// Think FDIC but from the servers account (keeblarcraft insurance corporation)
// KBIC will ensure an amount of money based on its trustworthiness to a bank and the number of holders it has.
private Integer kbicInsuredAmount;
private Boolean kbicInsured;
// bankMoney is the total amount of money the bank possesses itself. The bank itself is personally responsible
// for backing the amount of money it claims it has and this is the balance that is withdrawn from for credits.
// A bank can have a sum of money that is less than the total amount of money of all its account holders
private Integer bankMoney;
// Key = ACCOUNT NUMBER
// Value = ACCOUNT
private class Accounts {
// Key = account identifier
// Val = account object
private HashMap<String, IndividualAccount> accountsList = new HashMap<String, IndividualAccount>();
// Key = user uuid
// Val = List of account identifiers
private HashMap<String, List<String>> accountsListFromName = new HashMap<String, List<String>>(); // This is a list that just points to a list of account numbers by person. USEFUL
}
Accounts accounts;
private List<String> lockedUsers; // A list of users who are locked out of the bank and are incapable of performing more actions within it
public IndividualBank(String routingNumber, String nameOfBank) {
accounts = new Accounts();
lockedUsers = new ArrayList<String>();
registeredBankName = nameOfBank.toUpperCase();
bankFourLetterIdentifier = nameOfBank.substring(0, 4).toLowerCase();
this.routingNumber = Integer.parseInt(routingNumber);
System.out.println("CREATING BANK ACCOUNT WITH ROUTING NUMBER " + routingNumber + " AND NAME " + nameOfBank);
boolean existingFile = false;
try {
// Read in the global accounts list
String accountsListDir = "bank/" + routingNumber.toString() + "/accounts/";
System.out.println("accountsListDir + bankName is " + accountsListDir + nameOfBank);
accounts = config.GetJsonObjectFromFile(accountsListDir + nameOfBank, Accounts.class);
existingFile = true;
// TODO: REPLACE WITH SQL SERVER. DIRTY ITERATE OVER ALL FILES IN DIRECTORY TO LOAD STRUCTURE
File dir = new File(accountsListDir);
File[] allFiles = dir.listFiles();
if (allFiles != null) {
for (File file : allFiles ) {
// First grab file identifier as KEY
String accountIdentifier = file.getName();
String accountFromFile = accountsListDir + "/" + accountIdentifier;
System.out.println("accountIdentifier found in file is " + accountIdentifier);
System.out.println("account identifier with dir path is " + accountFromFile);
accounts.accountsList.put(accountIdentifier, config.GetJsonObjectFromFile(accountFromFile, IndividualAccount.class));
}
}
} catch (Exception e) {
System.out.println("The try-catch in IndividualBank.java failed to complete. Printing stack trace");
// e.printStackTrace();
// Falling into this catch just means the file needs to be made
}
if (!existingFile)
{
System.out.println(ChatUtil.ColoredString("Trying to create new file", CONSOLE_COLOR.BLUE));
try {
// We assume the bank dir is created by server. Create this banks dir
config.CreateDirectory("bank/" + routingNumber);
// Create this banks initial accounts dir
config.CreateDirectory("bank/" + routingNumber + "/accounts");
// Flash initial account configuration file for this bank
FlashConfig("accounts");
} catch (Exception e) {
System.out.println(ChatUtil.ColoredString("Could not write to file", CONSOLE_COLOR.RED));
}
} else {
System.out.println(ChatUtil.ColoredString("Moving on", CONSOLE_COLOR.BLUE));
}
// A modified config reader is needed here for when each IndividualAccount is read in - the name is taken from that and is attached to the
// 'accountsListFromName' structure. This makes it no worse than O(n) to fill these two structures in.
// NOTE: This is an *EXPENSIVE* operation! Future us might need to update this. Also note a method is needed for everytime a player opens a new account
// or gets put on one to update the map every time
for (Entry<String, IndividualAccount> account : accounts.accountsList.entrySet()) {
// We must loop over the string of holders for each account as well to make the flattened accountsListFromName map
List<String> accountHolders = account.getValue().GetAccountHolders();
// Match each user to the secondary map & add to list-value if not existing
for (Integer holderIndex = 0; holderIndex < accountHolders.size(); holderIndex++) {
if (accounts.accountsListFromName.containsKey(accountHolders.get(holderIndex))) {
// Case 1: User exists, update map entry
accounts.accountsListFromName.get(accountHolders.get(holderIndex)).add(account.getKey()); // Add a new account id to this person in the new flat map
} else {
// Case 2: User does not already exist; add a new map entry
accounts.accountsListFromName.put(accountHolders.get(holderIndex), List.of(account.getKey())); // Store name as key, and new List with the value of ACCOUNT #
}
}
}
numberOfAccounts = accounts.accountsList.size();
}
public String GetBankName() {
return registeredBankName;
}
public List<IndividualAccount> GetAccountsOfUser(String uuid) {
System.out.println("UUID passed in: " + uuid);
List<IndividualAccount> accountsFromUser = new ArrayList<IndividualAccount>();
List<String> listOfAccounts = accounts.accountsListFromName.get(uuid);
System.out.println("Is list of accounts null? " + (listOfAccounts == null ? "YES" : "NO"));
System.out.println("List of account size: " + listOfAccounts.size());
if (listOfAccounts != null && listOfAccounts.size() > 0) {
for (int i = 0; i < listOfAccounts.size(); i++) {
accountsFromUser.add(accounts.accountsList.get(listOfAccounts.get(i)));
}
}
return accountsFromUser;
}
public Integer GetBankBalance() {
return bankMoney;
}
public void AddBankBalance(Integer amount) {
bankMoney += amount;
}
public void SubtractBankBalance(Integer amount) {
bankMoney -= amount;
}
public void SetBankBalance(Integer amount) {
bankMoney = amount;
}
public Boolean IsBankInsured() {
return kbicInsured;
}
public Integer InsuranceAmount() {
Integer insuredAmnt = 0;
if (kbicInsured) {
insuredAmnt = kbicInsuredAmount;
} else {
insuredAmnt = 0;
}
return insuredAmnt;
}
// Updates the regular bank account list & the fast-access bank account list
// NO values are allowed to be null. Manually update lists separately if that's the behaviour you want!
public void UpdateBankAccounts(String newHolderName, String newHolderUuid, String accountIdentifier, IndividualAccount newAccountOnly) {
// Update the fast-access map first
System.out.println("UpdateBankAccounts called with information " + newHolderName + " " + newHolderUuid + " " + accountIdentifier);
if (accounts.accountsListFromName.containsKey(newHolderUuid)) {
// Check if user is already in map
accounts.accountsListFromName.get(newHolderUuid).add(accountIdentifier);
} else {
// Add new entry to map
List<String> userAccountList = new ArrayList<String>(); // Lists are immutable; must make ArrayList
userAccountList.add(accountIdentifier);
accounts.accountsListFromName.put(newHolderUuid, userAccountList);
}
// Update regular account list
if (accounts.accountsList.containsKey(accountIdentifier)) {
// This path assumes we are adding a holder as opposed to adding an account (else, how else would this work?)
System.out.println("Account found in accounts list, adding this person as a holder instead");
accounts.accountsList.get(accountIdentifier).AddAccountHolder(newHolderName, newHolderUuid);
} else {
// Non-existent account means a new one!
System.out.println("Brand new account creation, adding!");
accounts.accountsList.put(accountIdentifier, newAccountOnly);
numberOfAccounts++;
}
System.out.println("Flashing configuration file");
FlashConfig("bank/" + routingNumber + "/accounts");
}
public Integer GetRoutingNumber() {
return this.routingNumber;
}
public void AddMoneyToAccount(String accountId, Integer amount) {
IndividualAccount account = accounts.accountsList.get(accountId);
System.out.println("Received account # " + accountId + " and money amnt " + amount);
if (account != null) {
account.Deposit(amount);
}
FlashConfig("bank/" + routingNumber + "/accounts");
}
public void SubtractMoneyFromAccount(String accountId, Integer amount) {
IndividualAccount account = accounts.accountsList.get(accountId);
for (Entry<String, IndividualAccount> debug : accounts.accountsList.entrySet()) {
System.out.println("ACCOUNT ID: " + debug.getKey());
System.out.println("ACCOUNT NUM: " + debug.getValue().GetAccountNumber());
}
if (account != null) {
account.Withdraw(amount);
}
FlashConfig("bank/" + routingNumber + "/accounts");
}
public void SetMoneyOnAccount(String accountId, Integer amount) {
IndividualAccount account = accounts.accountsList.get(accountId);
System.out.println("Is account null? " + (account == null ? "YES" : "NO"));
System.out.println("Received account # " + accountId + " and money amnt " + amount);
for (Entry<String, IndividualAccount> debug : accounts.accountsList.entrySet()) {
System.out.println("ACCOUNT ID: " + debug.getKey());
System.out.println("ACCOUNT NUM: " + debug.getValue().GetAccountNumber());
}
if (account != null) {
account.SetMoney(amount);
}
FlashConfig("bank/" + routingNumber + "/accounts");
}
public Boolean CreateAccount(String holderUuid, String holderName, String accountTypeStr) {
Boolean success = false;
System.out.println("Attempting to create new bank account given args UUID / NAME / TYPE : " + holderUuid + " " + holderName + " " + accountTypeStr);
System.out.println("accounts size is " + accounts.accountsList.size());
System.out.println("account string is { " + accountTypeStr + " }. Does this account type exist in this bank? => " + ACCOUNT_TYPES.containsKey(accountTypeStr.toLowerCase()));
if (accounts.accountsList.size() <= maxBankAccounts && ACCOUNT_TYPES.containsKey(accountTypeStr.toLowerCase())) {
// Verify this isn't a blacklisted user
System.out.println("Is user bank locked? " + lockedUsers.contains(holderName));
if (!lockedUsers.contains(holderName)) {
Integer maxAttempts = 15; // Reasonably a unique bank account should pop up within 1000 generations. If not, the user may try again.
String accountId = AccountNumberGenerator.GenerateNewAccountNumber(bankFourLetterIdentifier, routingNumber, ACCOUNT_TYPES.get(accountTypeStr), holderName);
System.out.println("Account generator came back with bank account id { " + accountId + " }");
System.out.println("4 letter bank: " + AccountNumberGenerator.GetFinancialSymbolFromId(accountId));
System.out.println("Routing: " + AccountNumberGenerator.GetRoutingNumberFromId(accountId));
System.out.println("Account type: " + AccountNumberGenerator.GetAccountTypeFromId(accountId));
System.out.println("RNG Account number: " + AccountNumberGenerator.GetAccountNumberFromId(accountId));
// TODO: Fix in future with a method that will guarentee a one-time necessary number generator. Statistically speaking; this will be okay for the
// entire life time of the server. BUT, you never know!
while (maxAttempts != 0 && !accounts.accountsList.containsKey(AccountNumberGenerator.GetAccountNumberFromId(accountId))) {
accountId = AccountNumberGenerator.GenerateNewAccountNumber(bankFourLetterIdentifier, routingNumber, ACCOUNT_TYPES.get(accountTypeStr), holderName);
System.out.println("Account generator came back with bank account id { " + accountId + " }");
maxAttempts--;
}
// Final check to add the account
String actualAccountNumber = AccountNumberGenerator.GetAccountNumberFromId(accountId);
System.out.println("Bank account identifier is { " + actualAccountNumber + " }. Is this already an existing account? " + accounts.accountsList.containsKey(actualAccountNumber));
if (!accounts.accountsList.containsKey(actualAccountNumber)) {
IndividualAccount newAccount = new IndividualAccount(actualAccountNumber, this.routingNumber, List.of(holderName),
List.of(holderUuid), false, 0,
"", ACCOUNT_TYPES.get(accountTypeStr), bankFourLetterIdentifier);
System.out.println("Updating accounts list for this bank");
UpdateBankAccounts(holderName, holderUuid, actualAccountNumber, newAccount);
success = true;
}
}
}
return success;
}
public void AliasAccount(String accountId, String newAlias) {
String accountNumber = AccountNumberGenerator.GetAccountNumberFromId(accountId);
if (accounts.accountsList.containsKey(accountNumber)) {
accounts.accountsList.get(accountNumber).AliasAccount(newAlias);
}
}
public Boolean LockAccountHolder(String holderName) {
Boolean success = false;
Integer accountIter = 0;
for (Entry<String, List<String>> holderAccounts : accounts.accountsListFromName.entrySet()) {
accounts.accountsList.get(holderAccounts.getValue().get(accountIter++)).LockAccount();
}
return success;
}
public Boolean CloseAccount(String accountId) {
Boolean success = false;
String accountNumber = AccountNumberGenerator.GetAccountNumberFromId(accountId);
if (accounts.accountsList.get(accountNumber).GetAccountBalance() == 0) {
accounts.accountsList.remove(accountNumber);
success = true;
}
return success;
}
public Boolean HasAccount(String accountIdentifier) {
Boolean containsAccount = false;
if (accounts.accountsList.containsKey(accountIdentifier)) {
containsAccount = true;
}
return containsAccount;
}
public Boolean IsValidWithdrawal(Integer withdrawalAmount, String accountIdentifier) {
Boolean isValid = false;
if (accounts.accountsList.containsKey(accountIdentifier)) {
IndividualAccount account = accounts.accountsList.get(accountIdentifier);
if (account.CanWithdraw(withdrawalAmount)) {
isValid = true;
}
}
return isValid;
}
public Boolean IsAccountHolder(String accountIdentifier, String uuid) {
Boolean isHolder = false;
// Verify account exists first
if (accounts.accountsList.containsKey(accountIdentifier)) {
isHolder = accounts.accountsList.get(accountIdentifier).IsHolder(uuid);
}
return isHolder;
}
/////////////////////////////////////////////////////////////////////////////
/// @fn FlashConfig
///
/// @brief Flashes the config to the disk
///
/// @note dirName should be the relative full path to dir
/// @note Function will be removed in near future for SQL but is
/// expensive to run as it flashes everything even if un-updated
/////////////////////////////////////////////////////////////////////////////
public void FlashConfig(String dirName) {
for (Entry<String, IndividualAccount> singleAccount : accounts.accountsList.entrySet()) {
// Iterate over each one & verify if a file exists inside the dir. if it does;
// nuke it and
// replace it with the new contents in memory
String accountNum = singleAccount.getKey().toString();
// delete file
File file = new File(dirName + "/" + accountNum + ".json");
if (file.exists()) {
file.delete();
}
// Re-flash file
try {
config.WriteToJsonFile(dirName + "/" + accountNum + ".json", singleAccount.getValue());
} catch (FILE_WRITE_EXCEPTION e) {
System.out.println("Failed to flash configuration file properly. Printing stack trace");
e.printStackTrace();
}
}
}
}