/*
 * 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.impl.economy.account;

import io.github.flibio.economylite.CauseFactory;
import io.github.flibio.economylite.EconomyLite;
import io.github.flibio.economylite.api.CurrencyEconService;
import io.github.flibio.economylite.api.VirtualEconService;
import io.github.flibio.economylite.impl.economy.event.LiteEconomyTransactionEvent;
import io.github.flibio.economylite.impl.economy.result.LiteTransactionResult;
import io.github.flibio.economylite.impl.economy.result.LiteTransferResult;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.economy.Currency;
import org.spongepowered.api.service.economy.account.Account;
import org.spongepowered.api.service.economy.account.VirtualAccount;
import org.spongepowered.api.service.economy.transaction.ResultType;
import org.spongepowered.api.service.economy.transaction.TransactionResult;
import org.spongepowered.api.service.economy.transaction.TransactionType;
import org.spongepowered.api.service.economy.transaction.TransactionTypes;
import org.spongepowered.api.service.economy.transaction.TransferResult;
import org.spongepowered.api.text.Text;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public class LiteVirtualAccount implements VirtualAccount {

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

    private String name;

    public LiteVirtualAccount(String id) {
        this.name = id;
    }

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

    @Override
    public BigDecimal getDefaultBalance(Currency currency) {
        Optional<Double> bOpt = EconomyLite.getConfigManager().getValue(Double.class, "default-balance", "virtual");
        if (bOpt.isPresent()) {
            return BigDecimal.valueOf(bOpt.get());
        } else {
            return BigDecimal.ZERO;
        }
    }

    @Override
    public boolean hasBalance(Currency currency, Set<Context> contexts) {
        return virtualService.accountExists(name, currency, CauseFactory.create("Has Balance"));
    }

    @Override
    public BigDecimal getBalance(Currency currency, Set<Context> contexts) {
        if (!hasBalance(currency, contexts)) {
            virtualService.setBalance(name, getDefaultBalance(currency), currency, CauseFactory.create("New Account"));
        }
        return virtualService.getBalance(name, currency, CauseFactory.create("Get Balance"));
    }

    @Override
    public Map<Currency, BigDecimal> getBalances(Set<Context> contexts) {
        HashMap<Currency, BigDecimal> balances = new HashMap<Currency, BigDecimal>();
        for (Currency currency : currencyService.getCurrencies()) {
            if (virtualService.accountExists(name, currency, CauseFactory.create("Get Balances"))) {
                balances.put(currency, virtualService.getBalance(name, currency, CauseFactory.create("Get Balances Put")));
            }
        }
        return balances;
    }

    @Override
    public TransactionResult setBalance(Currency currency, BigDecimal amount, Cause cause, Set<Context> contexts) {
        // Check if the new balance is in bounds
        if (amount.compareTo(BigDecimal.ZERO) == -1 || amount.compareTo(BigDecimal.valueOf(999999999)) == 1) {
            return resultAndEvent(this, amount, currency, ResultType.ACCOUNT_NO_SPACE, TransactionTypes.DEPOSIT, cause);
        }
        if (virtualService.setBalance(name, amount, currency, cause)) {
            return resultAndEvent(this, amount, currency, ResultType.SUCCESS, TransactionTypes.DEPOSIT, cause);
        } else {
            return resultAndEvent(this, amount, currency, ResultType.FAILED, TransactionTypes.DEPOSIT, cause);
        }
    }

    @Override
    public Map<Currency, TransactionResult> resetBalances(Cause cause, Set<Context> contexts) {
        HashMap<Currency, TransactionResult> results = new HashMap<>();
        for (Currency currency : currencyService.getCurrencies()) {
            if (virtualService.accountExists(name, currency, cause)) {
                if (virtualService.setBalance(name, getDefaultBalance(currency), currency, cause)) {
                    results.put(currency, resultAndEvent(this, getBalance(currency), currency, ResultType.SUCCESS, TransactionTypes.WITHDRAW, cause));
                } else {
                    results.put(currency, resultAndEvent(this, getBalance(currency), currency, ResultType.FAILED, TransactionTypes.WITHDRAW, cause));
                }
            }
        }
        return results;
    }

    @Override
    public TransactionResult resetBalance(Currency currency, Cause cause, Set<Context> contexts) {
        if (virtualService.setBalance(name, getDefaultBalance(currency), currency, cause)) {
            return resultAndEvent(this, BigDecimal.ZERO, currency, ResultType.SUCCESS, TransactionTypes.WITHDRAW, cause);
        } else {
            return resultAndEvent(this, BigDecimal.ZERO, currency, ResultType.FAILED, TransactionTypes.WITHDRAW, cause);
        }
    }

    @Override
    public TransactionResult deposit(Currency currency, BigDecimal amount, Cause cause, Set<Context> contexts) {
        BigDecimal newBal = getBalance(currency).add(amount);
        // Check if the new balance is in bounds
        if (newBal.compareTo(BigDecimal.ZERO) == -1 || newBal.compareTo(BigDecimal.valueOf(999999999)) == 1) {
            return resultAndEvent(this, amount, currency, ResultType.ACCOUNT_NO_SPACE, TransactionTypes.DEPOSIT, cause);
        }
        if (virtualService.deposit(name, amount, currency, cause)) {
            return resultAndEvent(this, amount, currency, ResultType.SUCCESS, TransactionTypes.DEPOSIT, cause);
        } else {
            return resultAndEvent(this, amount, currency, ResultType.FAILED, TransactionTypes.DEPOSIT, cause);
        }
    }

    @Override
    public TransactionResult withdraw(Currency currency, BigDecimal amount, Cause cause, Set<Context> contexts) {
        BigDecimal newBal = getBalance(currency).subtract(amount);
        // Check if the new balance is in bounds
        if (newBal.compareTo(BigDecimal.ZERO) == -1) {
            return resultAndEvent(this, amount, currency, ResultType.ACCOUNT_NO_FUNDS, TransactionTypes.WITHDRAW, cause);
        }
        if (newBal.compareTo(BigDecimal.valueOf(999999999)) == 1) {
            return resultAndEvent(this, amount, currency, ResultType.ACCOUNT_NO_SPACE, TransactionTypes.WITHDRAW, cause);
        }
        if (virtualService.withdraw(name, amount, currency, cause)) {
            return resultAndEvent(this, amount, currency, ResultType.SUCCESS, TransactionTypes.WITHDRAW, cause);
        } else {
            return resultAndEvent(this, amount, currency, ResultType.FAILED, TransactionTypes.WITHDRAW, cause);
        }
    }

    @Override
    public TransferResult transfer(Account to, Currency currency, BigDecimal amount, Cause cause, Set<Context> contexts) {
        BigDecimal newBal = to.getBalance(currency).add(amount);
        // Check if the new balance is in bounds
        if (newBal.compareTo(BigDecimal.ZERO) == -1 || newBal.compareTo(BigDecimal.valueOf(999999999)) == 1) {
            return resultAndEvent(this, amount, currency, ResultType.ACCOUNT_NO_SPACE, to, cause);
        }
        // Check if the account has enough funds
        if (amount.compareTo(getBalance(currency)) == 1) {
            return resultAndEvent(this, amount, currency, ResultType.ACCOUNT_NO_FUNDS, to, cause);
        }
        if (withdraw(currency, amount, cause).getResult().equals(ResultType.SUCCESS)
                && to.deposit(currency, amount, cause).getResult().equals(ResultType.SUCCESS)) {
            return resultAndEvent(this, amount, currency, ResultType.SUCCESS, to, cause);
        } else {
            return resultAndEvent(this, amount, currency, ResultType.FAILED, to, cause);
        }
    }

    @Override
    public String getIdentifier() {
        return name;
    }

    @Override
    public Set<Context> getActiveContexts() {
        return new HashSet<Context>();
    }

    private TransactionResult resultAndEvent(Account account, BigDecimal amount, Currency currency, ResultType resultType,
            TransactionType transactionType, Cause cause) {
        TransactionResult result = new LiteTransactionResult(account, amount, currency, resultType, transactionType);
        Sponge.getEventManager().post(new LiteEconomyTransactionEvent(result, cause));
        return result;
    }

    private TransferResult resultAndEvent(Account account, BigDecimal amount, Currency currency, ResultType resultType, Account toWho, Cause cause) {
        TransferResult result = new LiteTransferResult(account, amount, currency, resultType, toWho);
        Sponge.getEventManager().post(new LiteEconomyTransactionEvent(result, cause));
        return result;
    }

}