MoneyWiseTransCategory.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.data.basic;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryType;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryType.MoneyWiseTransCategoryTypeList;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoClass;
import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusStaticDataItem;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

/**
 * Transaction Category class.
 */
public final class MoneyWiseTransCategory
        extends MoneyWiseCategoryBase {
    /**
     * Object name.
     */
    public static final String OBJECT_NAME = MoneyWiseBasicDataType.TRANSCATEGORY.getItemName();

    /**
     * List name.
     */
    public static final String LIST_NAME = MoneyWiseBasicDataType.TRANSCATEGORY.getListName();

    /**
     * Local Report fields.
     */
    private static final MetisFieldVersionedSet<MoneyWiseTransCategory> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(MoneyWiseTransCategory.class);

    /*
     * FieldIds.
     */
    static {
        FIELD_DEFS.declareLinkField(MoneyWiseStaticDataType.TRANSTYPE);
    }

    /**
     * Copy Constructor.
     *
     * @param pList     the list
     * @param pCategory The Category to copy
     */
    MoneyWiseTransCategory(final MoneyWiseTransCategoryList pList,
                           final MoneyWiseTransCategory pCategory) {
        /* Set standard values */
        super(pList, pCategory);
    }

    /**
     * Values constructor.
     *
     * @param pList   the List to add to
     * @param pValues the values constructor
     * @throws OceanusException on error
     */
    private MoneyWiseTransCategory(final MoneyWiseTransCategoryList pList,
                                   final PrometheusDataValues pValues) throws OceanusException {
        /* Initialise the item */
        super(pList, pValues);

        /* Store the Category Type */
        final Object myValue = pValues.getValue(MoneyWiseStaticDataType.TRANSTYPE);
        if (myValue instanceof Integer i) {
            setValueType(i);
        } else if (myValue instanceof String s) {
            setValueType(s);
        }
    }

    /**
     * Edit Constructor.
     *
     * @param pList the list
     */
    public MoneyWiseTransCategory(final MoneyWiseTransCategoryList pList) {
        super(pList);
    }

    @Override
    public MetisFieldSetDef getDataFieldSet() {
        return FIELD_DEFS;
    }

    @Override
    public boolean isActive() {
        return super.isActive() || isHidden();
    }

    @Override
    public boolean includeXmlField(final MetisDataFieldId pField) {
        /* Determine whether fields should be included */
        if (MoneyWiseStaticDataType.TRANSTYPE.equals(pField)) {
            return true;
        }

        /* Pass call on */
        return super.includeXmlField(pField);
    }

    @Override
    public MoneyWiseTransCategoryType getCategoryType() {
        return getValues().getValue(MoneyWiseStaticDataType.TRANSTYPE, MoneyWiseTransCategoryType.class);
    }

    @Override
    public MoneyWiseTransCategoryClass getCategoryTypeClass() {
        final MoneyWiseTransCategoryType myType = getCategoryType();
        return myType == null
                ? null
                : myType.getCategoryClass();
    }

    @Override
    public MoneyWiseTransCategory getParentCategory() {
        return getValues().getValue(PrometheusDataResource.DATAGROUP_PARENT, MoneyWiseTransCategory.class);
    }

    /**
     * Set category type value.
     *
     * @param pValue the value
     */
    private void setValueType(final MoneyWiseTransCategoryType pValue) {
        getValues().setUncheckedValue(MoneyWiseStaticDataType.TRANSTYPE, pValue);
    }

    /**
     * Set category type id.
     *
     * @param pValue the value
     */
    private void setValueType(final Integer pValue) {
        getValues().setUncheckedValue(MoneyWiseStaticDataType.TRANSTYPE, pValue);
    }

    /**
     * Set category type name.
     *
     * @param pValue the value
     */
    private void setValueType(final String pValue) {
        getValues().setUncheckedValue(MoneyWiseStaticDataType.TRANSTYPE, pValue);
    }

    @Override
    public MoneyWiseTransCategoryList getList() {
        return (MoneyWiseTransCategoryList) super.getList();
    }

    /**
     * Is this event category the required class.
     *
     * @param pClass the required category class.
     * @return true/false
     */
    public boolean isCategoryClass(final MoneyWiseTransCategoryClass pClass) {
        /* Check for match */
        return getCategoryTypeClass() == pClass;
    }

    /**
     * Is this event category a transfer?
     *
     * @return true/false
     */
    public boolean isTransfer() {
        /* Check for match */
        final MoneyWiseTransCategoryClass myClass = getCategoryTypeClass();
        return myClass != null
                && myClass.isTransfer();
    }

    /**
     * Set defaults.
     *
     * @param pParent the parent
     * @throws OceanusException on error
     */
    public void setDefaults(final MoneyWiseTransCategory pParent) throws OceanusException {
        getList().getValidator().setDefaults(pParent, this);
    }

    @Override
    public void resolveDataSetLinks() throws OceanusException {
        /* Update the Underlying details */
        super.resolveDataSetLinks();

        /* Resolve category type and parent */
        final MoneyWiseDataSet myData = getDataSet();
        resolveDataLink(MoneyWiseStaticDataType.TRANSTYPE, myData.getTransCategoryTypes());
    }

    @Override
    protected void resolveEditSetLinks() throws OceanusException {
        /* Resolve parent within list */
        resolveDataLink(PrometheusDataResource.DATAGROUP_PARENT, getList());

        /* Resolve StaticType if required */
        final PrometheusEditSet myEditSet = getList().getEditSet();
        if (myEditSet.hasDataType(MoneyWiseStaticDataType.TRANSTYPE)) {
            resolveDataLink(MoneyWiseStaticDataType.TRANSTYPE, myEditSet.getDataList(MoneyWiseStaticDataType.TRANSTYPE, MoneyWiseTransCategoryTypeList.class));
        }
    }

    @Override
    public void setCategoryType(final PrometheusStaticDataItem pType) {
        setValueType((MoneyWiseTransCategoryType) pType);
    }

    /**
     * Update base category from an edited category.
     *
     * @param pCategory the edited category
     * @return whether changes have been made
     */
    @Override
    public boolean applyChanges(final PrometheusDataItem pCategory) {
        /* Can only update from a transaction category */
        if (!(pCategory instanceof MoneyWiseTransCategory)) {
            return false;
        }
        final MoneyWiseTransCategory myCategory = (MoneyWiseTransCategory) pCategory;

        /* Store the current detail into history */
        pushHistory();

        /* Apply basic changes */
        applyBasicChanges(myCategory);

        /* Update the category type if required */
        if (!MetisDataDifference.isEqual(getCategoryType(), myCategory.getCategoryType())) {
            setValueType(myCategory.getCategoryType());
        }

        /* Check for changes */
        return checkForHistory();
    }

    /**
     * Is the category hidden?
     *
     * @return true/false
     */
    public boolean isHidden() {
        final MoneyWiseTransCategoryClass myClass = this.getCategoryTypeClass();
        return myClass != null
                && myClass.isHiddenType();
    }

    /**
     * The Transaction Category List class.
     */
    public static class MoneyWiseTransCategoryList
            extends MoneyWiseCategoryBaseList<MoneyWiseTransCategory> {
        /**
         * Report fields.
         */
        private static final MetisFieldSet<MoneyWiseTransCategoryList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseTransCategoryList.class);

        /**
         * The EditSet.
         */
        private PrometheusEditSet theEditSet;

        /**
         * Construct an empty CORE Category list.
         *
         * @param pData the DataSet for the list
         */
        public MoneyWiseTransCategoryList(final MoneyWiseDataSet pData) {
            super(pData, MoneyWiseTransCategory.class, MoneyWiseBasicDataType.TRANSCATEGORY);
        }

        /**
         * Constructor for a cloned List.
         *
         * @param pSource the source List
         */
        protected MoneyWiseTransCategoryList(final MoneyWiseTransCategoryList pSource) {
            super(pSource);
        }

        @Override
        public MetisFieldSet<MoneyWiseTransCategoryList> getDataFieldSet() {
            return FIELD_DEFS;
        }

        @Override
        public String listName() {
            return LIST_NAME;
        }

        @Override
        public MetisFieldSetDef getItemFields() {
            return MoneyWiseTransCategory.FIELD_DEFS;
        }

        /**
         * Obtain editSet.
         *
         * @return the editSet
         */
        public PrometheusEditSet getEditSet() {
            return theEditSet;
        }

        @Override
        public MoneyWiseTransCategoryDataMap getDataMap() {
            return (MoneyWiseTransCategoryDataMap) super.getDataMap();
        }

        @Override
        protected MoneyWiseTransCategoryList getEmptyList(final PrometheusListStyle pStyle) {
            final MoneyWiseTransCategoryList myList = new MoneyWiseTransCategoryList(this);
            myList.setStyle(pStyle);
            return myList;
        }

        /**
         * Derive Edit list.
         *
         * @param pEditSet the editSet
         * @return the edit list
         * @throws OceanusException on error
         */
        public MoneyWiseTransCategoryList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
            /* Build an empty List */
            final MoneyWiseTransCategoryList myList = getEmptyList(PrometheusListStyle.EDIT);
            myList.ensureMap();
            pEditSet.setEditEntryList(MoneyWiseBasicDataType.TRANSCATEGORY, myList);
            myList.getValidator().setEditSet(pEditSet);

            /* Store the editSet */
            myList.theEditSet = pEditSet;

            /* Loop through the categories */
            final Iterator<MoneyWiseTransCategory> myIterator = iterator();
            while (myIterator.hasNext()) {
                final MoneyWiseTransCategory myCurr = myIterator.next();

                /* Ignore deleted events */
                if (myCurr.isDeleted()) {
                    continue;
                }

                /* Build the new linked category and add it to the list */
                final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(myList, myCurr);
                myList.add(myCategory);
                myCategory.resolveEditSetLinks();

                /* Adjust the map */
                myCategory.adjustMapForItem();
            }

            /* Return the list */
            return myList;
        }

        /**
         * Add a new item to the core list.
         *
         * @param pCategory item
         * @return the newly added item
         */
        @Override
        public MoneyWiseTransCategory addCopyItem(final PrometheusDataItem pCategory) {
            /* Can only clone a TransactionCategory */
            if (!(pCategory instanceof MoneyWiseTransCategory)) {
                throw new UnsupportedOperationException();
            }

            final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this, (MoneyWiseTransCategory) pCategory);
            add(myCategory);
            return myCategory;
        }

        /**
         * Add a new item to the edit list.
         *
         * @return the new item
         */
        @Override
        public MoneyWiseTransCategory addNewItem() {
            final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this);
            add(myCategory);
            return myCategory;
        }

        /**
         * Obtain the first category for the specified class.
         *
         * @param pClass the category class
         * @return the category
         */
        public MoneyWiseTransCategory getSingularClass(final MoneyWiseTransCategoryClass pClass) {
            /* Lookup in the map */
            return getDataMap().findSingularItem(pClass);
        }

        /**
         * Obtain singular category for EventInfoClass.
         *
         * @param pInfoClass the Event info class
         * @return the corresponding category.
         */
        public MoneyWiseTransCategory getEventInfoCategory(final MoneyWiseTransInfoClass pInfoClass) {
            /* Switch on info class */
            switch (pInfoClass) {
                case TAXCREDIT:
                    return getSingularClass(MoneyWiseTransCategoryClass.INCOMETAX);
                case DEEMEDBENEFIT:
                    return getSingularClass(MoneyWiseTransCategoryClass.VIRTUALINCOME);
                case EMPLOYEENATINS:
                    return getSingularClass(MoneyWiseTransCategoryClass.EMPLOYEENATINS);
                case EMPLOYERNATINS:
                    return getSingularClass(MoneyWiseTransCategoryClass.EMPLOYERNATINS);
                case WITHHELD:
                    return getSingularClass(MoneyWiseTransCategoryClass.WITHHELD);
                default:
                    return null;
            }
        }

        @Override
        public MoneyWiseTransCategory addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
            /* Create the category */
            final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this, pValues);

            /* Check that this CategoryId has not been previously added */
            if (!isIdUnique(myCategory.getIndexedId())) {
                myCategory.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
                throw new MoneyWiseDataException(myCategory, ERROR_VALIDATION);
            }

            /* Add to the list */
            add(myCategory);

            /* Return it */
            return myCategory;
        }

        @Override
        protected MoneyWiseTransCategoryDataMap allocateDataMap() {
            return new MoneyWiseTransCategoryDataMap();
        }
    }

    /**
     * The dataMap class.
     */
    public static class MoneyWiseTransCategoryDataMap
            extends MoneyWiseCategoryDataMap<MoneyWiseTransCategory> {
        /**
         * Report fields.
         */
        private static final MetisFieldSet<MoneyWiseTransCategoryDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseTransCategoryDataMap.class);

        /*
         * Declare Fields.
         */
        static {
            FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARMAP, MoneyWiseTransCategoryDataMap::getSingularMap);
            FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARCOUNTS, MoneyWiseTransCategoryDataMap::getSingularCountMap);
        }

        /**
         * Map of category counts.
         */
        private final Map<Integer, Integer> theCategoryCountMap;

        /**
         * Map of singular categories.
         */
        private final Map<Integer, MoneyWiseTransCategory> theCategoryMap;

        /**
         * Constructor.
         */
        public MoneyWiseTransCategoryDataMap() {
            /* Create the maps */
            theCategoryCountMap = new HashMap<>();
            theCategoryMap = new HashMap<>();
        }

        @Override
        public MetisFieldSet<MoneyWiseTransCategoryDataMap> getDataFieldSet() {
            return FIELD_DEFS;
        }

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

        /**
         * Obtain the categoryMap.
         *
         * @return the map
         */
        private Map<Integer, MoneyWiseTransCategory> getSingularMap() {
            return theCategoryMap;
        }

        /**
         * Obtain the categoryCountMap.
         *
         * @return the map
         */
        private Map<Integer, Integer> getSingularCountMap() {
            return theCategoryCountMap;
        }

        @Override
        public void resetMap() {
            super.resetMap();
            theCategoryCountMap.clear();
            theCategoryMap.clear();
        }

        @Override
        public void adjustForItem(final PrometheusDataItem pItem) {
            /* Access item */
            final MoneyWiseTransCategory myItem = (MoneyWiseTransCategory) pItem;

            /* If the class is singular */
            final MoneyWiseTransCategoryClass myClass = Objects.requireNonNull(myItem.getCategoryTypeClass());
            if (myClass.isSingular()) {
                /* Adjust category count */
                final Integer myId = myClass.getClassId();
                final Integer myCount = theCategoryCountMap.get(myId);
                if (myCount == null) {
                    theCategoryCountMap.put(myId, ONE);
                } else {
                    theCategoryCountMap.put(myId, myCount + 1);
                }

                /* Adjust category map */
                theCategoryMap.put(myId, myItem);
            }

            /* Adjust name count */
            adjustForItem(myItem, myItem.getName());
        }

        /**
         * find singular item.
         *
         * @param pClass the class to look up
         * @return the matching item
         */
        public MoneyWiseTransCategory findSingularItem(final MoneyWiseTransCategoryClass pClass) {
            return theCategoryMap.get(pClass.getClassId());
        }

        /**
         * Check validity of singular count.
         *
         * @param pClass the class to look up
         * @return true/false
         */
        public boolean validSingularCount(final MoneyWiseTransCategoryClass pClass) {
            final Integer myResult = theCategoryCountMap.get(pClass.getClassId());
            return ONE.equals(myResult);
        }
    }
}