MoneyWiseDepositRateTable.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.ui.dialog;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDateConfig;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
import io.github.tonywasher.joceanus.metis.ui.MetisAction;
import io.github.tonywasher.joceanus.metis.ui.MetisErrorPanel;
import io.github.tonywasher.joceanus.metis.ui.MetisIcon;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDepositRate;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDepositRate.MoneyWiseDepositRateList;
import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
import io.github.tonywasher.joceanus.moneywise.ui.MoneyWiseUIResource;
import io.github.tonywasher.joceanus.moneywise.ui.base.MoneyWiseDialogTable;
import io.github.tonywasher.joceanus.moneywise.views.MoneyWiseView;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
import io.github.tonywasher.joceanus.prometheus.ui.fieldset.PrometheusFieldSetTableTab.PrometheusFieldSetTable;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControl.TethysUIIconMapSet;
import io.github.tonywasher.joceanus.tethys.api.table.TethysUITableColumn;
import io.github.tonywasher.joceanus.tethys.api.table.TethysUITableManager;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Deposit Rate Table.
 */
public class MoneyWiseDepositRateTable
        extends MoneyWiseDialogTable<MoneyWiseDepositRate>
        implements PrometheusFieldSetTable<MoneyWiseDeposit> {
    /**
     * Deposit.
     */
    private MoneyWiseDeposit theDeposit;

    /**
     * The edit list.
     */
    private MoneyWiseDepositRateList theRates;

    /**
     * The active column.
     */
    private final TethysUITableColumn<MetisAction, MetisDataFieldId, MoneyWiseDepositRate> theActiveColumn;

    /**
     * Constructor.
     *
     * @param pView    the view
     * @param pEditSet the editSet
     * @param pError   the error panel
     */
    public MoneyWiseDepositRateTable(final MoneyWiseView pView,
                                     final PrometheusEditSet pEditSet,
                                     final MetisErrorPanel pError) {
        /* Store parameters */
        super(pView, pEditSet, pError, MoneyWiseBasicDataType.DEPOSITRATE);

        /* Access Gui factory */
        final TethysUITableManager<MetisDataFieldId, MoneyWiseDepositRate> myTable = getTable();

        /* Set table configuration */
        myTable.setDisabled(MoneyWiseDepositRate::isDisabled)
                .setComparator(MoneyWiseDepositRate::compareTo);

        /* Create the rate column */
        myTable.declareRateColumn(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_RATE)
                .setCellValueFactory(MoneyWiseDepositRate::getRate)
                .setEditable(true)
                .setColumnWidth(WIDTH_RATE)
                .setOnCommit((r, v) -> updateField(MoneyWiseDepositRate::setRate, r, v));

        /* Create the bonus column */
        myTable.declareRateColumn(MoneyWiseBasicResource.DEPOSITRATE_BONUS)
                .setCellValueFactory(MoneyWiseDepositRate::getBonus)
                .setEditable(true)
                .setColumnWidth(WIDTH_RATE)
                .setOnCommit((r, v) -> updateField(MoneyWiseDepositRate::setBonus, r, v));

        /* Create the endDate column */
        myTable.declareDateColumn(MoneyWiseBasicResource.DEPOSITRATE_ENDDATE)
                .setDateConfigurator(this::handleDateConfig)
                .setCellValueFactory(MoneyWiseDepositRate::getEndDate)
                .setEditable(true)
                .setColumnWidth(WIDTH_DATE)
                .setOnCommit((r, v) -> updateField(MoneyWiseDepositRate::setEndDate, r, v));

        /* Create the Active column */
        final TethysUIIconMapSet<MetisAction> myActionMapSet = MetisIcon.configureStatusIconButton(pView.getGuiFactory());
        theActiveColumn = myTable.declareIconColumn(PrometheusDataResource.DATAITEM_TOUCH, MetisAction.class)
                .setIconMapSet(r -> myActionMapSet)
                .setCellValueFactory(r -> r.isActive() ? MetisAction.ACTIVE : MetisAction.DELETE)
                .setName(MoneyWiseUIResource.STATICDATA_ACTIVE.getValue())
                .setEditable(true)
                .setCellEditable(r -> !r.isActive())
                .setColumnWidth(WIDTH_ICON)
                .setOnCommit((r, v) -> updateField(this::deleteRow, r, v));
    }

    @Override
    public void refreshData() {
        /* Access the rates list */
        theRates = getEditSet().getDataList(MoneyWiseBasicDataType.DEPOSITRATE, MoneyWiseDepositRateList.class);
        getTable().setItems(theRates.getUnderlyingList());
    }

    @Override
    public void setItem(final MoneyWiseDeposit pDeposit) {
        /* Store the security */
        if (!MetisDataDifference.isEqual(pDeposit, theDeposit)) {
            theDeposit = pDeposit;
            updateTableData();
        }
    }

    @Override
    protected void addNewItem() {
        /* Protect against Exceptions */
        try {
            /* Add a new rate */
            final MoneyWiseDepositRate myRate = addNewRate();

            /* Shift display to line */
            updateTableData();
            selectItem(myRate);
            getEditSet().incrementVersion();
            notifyChanges();

            /* Handle Exceptions */
        } catch (OceanusException e) {
            /* Build the error */
            final OceanusException myError = new MoneyWiseDataException("Failed to create new rate", e);

            /* Show the error */
            setError(myError);
        }
    }

    /**
     * Add a new rate for a deposit.
     *
     * @return the rate
     * @throws OceanusException on error
     */
    private MoneyWiseDepositRate addNewRate() throws OceanusException {
        /* Create the new rate */
        final MoneyWiseDepositRate myRate = new MoneyWiseDepositRate(theRates);

        /* Set the item value */
        myRate.setDeposit(theDeposit);
        myRate.setRate(OceanusRate.getWholePercentage(0));

        /* Access iterator */
        final Iterator<MoneyWiseDepositRate> myIterator = getTable().viewIterator();

        /* Access the last rate and look to see whether we have a null date */
        final MoneyWiseDepositRate myLast = myIterator.hasNext()
                ? myIterator.next()
                : null;
        final boolean hasNullDate = myLast != null && myLast.getEndDate() == null;

        /* If we have a null date in the last element */
        if (hasNullDate) {
            /* Access the previous element */
            final MoneyWiseDepositRate myLatest = myIterator.hasNext()
                    ? myIterator.next()
                    : null;

            /* Assume that we can use today's date */
            OceanusDate myDate = new OceanusDate();
            if (myLatest != null) {
                /* Obtain the date that is one after the latest date used */
                final OceanusDate myNew = new OceanusDate(myLatest.getEndDate());
                myNew.adjustDay(1);

                /* Use the latest of the two dates */
                if (myDate.compareTo(myNew) < 0) {
                    myDate = myNew;
                }
            }

            /* Adjust the last date */
            myLast.pushHistory();
            myLast.setNewVersion();
            myLast.setEndDate(myDate);
        }

        /* Add the new item */
        myRate.setNewVersion();
        theRates.add(myRate);

        /*
         * Don't validate the rate yet. We need to take care such that we can only add a new price
         * when there is a slot available, and that we validate the entire list after an update
         */
        return myRate;
    }

    @Override
    public boolean isVisible() {
        return isViewActive();
    }

    @Override
    public void setEditable(final boolean pEditable) {
        /* Show/Hide columns/panels */
        super.setEditable(pEditable);
        theActiveColumn.setVisible(pEditable);
    }

    @Override
    protected boolean isFiltered(final MoneyWiseDepositRate pRow) {
        return super.isFiltered(pRow)
                && theDeposit != null
                && theDeposit.equals(pRow.getDeposit());
    }

    /**
     * Handle dateConfig.
     *
     * @param pRow    the row
     * @param pConfig the dateConfig
     */
    private void handleDateConfig(final MoneyWiseDepositRate pRow,
                                  final OceanusDateConfig pConfig) {
        /* Build a list of used dates */
        final List<OceanusDate> myDates = new ArrayList<>();
        final Iterator<MoneyWiseDepositRate> myIterator = getTable().viewIterator();
        while (myIterator.hasNext()) {
            final MoneyWiseDepositRate myRate = myIterator.next();
            myDates.add(myRate.getEndDate());
        }

        /* Set filter constraint */
        final OceanusDate myCurrent = pRow.getEndDate();
        pConfig.setAllowed(d -> d.equals(myCurrent) || !myDates.contains(d));

        /* Set null constraint */
        final boolean hasNull = myDates.contains(null);
        pConfig.setAllowNullDateSelection(!hasNull || myCurrent == null);
    }
}