MoneyWiseAnalysisAccountBucket.java

/*
 * MoneyWise: Finance Application
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusDecimal;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
import io.github.tonywasher.joceanus.metis.data.MetisDataFieldValue;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldTableItem;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.metis.list.MetisListIndexed;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataSet;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseExchangeRate.MoneyWiseExchangeRateDataMap;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.base.MoneyWiseAnalysisBaseResource;
import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.base.MoneyWiseAnalysisHistory;
import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.values.MoneyWiseAnalysisAccountAttr;
import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.values.MoneyWiseAnalysisAccountValues;

import java.util.Currency;
import java.util.Iterator;
import java.util.List;

/**
 * The Account Bucket class.
 *
 * @param <T> the account data type
 */
public abstract class MoneyWiseAnalysisAccountBucket<T extends MoneyWiseAssetBase>
        implements MetisFieldTableItem {
    /**
     * Default currency.
     */
    protected static final Currency DEFAULT_CURRENCY = OceanusMoney.getDefaultCurrency();

    /**
     * Report fields.
     */
    @SuppressWarnings("rawtypes")
    private static final MetisFieldSet<MoneyWiseAnalysisAccountBucket> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseAnalysisAccountBucket.class);

    /*
     * Declare Fields.
     */
    static {
        FIELD_DEFS.declareLocalField(MoneyWiseAnalysisDataResource.ANALYSIS_NAME, MoneyWiseAnalysisAccountBucket::getAnalysis);
        FIELD_DEFS.declareLocalField(MoneyWiseAnalysisDataResource.BUCKET_ACCOUNT, MoneyWiseAnalysisAccountBucket::getAccount);
        FIELD_DEFS.declareLocalField(MoneyWiseAnalysisDataResource.BUCKET_BASEVALUES, MoneyWiseAnalysisAccountBucket::getBaseValues);
        FIELD_DEFS.declareLocalField(MoneyWiseAnalysisBaseResource.BUCKET_HISTORY, MoneyWiseAnalysisAccountBucket::getHistoryMap);
        FIELD_DEFS.declareLocalFieldsForEnum(MoneyWiseAnalysisAccountAttr.class, MoneyWiseAnalysisAccountBucket::getAttributeValue);
    }

    /**
     * Totals bucket name.
     */
    private static final String NAME_TOTALS = MoneyWiseAnalysisDataResource.ANALYSIS_TOTALS.getValue();

    /**
     * The analysis.
     */
    private final MoneyWiseAnalysis theAnalysis;

    /**
     * The account.
     */
    private final T theAccount;

    /**
     * Is this a foreign currency?
     */
    private final Boolean isForeignCurrency;

    /**
     * Values.
     */
    private final MoneyWiseAnalysisAccountValues theValues;

    /**
     * The base values.
     */
    private final MoneyWiseAnalysisAccountValues theBaseValues;

    /**
     * History Map.
     */
    private final MoneyWiseAnalysisHistory<MoneyWiseAnalysisAccountValues, MoneyWiseAnalysisAccountAttr> theHistory;

    /**
     * Constructor.
     *
     * @param pAnalysis the analysis
     * @param pAccount  the account
     */
    protected MoneyWiseAnalysisAccountBucket(final MoneyWiseAnalysis pAnalysis,
                                             final T pAccount) {
        /* Store the details */
        theAccount = pAccount;
        theAnalysis = pAnalysis;

        /* Determine currency */
        final MoneyWiseCurrency myReportingCurrency = pAnalysis.getCurrency();
        final MoneyWiseCurrency myAccountCurrency = pAccount == null
                ? myReportingCurrency
                : pAccount.getAssetCurrency();

        /* Determine whether we are a foreign currency */
        isForeignCurrency = !MetisDataDifference.isEqual(myReportingCurrency, myAccountCurrency);
        final Currency myCurrency = deriveCurrency(myAccountCurrency);
        final Currency myRepCurrency = deriveCurrency(myReportingCurrency);

        /* Create the history map */
        final MoneyWiseAnalysisAccountValues myValues = Boolean.TRUE.equals(isForeignCurrency)
                ? allocateForeignValues(myCurrency, myRepCurrency)
                : allocateStandardValues(myCurrency);
        theHistory = new MoneyWiseAnalysisHistory<>(myValues);

        /* Access the key value maps */
        theValues = theHistory.getValues();
        theBaseValues = theHistory.getBaseValues();
    }

    /**
     * Constructor.
     *
     * @param pAnalysis the analysis
     * @param pBase     the underlying bucket
     */
    protected MoneyWiseAnalysisAccountBucket(final MoneyWiseAnalysis pAnalysis,
                                             final MoneyWiseAnalysisAccountBucket<T> pBase) {
        /* Copy details from base */
        theAccount = pBase.getAccount();
        theAnalysis = pAnalysis;
        isForeignCurrency = pBase.isForeignCurrency();

        /* Access the relevant history */
        theHistory = new MoneyWiseAnalysisHistory<>(pBase.getHistoryMap());

        /* Access the key value maps */
        theValues = theHistory.getValues();
        theBaseValues = theHistory.getBaseValues();
    }

    /**
     * Constructor.
     *
     * @param pAnalysis the analysis
     * @param pBase     the underlying bucket
     * @param pDate     the date for the bucket
     */
    protected MoneyWiseAnalysisAccountBucket(final MoneyWiseAnalysis pAnalysis,
                                             final MoneyWiseAnalysisAccountBucket<T> pBase,
                                             final OceanusDate pDate) {
        /* Copy details from base */
        theAccount = pBase.getAccount();
        theAnalysis = pAnalysis;
        isForeignCurrency = pBase.isForeignCurrency();

        /* Access the relevant history */
        theHistory = new MoneyWiseAnalysisHistory<>(pBase.getHistoryMap(), pDate);

        /* Access the key value maps */
        theValues = theHistory.getValues();
        theBaseValues = theHistory.getBaseValues();
    }

    /**
     * Constructor.
     *
     * @param pAnalysis the analysis
     * @param pBase     the underlying bucket
     * @param pRange    the range for the bucket
     */
    protected MoneyWiseAnalysisAccountBucket(final MoneyWiseAnalysis pAnalysis,
                                             final MoneyWiseAnalysisAccountBucket<T> pBase,
                                             final OceanusDateRange pRange) {
        /* Copy details from base */
        theAccount = pBase.getAccount();
        theAnalysis = pAnalysis;
        isForeignCurrency = pBase.isForeignCurrency();

        /* Access the relevant history */
        theHistory = new MoneyWiseAnalysisHistory<>(pBase.getHistoryMap(), pRange);

        /* Access the key value maps */
        theValues = theHistory.getValues();
        theBaseValues = theHistory.getBaseValues();
    }

    @Override
    public String formatObject(final OceanusDataFormatter pFormatter) {
        return toString();
    }

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

    /**
     * derive currency.
     *
     * @param pAssetCurrency the asset currency
     * @return the actual currency to use
     */
    protected static Currency deriveCurrency(final MoneyWiseCurrency pAssetCurrency) {
        return pAssetCurrency == null
                ? DEFAULT_CURRENCY
                : pAssetCurrency.getCurrency();
    }

    /**
     * allocate standard values.
     *
     * @param pCurrency the asset currency
     * @return the actual currency to use
     */
    protected MoneyWiseAnalysisAccountValues allocateStandardValues(final Currency pCurrency) {
        return new MoneyWiseAnalysisAccountValues(pCurrency);
    }

    /**
     * allocate foreign values.
     *
     * @param pCurrency          the asset currency
     * @param pReportingCurrency the reporting currency
     * @return the actual currency to use
     */
    protected MoneyWiseAnalysisAccountValues allocateForeignValues(final Currency pCurrency,
                                                                   final Currency pReportingCurrency) {
        return new MoneyWiseAnalysisAccountValues(pCurrency, pReportingCurrency);
    }

    /**
     * Obtain the name.
     *
     * @return the name
     */
    public String getName() {
        return theAccount == null
                ? NAME_TOTALS
                : theAccount.getName();
    }

    /**
     * Obtain the account.
     *
     * @return the account
     */
    public T getAccount() {
        return theAccount;
    }

    /**
     * Is this a foreign currency?
     *
     * @return true/false
     */
    public Boolean isForeignCurrency() {
        return isForeignCurrency;
    }

    @Override
    public Integer getIndexedId() {
        return theAccount.getIndexedId();
    }

    /**
     * Is this bucket idle?
     *
     * @return true/false
     */
    public Boolean isIdle() {
        return theHistory.isIdle();
    }

    /**
     * Obtain the analysis.
     *
     * @return the analysis
     */
    protected MoneyWiseAnalysis getAnalysis() {
        return theAnalysis;
    }

    /**
     * Obtain date range.
     *
     * @return the range
     */
    public OceanusDateRange getDateRange() {
        return theAnalysis.getDateRange();
    }

    /**
     * Obtain the value map.
     *
     * @return the value map
     */
    public MoneyWiseAnalysisAccountValues getValues() {
        return theValues;
    }

    /**
     * Obtain the base value map.
     *
     * @return the base value map
     */
    public MoneyWiseAnalysisAccountValues getBaseValues() {
        return theBaseValues;
    }

    /**
     * Obtain values for transaction.
     *
     * @param pTrans the transaction
     * @return the values (or null)
     */
    public MoneyWiseAnalysisAccountValues getValuesForTransaction(final MoneyWiseTransaction pTrans) {
        return theHistory.getValuesForTransaction(pTrans);
    }

    /**
     * Obtain previous values for transaction.
     *
     * @param pTrans the transaction
     * @return the values (or null)
     */
    public MoneyWiseAnalysisAccountValues getPreviousValuesForTransaction(final MoneyWiseTransaction pTrans) {
        return theHistory.getPreviousValuesForTransaction(pTrans);
    }

    /**
     * Obtain delta for transaction.
     *
     * @param pTrans the transaction
     * @param pAttr  the attribute
     * @return the delta (or null)
     */
    public OceanusDecimal getDeltaForTransaction(final MoneyWiseTransaction pTrans,
                                                 final MoneyWiseAnalysisAccountAttr pAttr) {
        /* Obtain delta for transaction */
        return theHistory.getDeltaValue(pTrans, pAttr);
    }

    /**
     * Obtain money delta for transaction.
     *
     * @param pTrans the transaction
     * @param pAttr  the attribute
     * @return the delta (or null)
     */
    public OceanusMoney getMoneyDeltaForTransaction(final MoneyWiseTransaction pTrans,
                                                    final MoneyWiseAnalysisAccountAttr pAttr) {
        /* Obtain delta for transaction */
        return theHistory.getDeltaMoneyValue(pTrans, pAttr);
    }

    /**
     * Obtain the history map.
     *
     * @return the history map
     */
    private MoneyWiseAnalysisHistory<MoneyWiseAnalysisAccountValues, MoneyWiseAnalysisAccountAttr> getHistoryMap() {
        return theHistory;
    }

    /**
     * Set Value.
     *
     * @param pAttr  the attribute
     * @param pValue the value of the attribute
     */
    protected void setValue(final MoneyWiseAnalysisAccountAttr pAttr,
                            final Object pValue) {
        /* Set the value */
        theValues.setValue(pAttr, pValue);
    }

    /**
     * Get an attribute value.
     *
     * @param pAttr the attribute
     * @return the value to set
     */
    private Object getAttributeValue(final MoneyWiseAnalysisAccountAttr pAttr) {
        /* Access value of object */
        final Object myValue = getValue(pAttr);

        /* Return the value */
        return myValue != null
                ? myValue
                : MetisDataFieldValue.SKIP;
    }

    /**
     * Obtain an attribute value.
     *
     * @param pAttr the attribute
     * @return the value of the attribute or null
     */
    private Object getValue(final MoneyWiseAnalysisAccountAttr pAttr) {
        /* Obtain the attribute value */
        return theValues.getValue(pAttr);
    }

    /**
     * Adjust counter.
     *
     * @param pAttr  the attribute
     * @param pDelta the delta
     */
    protected void adjustCounter(final MoneyWiseAnalysisAccountAttr pAttr,
                                 final OceanusMoney pDelta) {
        OceanusMoney myValue = theValues.getMoneyValue(pAttr);
        myValue = new OceanusMoney(myValue);
        myValue.addAmount(pDelta);
        setValue(pAttr, myValue);
    }

    /**
     * Adjust account for debit.
     *
     * @param pHelper the transaction helper
     */
    public void adjustForDebit(final MoneyWiseAnalysisTransactionHelper pHelper) {
        /* Access event amount */
        OceanusMoney myAmount = pHelper.getDebitAmount();

        /* If we have a non-zero amount */
        if (myAmount.isNonZero()) {
            /* Adjust valuation */
            myAmount = new OceanusMoney(myAmount);
            myAmount.negate();

            /* If we are a foreign account */
            if (Boolean.TRUE.equals(isForeignCurrency)) {
                /* Access local amount amount */
                final OceanusMoney myLocalAmount = pHelper.getLocalAmount();

                /* Adjust counters */
                adjustCounter(MoneyWiseAnalysisAccountAttr.FOREIGNVALUE, myAmount);
                adjustCounter(MoneyWiseAnalysisAccountAttr.LOCALVALUE, myLocalAmount);

                /* Obtain the debit exchangeRate and convert the foreign valuation */
                final OceanusRatio myRate = pHelper.getDebitExchangeRate();
                final OceanusMoney myLocalValue = myAmount.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate);

                /* Set the valuation */
                setValue(MoneyWiseAnalysisAccountAttr.VALUATION, myLocalValue);
                setValue(MoneyWiseAnalysisAccountAttr.EXCHANGERATE, myRate);

                /* Determine currency fluctuation */
                final OceanusMoney myFluct = new OceanusMoney(myLocalValue);
                myFluct.subtractAmount(theValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.LOCALVALUE));
                adjustCounter(MoneyWiseAnalysisAccountAttr.CURRENCYFLUCT, myFluct);

                /* else this is a standard account */
            } else {
                /* Adjust valuation */
                adjustCounter(MoneyWiseAnalysisAccountAttr.VALUATION, myAmount);
            }
        }

        /* Register the transaction in the history */
        registerTransaction(pHelper);
    }

    /**
     * Adjust account for credit. TODO rework
     *
     * @param pHelper the transaction helper
     */
    public void adjustForCredit(final MoneyWiseAnalysisTransactionHelper pHelper) {
        /* Access event amount */
        final OceanusMoney myAmount = pHelper.getCreditAmount();

        /* If we have a non-zero amount */
        if (myAmount.isNonZero()) {
            /* If we are a foreign account */
            if (Boolean.TRUE.equals(isForeignCurrency)) {
                /* Access local amount */
                final OceanusMoney myLocalAmount = pHelper.getLocalAmount();

                /* Adjust counters */
                adjustCounter(MoneyWiseAnalysisAccountAttr.FOREIGNVALUE, myAmount);
                adjustCounter(MoneyWiseAnalysisAccountAttr.LOCALVALUE, myLocalAmount);

                /* Obtain the credit exchangeRate and convert the foreign valuation */
                final OceanusRatio myRate = pHelper.getCreditExchangeRate();
                final OceanusMoney myLocalValue = myAmount.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate);

                /* Set the valuation */
                setValue(MoneyWiseAnalysisAccountAttr.VALUATION, myLocalValue);
                setValue(MoneyWiseAnalysisAccountAttr.EXCHANGERATE, myRate);

                /* Determine currency fluctuation */
                final OceanusMoney myFluct = new OceanusMoney(myLocalValue);
                myFluct.subtractAmount(theValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.LOCALVALUE));
                setValue(MoneyWiseAnalysisAccountAttr.CURRENCYFLUCT, myFluct);

                /* else this is a standard account */
            } else {
                /* Adjust valuation */
                adjustCounter(MoneyWiseAnalysisAccountAttr.VALUATION, myAmount);
            }
        }

        /* Register the transaction in the history */
        registerTransaction(pHelper);
    }

    /**
     * Adjust account for credit.
     *
     * @param pHelper the transaction helper
     */
    public void adjustForReturnedCashCredit(final MoneyWiseAnalysisTransactionHelper pHelper) {
        /* Access event amount */
        final OceanusMoney myAmount = pHelper.getReturnedCash();

        /* If we have a non-zero amount */
        if (myAmount.isNonZero()) {
            /* If we are a foreign account */
            if (Boolean.TRUE.equals(isForeignCurrency)) {
                /* Access local amount */
                final OceanusMoney myLocalAmount = pHelper.getLocalReturnedCash();

                /* Adjust counters */
                adjustCounter(MoneyWiseAnalysisAccountAttr.FOREIGNVALUE, myAmount);
                adjustCounter(MoneyWiseAnalysisAccountAttr.LOCALVALUE, myLocalAmount);

                /* Obtain the credit exchangeRate and convert the foreign valuation */
                final OceanusRatio myRate = pHelper.getReturnedCashExchangeRate();
                final OceanusMoney myLocalValue = myAmount.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate);

                /* Set the valuation */
                setValue(MoneyWiseAnalysisAccountAttr.VALUATION, myLocalValue);
                setValue(MoneyWiseAnalysisAccountAttr.EXCHANGERATE, myRate);

                /* Determine currency fluctuation */
                final OceanusMoney myFluct = new OceanusMoney(myLocalValue);
                myFluct.subtractAmount(theValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.LOCALVALUE));
                setValue(MoneyWiseAnalysisAccountAttr.CURRENCYFLUCT, myFluct);

                /* else this is a standard account */
            } else {
                /* Adjust valuation */
                adjustCounter(MoneyWiseAnalysisAccountAttr.VALUATION, myAmount);
            }
        }

        /* Register the transaction in the history */
        registerTransaction(pHelper);
    }

    /**
     * Set opening balance.
     *
     * @param pHelper  the transaction helper
     * @param pBalance the opening balance
     */
    protected void setOpeningBalance(final MoneyWiseAnalysisTransactionHelper pHelper,
                                     final OceanusMoney pBalance) {
        /* Obtain the base valuation */
        final MoneyWiseAnalysisAccountValues myValues = getBaseValues();
        final OceanusMoney myBaseValue = myValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.VALUATION);

        /* If we are a foreign account */
        if (Boolean.TRUE.equals(isForeignCurrency)) {
            /* Obtain the foreign valuation */
            final OceanusMoney myForeignValue = myValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.FOREIGNVALUE);
            final OceanusMoney myLocalValue = myValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.LOCALVALUE);

            /* Obtain exchange rate and reporting value */
            final OceanusRatio myRate = pHelper.getExchangeRate(theAccount.getAssetCurrency(), theAnalysis.getData().getDateRange().getStart());
            final OceanusMoney myLocalAmount = pBalance.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate);

            /* Record details */
            myBaseValue.addAmount(myLocalAmount);
            myLocalValue.addAmount(myLocalAmount);
            myForeignValue.addAmount(pBalance);
            myValues.setValue(MoneyWiseAnalysisAccountAttr.EXCHANGERATE, myRate);

            /* else this is a standard account */
        } else {
            /* Set the base value (this will set the current value as well) */
            myBaseValue.addAmount(pBalance);
        }
    }

    /**
     * Register the transaction.
     *
     * @param pHelper the transaction helper
     */
    public void registerTransaction(final MoneyWiseAnalysisTransactionHelper pHelper) {
        /* Register the transaction in the history */
        theHistory.registerTransaction(pHelper.getTransaction(), theValues);
    }

    /**
     * Register the transaction.
     *
     * @param pTrans the transaction
     */
    public void registerTransaction(final MoneyWiseTransaction pTrans) {
        /* Register the transaction in the history */
        theHistory.registerTransaction(pTrans, theValues);
    }

    /**
     * calculate currency fluctuations over the range.
     *
     * @param pRange the range of valuation
     */
    protected void calculateFluctuations(final OceanusDateRange pRange) {
        /* Obtain the appropriate rates */
        final MoneyWiseDataSet myData = theAnalysis.getData();
        final MoneyWiseExchangeRateDataMap myRateMap = myData.getExchangeRateDataMap();
        final OceanusRatio[] myRates = myRateMap.getRatesForRange(theAccount.getAssetCurrency(), pRange);
        final Currency myBaseCurrency = theAnalysis.getCurrency().getCurrency();

        /* Access the base value */
        OceanusRatio myRate = myRates[0];
        OceanusMoney myForeignValue = theBaseValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.FOREIGNVALUE);
        OceanusMoney myLocalValue = theBaseValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.LOCALVALUE);

        /* Calculate the base value */
        OceanusMoney myLocalValuation = myForeignValue.convertCurrency(myBaseCurrency, myRate);
        theBaseValues.setValue(MoneyWiseAnalysisAccountAttr.EXCHANGERATE, myRate);
        theBaseValues.setValue(MoneyWiseAnalysisAccountAttr.VALUATION, myLocalValuation);

        /* Determine currency fluctuation */
        OceanusMoney myFluct = new OceanusMoney(myLocalValuation);
        myFluct.subtractAmount(myLocalValue);
        theBaseValues.setValue(MoneyWiseAnalysisAccountAttr.CURRENCYFLUCT, myFluct);

        /* Access current values */
        myRate = myRates[1];
        myForeignValue = theValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.FOREIGNVALUE);
        myLocalValue = theValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.LOCALVALUE);

        /* Calculate the current value */
        myLocalValuation = myForeignValue.convertCurrency(myBaseCurrency, myRate);
        theValues.setValue(MoneyWiseAnalysisAccountAttr.EXCHANGERATE, myRate);
        theValues.setValue(MoneyWiseAnalysisAccountAttr.VALUATION, myLocalValuation);

        /* Determine currency fluctuation */
        myFluct = new OceanusMoney(myLocalValuation);
        myFluct.subtractAmount(myLocalValue);
        theValues.setValue(MoneyWiseAnalysisAccountAttr.CURRENCYFLUCT, myFluct);
    }

    /**
     * Calculate delta.
     */
    protected void calculateDelta() {
        /* Obtain a copy of the value */
        OceanusMoney myDelta = theValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.VALUATION);
        myDelta = new OceanusMoney(myDelta);

        /* Subtract any base value */
        final OceanusMoney myBase = theBaseValues.getMoneyValue(MoneyWiseAnalysisAccountAttr.VALUATION);
        myDelta.subtractAmount(myBase);

        /* Set the delta */
        setValue(MoneyWiseAnalysisAccountAttr.VALUEDELTA, myDelta);

        /* Adjust to base values */
        theValues.adjustToBaseValues(theBaseValues);
        theBaseValues.resetBaseValues();
    }

    /**
     * Is the bucket active?
     *
     * @return true/false
     */
    public boolean isActive() {
        return theValues.isActive();
    }

    /**
     * record the rate of the account at a given date.
     *
     * @param pDate the date of valuation
     */
    protected void recordRate(final OceanusDate pDate) {
    }

    /**
     * AccountBucket list class.
     *
     * @param <B> the account bucket data type
     * @param <T> the account data type
     */
    public abstract static class MoneyWiseAnalysisAccountBucketList<B extends MoneyWiseAnalysisAccountBucket<T>, T extends MoneyWiseAssetBase>
            implements MetisFieldItem, MetisDataList<B> {
        /**
         * Local Report fields.
         */
        @SuppressWarnings("rawtypes")
        private static final MetisFieldSet<MoneyWiseAnalysisAccountBucketList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseAnalysisAccountBucketList.class);

        /*
         * Field IDs.
         */
        static {
            FIELD_DEFS.declareLocalField(MoneyWiseAnalysisDataResource.ANALYSIS_NAME, MoneyWiseAnalysisAccountBucketList::getAnalysis);
        }

        /**
         * The analysis.
         */
        private final MoneyWiseAnalysis theAnalysis;

        /**
         * The list.
         */
        private final MetisListIndexed<B> theList;

        /**
         * Construct a top-level List.
         *
         * @param pAnalysis the analysis
         */
        protected MoneyWiseAnalysisAccountBucketList(final MoneyWiseAnalysis pAnalysis) {
            /* Initialise class */
            theAnalysis = pAnalysis;
            theList = new MetisListIndexed<>();
            theList.setComparator((l, r) -> l.getAccount().compareTo(r.getAccount()));
        }

        @Override
        public List<B> getUnderlyingList() {
            return theList.getUnderlyingList();
        }

        @Override
        public String formatObject(final OceanusDataFormatter pFormatter) {
            return getDataFieldSet().getName();
        }

        /**
         * Obtain the analysis.
         *
         * @return the analysis
         */
        protected MoneyWiseAnalysis getAnalysis() {
            return theAnalysis;
        }

        /**
         * Construct a view List.
         *
         * @param pBase the base list
         */
        protected void constructFromBase(final MoneyWiseAnalysisAccountBucketList<B, T> pBase) {
            /* Loop through the buckets */
            final Iterator<B> myIterator = pBase.iterator();
            while (myIterator.hasNext()) {
                final B myCurr = myIterator.next();

                /* Access the bucket */
                final B myBucket = newBucket(myCurr);

                /* If the bucket is non-idle or active */
                if (myBucket.isActive() || Boolean.TRUE.equals(!myBucket.isIdle())) {
                    /* add to list */
                    theList.add(myBucket);
                }
            }
        }

        /**
         * Construct a view bucket.
         *
         * @param pBase the base bucket
         * @return the new bucket
         */
        protected abstract B newBucket(B pBase);

        /**
         * Construct a dated List.
         *
         * @param pBase the base list
         * @param pDate the Date
         */
        protected void constructFromBase(final MoneyWiseAnalysisAccountBucketList<B, T> pBase,
                                         final OceanusDate pDate) {
            /* Loop through the buckets */
            final Iterator<B> myIterator = pBase.iterator();
            while (myIterator.hasNext()) {
                final B myCurr = myIterator.next();

                /* Access the bucket for this date */
                final B myBucket = newBucket(myCurr, pDate);

                /* If the bucket is non-idle or active */
                if (myBucket.isActive() || Boolean.TRUE.equals(!myBucket.isIdle())) {
                    /* Record the rate (if required) and add to list */
                    myBucket.recordRate(pDate);
                    theList.add(myBucket);
                }
            }
        }

        /**
         * Construct a dated bucket.
         *
         * @param pBase the base bucket
         * @param pDate the Date
         * @return the new bucket
         */
        protected abstract B newBucket(B pBase,
                                       OceanusDate pDate);

        /**
         * Construct a ranged List.
         *
         * @param pBase  the base list
         * @param pRange the Date Range
         */
        protected void constructFromBase(final MoneyWiseAnalysisAccountBucketList<B, T> pBase,
                                         final OceanusDateRange pRange) {
            /* Loop through the buckets */
            final Iterator<B> myIterator = pBase.iterator();
            while (myIterator.hasNext()) {
                final B myCurr = myIterator.next();

                /* Access the bucket for this range */
                final B myBucket = newBucket(myCurr, pRange);

                /* If the bucket is non-idle or active */
                if (myBucket.isActive()
                        || Boolean.TRUE.equals(!myBucket.isIdle())) {
                    /* Add to the list */
                    theList.add(myBucket);
                }
            }
        }

        /**
         * Obtain item by id.
         *
         * @param pId the id to lookup
         * @return the item (or null if not present)
         */
        public B findItemById(final Integer pId) {
            /* Return results */
            return theList.getItemById(pId);
        }

        /**
         * Construct a ranged bucket.
         *
         * @param pBase  the base bucket
         * @param pRange the Range
         * @return the new bucket
         */
        protected abstract B newBucket(B pBase,
                                       OceanusDateRange pRange);

        /**
         * Obtain the AccountBucket for a given account.
         *
         * @param pAccount the account
         * @return the bucket
         */
        public B getBucket(final T pAccount) {
            /* Locate the bucket in the list */
            B myItem = findItemById(pAccount.getIndexedId());

            /* If the item does not yet exist */
            if (myItem == null) {
                /* Create the new bucket */
                myItem = newBucket(pAccount);

                /* Add to the list */
                theList.add(myItem);
            }

            /* Return the bucket */
            return myItem;
        }

        /**
         * Construct a standard bucket.
         *
         * @param pAccount the Account
         * @return the new bucket
         */
        protected abstract B newBucket(T pAccount);

        /**
         * SortBuckets.
         */
        protected void sortBuckets() {
            theList.sortList();
        }

        /**
         * Mark active accounts.
         *
         * @throws OceanusException on error
         */
        public void markActiveAccounts() throws OceanusException {
            /* Loop through the buckets */
            final Iterator<B> myIterator = iterator();
            while (myIterator.hasNext()) {
                final B myCurr = myIterator.next();
                final T myAccount = myCurr.getAccount();

                /* If we are active */
                if (myCurr.isActive()) {
                    /* Set the account as relevant */
                    myAccount.setRelevant();
                }

                /* If we are closed */
                if (Boolean.TRUE.equals(myAccount.isClosed())) {
                    /* Ensure that we have correct closed/maturity dates */
                    myAccount.adjustClosed();

                    /* If we are Relevant */
                    if (myAccount.isRelevant()
                            && theAnalysis.getData().checkClosedAccounts()) {
                        /* throw exception */
                        throw new MoneyWiseDataException(myCurr, "Illegally closed account");
                    }
                }
            }
        }
    }
}