MoneyWiseXAnalysisState.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.atlas.data.analysis.analyse;

import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseNewDepositRate;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseNewDepositRate.MoneyWiseNewDepositRateList;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisEvent;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisEventList;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisEventType;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisBucketForeign;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisBucketPriced;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisBucketRegister;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisCursor;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase.MoneyWiseAssetBaseList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataSet;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseExchangeRate;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseExchangeRate.MoneyWiseExchangeRateList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityPrice;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityPrice.MoneyWiseSecurityPriceList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction.MoneyWiseTransactionList;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Analysis Cursor.
 */
public class MoneyWiseXAnalysisState
        implements MoneyWiseXAnalysisCursor {
    /**
     * The securityPrice iterator.
     */
    private final Iterator<MoneyWiseSecurityPrice> thePriceIterator;

    /**
     * The exchangeRate iterator.
     */
    private final Iterator<MoneyWiseExchangeRate> theXchgRateIterator;

    /**
     * The depositRate iterator.
     */
    private final Iterator<MoneyWiseNewDepositRate> theDepRateIterator;

    /**
     * The transaction iterator.
     */
    private final Iterator<MoneyWiseTransaction> theTransIterator;

    /**
     * The price map.
     */
    private final Map<MoneyWiseSecurity, OceanusPrice> thePriceMap;

    /**
     * The xchgRate map.
     */
    private final Map<MoneyWiseCurrency, OceanusRatio> theXchgRateMap;

    /**
     * The depositRate map.
     */
    private final Map<MoneyWiseDeposit, OceanusRate> theDepRateMap;

    /**
     * The pricedBucketMap.
     */
    private final Map<MoneyWiseSecurity, List<MoneyWiseXAnalysisBucketPriced>> thePricedBucketsMap;

    /**
     * The foreignBucketMap.
     */
    private final Map<MoneyWiseCurrency, List<MoneyWiseXAnalysisBucketForeign>> theForeignBucketsMap;

    /**
     * The eventBuckets that are to be registered to the event.
     */
    private final Map<Long, MoneyWiseXAnalysisBucketRegister> theEventBuckets;

    /**
     * The editSet.
     */
    private final PrometheusEditSet theEditSet;

    /**
     * The startDate.
     */
    private final OceanusDate theStartDate;

    /**
     * The editSet.
     */
    private final MoneyWiseXAnalysisEventList theEvents;

    /**
     * Have balances been built?.
     */
    private boolean balancesBuilt;

    /**
     * The next Price.
     */
    private MoneyWiseSecurityPrice theNextPrice;

    /**
     * The next xchgRate.
     */
    private MoneyWiseExchangeRate theNextXchgRate;

    /**
     * The next depRate.
     */
    private MoneyWiseNewDepositRate theNextDepRate;

    /**
     * The next Transaction.
     */
    private MoneyWiseTransaction theNextTrans;

    /**
     * The next Event.
     */
    private MoneyWiseXAnalysisEvent theNextEvent;

    /**
     * Constructor.
     *
     * @param pEditSet the editSet
     */
    public MoneyWiseXAnalysisState(final PrometheusEditSet pEditSet) {
        /* Store parameters */
        theEditSet = pEditSet;
        theEvents = new MoneyWiseXAnalysisEventList(theEditSet);

        final MoneyWiseSecurityPriceList myPrices = theEditSet.getDataList(MoneyWiseBasicDataType.SECURITYPRICE, MoneyWiseSecurityPriceList.class);
        thePriceIterator = myPrices.iterator();
        final MoneyWiseExchangeRateList myXchgRates = theEditSet.getDataList(MoneyWiseBasicDataType.EXCHANGERATE, MoneyWiseExchangeRateList.class);
        theXchgRateIterator = myXchgRates.iterator();
        theDepRateIterator = new MoneyWiseNewDepositRateList(theEditSet).iterator();
        theTransIterator = theEditSet.getDataList(MoneyWiseBasicDataType.TRANSACTION, MoneyWiseTransactionList.class).iterator();
        theStartDate = ((MoneyWiseDataSet) theEditSet.getDataSet()).getDateRange().getStart();
        balancesBuilt = false;

        /* Create the maps */
        thePriceMap = new HashMap<>();
        theXchgRateMap = new HashMap<>();
        theDepRateMap = new HashMap<>();
        thePricedBucketsMap = new HashMap<>();
        theForeignBucketsMap = new HashMap<>();
        theEventBuckets = new HashMap<>();

        /* Set up first elements */
        iteratePrice();
        iterateXchgRate();
        iterateDepRate();
        iterateTrans();
    }

    /**
     * Obtain the EventList.
     *
     * @return the list
     */
    public MoneyWiseXAnalysisEventList getEventList() {
        return theEvents;
    }

    /**
     * Build next event.
     *
     * @return the next event (or null)
     */
    public MoneyWiseXAnalysisEvent nextEvent() {
        /* Determine next eventType */
        final MoneyWiseXAnalysisEventType myType = nextEventType();

        /* Switch on the event Type */
        if (myType == null) {
            theNextEvent = null;
        } else {
            switch (myType) {
                case SECURITYPRICE:
                    nextSecurityPrice();
                    break;
                case XCHANGERATE:
                    nextXchgRate();
                    break;
                case DEPOSITRATE:
                    nextDepRate();
                    break;
                case OPENINGBALANCE:
                    buildBalances();
                    break;
                case TRANSACTION:
                default:
                    nextTransaction();
                    break;
            }
        }

        /* Return the new event */
        return theNextEvent;
    }

    /**
     * determine next eventType.
     *
     * @return the next eventTupe (or null)
     */
    private MoneyWiseXAnalysisEventType nextEventType() {
        /* Look for a price event */
        OceanusDate myDate = null;
        MoneyWiseXAnalysisEventType myType = null;
        if (theNextPrice != null) {
            myDate = theNextPrice.getDate();
            myType = MoneyWiseXAnalysisEventType.SECURITYPRICE;
        }

        /* Look for an earlier xchgRate event */
        if (theNextXchgRate != null
                && (myDate == null || myDate.compareTo(theNextXchgRate.getDate()) > 0)) {
            myDate = theNextXchgRate.getDate();
            myType = MoneyWiseXAnalysisEventType.XCHANGERATE;
        }

        /* Look for an earlier depRate event */
        if (theNextDepRate != null
                && (myDate == null || myDate.compareTo(theNextDepRate.getDate()) > 0)) {
            myDate = theNextDepRate.getDate();
            myType = MoneyWiseXAnalysisEventType.DEPOSITRATE;
        }

        /* Immediate return for starting prices */
        if (theStartDate.equals(myDate)) {
            return myType;
        }

        /* If we have not yet built opening balances */
        if (!balancesBuilt) {
            balancesBuilt = true;
            return MoneyWiseXAnalysisEventType.OPENINGBALANCE;
        }

        /* Look for an earlier transaction */
        if (theNextTrans != null
                && (myDate == null || myDate.compareTo(theNextTrans.getDate()) > 0)) {
            return MoneyWiseXAnalysisEventType.TRANSACTION;
        }

        /* Return the next eventType */
        return myType;
    }

    /**
     * Build next price event.
     */
    private void nextSecurityPrice() {
        final OceanusDate myDate = theNextPrice.getDate();
        theNextEvent = theEvents.newSecurityPrice(myDate);
        do {
            processSecurityPrice();
        } while (theNextPrice != null && myDate.equals(theNextPrice.getDate()));
    }

    /**
     * Process securityPrice.
     */
    private void processSecurityPrice() {
        theNextEvent.declareSecurityPrice(theNextPrice);
        thePriceMap.put(theNextPrice.getSecurity(), theNextPrice.getPrice());
        iteratePrice();
    }

    /**
     * Build next xchangeRate event.
     */
    private void nextXchgRate() {
        final OceanusDate myDate = theNextXchgRate.getDate();
        theNextEvent = theEvents.newXchangeRate(myDate);
        do {
            processXchgRate();
        } while (theNextXchgRate != null && myDate.equals(theNextXchgRate.getDate()));
    }

    /**
     * Process xchangeRate.
     */
    private void processXchgRate() {
        theNextEvent.declareExchangeRate(theNextXchgRate);
        theXchgRateMap.put(theNextXchgRate.getToCurrency(), theNextXchgRate.getExchangeRate());
        iterateXchgRate();
    }

    /**
     * Build next depositRate event.
     */
    private void nextDepRate() {
        final OceanusDate myDate = theNextDepRate.getDate();
        theNextEvent = theEvents.newDepositRate(myDate);
        do {
            processDepRate();
        } while (theNextDepRate != null && myDate.equals(theNextDepRate.getDate()));
    }

    /**
     * Process depositRate.
     */
    private void processDepRate() {
        theNextEvent.declareDepositRate(theNextDepRate);
        theDepRateMap.put(theNextDepRate.getDeposit(), theNextDepRate.getRate());
        iterateDepRate();
    }

    /**
     * Build next transaction event.
     */
    private void nextTransaction() {
        theNextEvent = theEvents.newTransaction(theNextTrans);
        iterateTrans();
    }

    @Override
    public OceanusPrice getCurrentPrice(final MoneyWiseSecurity pSecurity) {
        final OceanusPrice myPrice = thePriceMap.get(pSecurity);
        return myPrice == null
                ? OceanusPrice.getWholeUnits(1, pSecurity.getCurrency())
                : myPrice;
    }

    @Override
    public OceanusRatio getCurrentXchgRate(final MoneyWiseCurrency pCurrency) {
        final OceanusRatio myRate = theXchgRateMap.get(pCurrency);
        return myRate == null ? OceanusRatio.ONE : myRate;
    }

    @Override
    public OceanusRate getCurrentDepositRate(final MoneyWiseDeposit pDeposit) {
        return theDepRateMap.get(pDeposit);
    }

    @Override
    public void registerForPriceUpdates(final MoneyWiseXAnalysisBucketPriced pBucket) {
        /* Determine the security whose price we are tracking */
        MoneyWiseSecurity mySecurity = pBucket.getSecurity();
        if (pBucket.isStockOption()) {
            mySecurity = mySecurity.getUnderlyingStock();
        }

        /* Add to list */
        final List<MoneyWiseXAnalysisBucketPriced> myList = thePricedBucketsMap.computeIfAbsent(mySecurity, x -> new ArrayList<>());
        myList.add(pBucket);
    }

    @Override
    public void registerForXchgRateUpdates(final MoneyWiseXAnalysisBucketForeign pBucket) {
        final List<MoneyWiseXAnalysisBucketForeign> myList = theForeignBucketsMap.computeIfAbsent(pBucket.getCurrency(), x -> new ArrayList<>());
        myList.add(pBucket);
    }

    @Override
    public void registerBucketInterest(final MoneyWiseXAnalysisBucketRegister pBucket) {
        theEventBuckets.put(pBucket.getBucketId(), pBucket);
    }

    @Override
    public void registerInterestedBucketsForEvent(final MoneyWiseXAnalysisEvent pEvent) {
        for (MoneyWiseXAnalysisBucketRegister myBucket : theEventBuckets.values()) {
            myBucket.registerEvent(pEvent);
        }
        theEventBuckets.clear();
    }

    /**
     * Set new price for security as part of a transaction.
     *
     * @param pSecurity the security
     * @param pPrice    the new price
     */
    public void setNewPriceViaTransaction(final MoneyWiseSecurity pSecurity,
                                          final OceanusPrice pPrice) {
        thePriceMap.put(pSecurity, pPrice);
    }

    /**
     * Obtain the pricedBuckets iterator for a security.
     *
     * @param pSecurity the security
     * @return the iterator
     */
    public Iterator<MoneyWiseXAnalysisBucketPriced> pricedBucketIterator(final MoneyWiseSecurity pSecurity) {
        final List<MoneyWiseXAnalysisBucketPriced> myList = thePricedBucketsMap.get(pSecurity);
        return myList != null ? myList.iterator() : Collections.emptyIterator();
    }

    /**
     * Obtain the foreignBuckets iterator for a currency.
     *
     * @param pCurrency the currency
     * @return the iterator
     */
    public Iterator<MoneyWiseXAnalysisBucketForeign> foreignBucketIterator(final MoneyWiseCurrency pCurrency) {
        final List<MoneyWiseXAnalysisBucketForeign> myList = theForeignBucketsMap.get(pCurrency);
        return myList != null ? myList.iterator() : Collections.emptyIterator();
    }

    /**
     * Iterate the SecurityPrices.
     */
    private void iteratePrice() {
        theNextPrice = thePriceIterator.hasNext() ? thePriceIterator.next() : null;
    }

    /**
     * Iterate the ExchangeRates.
     */
    private void iterateXchgRate() {
        theNextXchgRate = theXchgRateIterator.hasNext() ? theXchgRateIterator.next() : null;
    }

    /**
     * Iterate the ExchangeRates.
     */
    private void iterateDepRate() {
        theNextDepRate = theDepRateIterator.hasNext() ? theDepRateIterator.next() : null;
    }

    /**
     * Iterate the Transactions.
     */
    private void iterateTrans() {
        do {
            theNextTrans = theTransIterator.hasNext() ? theTransIterator.next() : null;
        } while (theNextTrans != null && theNextTrans.isDeleted());
    }

    /**
     * Build opening balances.
     */
    private void buildBalances() {
        theNextEvent = theEvents.newOpeningBalance(theStartDate);
        buildBalances(MoneyWiseBasicDataType.DEPOSIT);
        buildBalances(MoneyWiseBasicDataType.CASH);
        buildBalances(MoneyWiseBasicDataType.LOAN);
    }

    /**
     * Build opening balances.
     *
     * @param pType the data type
     */
    private void buildBalances(final MoneyWiseBasicDataType pType) {
        final MoneyWiseAssetBaseList<?> myList = theEditSet.getDataList(pType, MoneyWiseAssetBaseList.class);
        final Iterator<? extends MoneyWiseAssetBase> myIterator = myList.iterator();
        while (myIterator.hasNext()) {
            final MoneyWiseAssetBase myAsset = myIterator.next();
            final OceanusMoney myBalance = myAsset.getOpeningBalance();
            if (myBalance != null) {
                theNextEvent.declareOpeningBalance(myAsset);
            }
        }
    }
}