MoneyWiseValidateTransInfoSet.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.validate;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
import io.github.tonywasher.joceanus.metis.field.MetisFieldRequired;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetDirection;
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.MoneyWiseDeposit.MoneyWiseDepositList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio.MoneyWisePortfolioList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxCredit;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransBase;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransInfo;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransInfoSet;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoClass;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataInfoClass;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
import io.github.tonywasher.joceanus.prometheus.validate.PrometheusValidateInfoSet;

import java.util.Currency;
import java.util.Iterator;
import java.util.Objects;

/**
 * Validate TransInfoSet.
 */
public class MoneyWiseValidateTransInfoSet
        extends PrometheusValidateInfoSet<MoneyWiseTransInfo> {
    /**
     * Are we using new validation?
     */
    private final boolean newValidation;

    /**
     * Constructor.
     *
     * @param pNewValidation true/false
     */
    MoneyWiseValidateTransInfoSet(final boolean pNewValidation) {
        newValidation = pNewValidation;
    }

    @Override
    public MoneyWiseTransaction getOwner() {
        return (MoneyWiseTransaction) super.getOwner();
    }

    @Override
    public MetisFieldRequired isClassRequired(final PrometheusDataInfoClass pClass) {
        /* Access details about the Transaction */
        final MoneyWiseTransaction myTransaction = getOwner();
        final MoneyWiseTransCategory myCategory = myTransaction.getCategory();

        /* If we have no Category, no class is allowed */
        if (myCategory == null) {
            return MetisFieldRequired.NOTALLOWED;
        }
        final MoneyWiseTransCategoryClass myClass = myCategory.getCategoryTypeClass();
        if (myClass == null) {
            return MetisFieldRequired.NOTALLOWED;
        }

        /* Switch on class */
        switch ((MoneyWiseTransInfoClass) pClass) {
            /* Reference and comments are always available */
            case REFERENCE:
            case COMMENTS:
            case TRANSTAG:
                return MetisFieldRequired.CANEXIST;

            /* NatInsurance and benefit can only occur on salary/pensionContribution */
            case EMPLOYERNATINS:
            case EMPLOYEENATINS:
                return myClass.isNatInsurance()
                        ? MetisFieldRequired.CANEXIST
                        : MetisFieldRequired.NOTALLOWED;

            /* Benefit can only occur on salary */
            case DEEMEDBENEFIT:
                return myClass == MoneyWiseTransCategoryClass.TAXEDINCOME
                        ? MetisFieldRequired.CANEXIST
                        : MetisFieldRequired.NOTALLOWED;

            /* Handle Withheld separately */
            case WITHHELD:
                return isWithheldAmountRequired(myClass);

            /* Handle Tax Credit */
            case TAXCREDIT:
                return isTaxCreditClassRequired(myClass);

            /* Handle AccountUnits */
            case ACCOUNTDELTAUNITS:
                return isAccountUnitsDeltaRequired(myClass);

            /* Handle PartnerUnits */
            case PARTNERDELTAUNITS:
                return isPartnerUnitsDeltaRequired(myClass);

            /* Handle Dilution separately */
            case DILUTION:
                return isDilutionClassRequired(myClass);

            /* Qualify Years is needed only for Taxable Gain */
            case QUALIFYYEARS:
                return isQualifyingYearsClassRequired(myClass);

            /* Handle ThirdParty separately */
            case RETURNEDCASHACCOUNT:
                return isReturnedCashAccountRequired(myClass);
            case RETURNEDCASH:
                return isReturnedCashRequired(myTransaction);

            case PARTNERAMOUNT:
                return isPartnerAmountClassRequired(myClass);

            case XCHANGERATE:
                return isXchangeRateClassRequired(myClass);

            case PRICE:
                return isPriceClassRequired(myClass);

            case COMMISSION:
                return isCommissionClassRequired(myClass);

            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    /**
     * Determine if an infoSet class is metaData.
     *
     * @param pClass the infoSet class
     * @return the status
     */
    public boolean isMetaData(final MoneyWiseTransInfoClass pClass) {
        /* Switch on class */
        switch (pClass) {
            /* Can always change reference/comments/tags */
            case REFERENCE:
            case COMMENTS:
            case TRANSTAG:
                return true;

            /* All others are locked */
            default:
                return false;
        }
    }

    /**
     * Determine if a TaxCredit infoSet class is required.
     *
     * @param pClass the category class
     * @return the status
     */
    private MetisFieldRequired isTaxCreditClassRequired(final MoneyWiseTransCategoryClass pClass) {
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseTaxCredit myYear = myTrans.getTaxYear();
        final MoneyWiseTransAsset myAccount = myTrans.getAccount();

        /* Switch on class */
        switch (pClass) {
            case TAXEDINCOME:
                return MetisFieldRequired.MUSTEXIST;
            case LOANINTERESTCHARGED:
                return MetisFieldRequired.CANEXIST;
            case LOYALTYBONUS:
            case INTEREST:
                return myAccount.isTaxFree()
                        || myAccount.isGross()
                        || !myYear.isTaxCreditRequired()
                        ? MetisFieldRequired.NOTALLOWED
                        : MetisFieldRequired.MUSTEXIST;
            case DIVIDEND:
                return !myAccount.isTaxFree()
                        && (myYear.isTaxCreditRequired() || myAccount.isForeign())
                        ? MetisFieldRequired.MUSTEXIST
                        : MetisFieldRequired.NOTALLOWED;
            case TRANSFER:
                return myAccount instanceof MoneyWiseSecurityHolding myHolding
                        && myHolding.getSecurity().isSecurityClass(MoneyWiseSecurityClass.LIFEBOND)
                        ? MetisFieldRequired.MUSTEXIST
                        : MetisFieldRequired.NOTALLOWED;
            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    /**
     * Determine if a Withheld amount is required.
     *
     * @param pClass the category class
     * @return the status
     */
    private static MetisFieldRequired isWithheldAmountRequired(final MoneyWiseTransCategoryClass pClass) {
        /* Withheld is only available for salary and interest */
        switch (pClass) {
            case TAXEDINCOME:
            case INTEREST:
                return MetisFieldRequired.CANEXIST;
            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    /**
     * Determine if an AccountDeltaUnits infoSet class is required.
     *
     * @param pClass the category class
     * @return the status
     */
    private MetisFieldRequired isAccountUnitsDeltaRequired(final MoneyWiseTransCategoryClass pClass) {
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseTransAsset myAccount = myTrans.getAccount();
        final MoneyWiseTransAsset myPartner = myTrans.getPartner();
        final MoneyWiseAssetDirection myDir = myTrans.getDirection();

        /* Account must be security holding */
        if (!(myAccount instanceof MoneyWiseSecurityHolding)) {
            return MetisFieldRequired.NOTALLOWED;
        }

        /* Account cannot be autoUnits */
        final MoneyWiseSecurityHolding myHolding = (MoneyWiseSecurityHolding) myAccount;
        if (myHolding.getSecurity().getCategoryClass().isAutoUnits()) {
            return MetisFieldRequired.NOTALLOWED;
        }

        /* Handle different transaction types */
        switch (pClass) {
            case TRANSFER:
            case STOCKDEMERGER:
                return MetisFieldRequired.CANEXIST;
            case UNITSADJUST:
            case STOCKSPLIT:
            case INHERITED:
                return MetisFieldRequired.MUSTEXIST;
            case DIVIDEND:
                return myAccount.equals(myPartner)
                        ? MetisFieldRequired.CANEXIST
                        : MetisFieldRequired.NOTALLOWED;
            case STOCKRIGHTSISSUE:
                return myDir.isFrom()
                        ? MetisFieldRequired.MUSTEXIST
                        : MetisFieldRequired.NOTALLOWED;
            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    /**
     * Determine if an PartnerDeltaUnits infoSet class is required.
     *
     * @param pClass the category class
     * @return the status
     */
    private MetisFieldRequired isPartnerUnitsDeltaRequired(final MoneyWiseTransCategoryClass pClass) {
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseTransAsset myPartner = myTrans.getPartner();
        final MoneyWiseAssetDirection myDir = myTrans.getDirection();

        /* Partner must be security holding */
        if (!(myPartner instanceof MoneyWiseSecurityHolding)) {
            return MetisFieldRequired.NOTALLOWED;
        }

        /* Partner cannot be autoUnits */
        final MoneyWiseSecurityHolding myHolding = (MoneyWiseSecurityHolding) myPartner;
        if (myHolding.getSecurity().getCategoryClass().isAutoUnits()) {
            return MetisFieldRequired.NOTALLOWED;
        }

        /* Handle different transaction types */
        switch (pClass) {
            case TRANSFER:
                return MetisFieldRequired.CANEXIST;
            case STOCKDEMERGER:
            case SECURITYREPLACE:
            case STOCKTAKEOVER:
                return MetisFieldRequired.MUSTEXIST;
            case STOCKRIGHTSISSUE:
                return myDir.isTo()
                        ? MetisFieldRequired.MUSTEXIST
                        : MetisFieldRequired.NOTALLOWED;
            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    /**
     * Determine if a Dilution infoSet class is required.
     *
     * @param pClass the category class
     * @return the status
     */
    private static MetisFieldRequired isDilutionClassRequired(final MoneyWiseTransCategoryClass pClass) {
        /* Dilution is only required for stock split/deMerger */
        switch (pClass) {
            case STOCKSPLIT:
            case UNITSADJUST:
                return MetisFieldRequired.CANEXIST;
            case STOCKDEMERGER:
                return MetisFieldRequired.MUSTEXIST;
            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    /**
     * Determine if a ReturnedCash Account class is required.
     *
     * @param pClass the category class
     * @return the status
     */
    private static MetisFieldRequired isReturnedCashAccountRequired(final MoneyWiseTransCategoryClass pClass) {
        /* Returned Cash is possible only for StockTakeOver */
        return pClass == MoneyWiseTransCategoryClass.STOCKTAKEOVER
                ? MetisFieldRequired.CANEXIST
                : MetisFieldRequired.NOTALLOWED;
    }

    /**
     * Determine if a ReturnedCash value is required.
     *
     * @param pTransaction the transaction
     * @return the status
     */
    private static MetisFieldRequired isReturnedCashRequired(final MoneyWiseTransaction pTransaction) {
        /* Returned Cash Amount is possible only if ReturnedCashAccount exists */
        return pTransaction.getReturnedCashAccount() != null
                ? MetisFieldRequired.MUSTEXIST
                : MetisFieldRequired.NOTALLOWED;
    }

    /**
     * Determine if a PartnerAmount infoSet class is required.
     *
     * @param pCategory the category
     * @return the status
     */
    private MetisFieldRequired isPartnerAmountClassRequired(final MoneyWiseTransCategoryClass pCategory) {
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseTransAsset myAccount = myTrans.getAccount();
        final MoneyWiseTransAsset myPartner = myTrans.getPartner();

        /* If the transaction requires null amount, then partner amount must also be null */
        if (pCategory.needsNullAmount()) {
            return MetisFieldRequired.NOTALLOWED;
        }

        /* If Partner currency is null or the same as Account then Partner amount is not allowed */
        final MoneyWiseCurrency myCurrency = myAccount.getAssetCurrency();
        final MoneyWiseCurrency myPartnerCurrency = myPartner == null ? null : myPartner.getAssetCurrency();
        if (myCurrency == null || myPartnerCurrency == null) {
            return MetisFieldRequired.NOTALLOWED;
        }
        return MetisDataDifference.isEqual(myCurrency, myPartnerCurrency)
                ? MetisFieldRequired.NOTALLOWED
                : MetisFieldRequired.MUSTEXIST;
    }

    /**
     * Determine if an QualifyingYears infoSet class is required.
     *
     * @param pCategory the category
     * @return the status
     */
    private MetisFieldRequired isQualifyingYearsClassRequired(final MoneyWiseTransCategoryClass pCategory) {
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseTransAsset myAccount = myTrans.getAccount();

        return pCategory == MoneyWiseTransCategoryClass.TRANSFER
                && myAccount instanceof MoneyWiseSecurityHolding myHolding
                && myHolding.getSecurity().isSecurityClass(MoneyWiseSecurityClass.LIFEBOND)
                ? MetisFieldRequired.MUSTEXIST
                : MetisFieldRequired.NOTALLOWED;
    }

    /**
     * Determine if an XchangeRate infoSet class is required.
     *
     * @param pCategory the category
     * @return the status
     */
    private MetisFieldRequired isXchangeRateClassRequired(final MoneyWiseTransCategoryClass pCategory) {
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseDataSet myData = myTrans.getDataSet();
        final MoneyWiseTransAsset myAccount = myTrans.getAccount();

        if (newValidation) {
            return MetisFieldRequired.CANEXIST;
        }
        return pCategory.isDividend()
                && !myAccount.getAssetCurrency().equals(myData.getReportingCurrency())
                ? MetisFieldRequired.MUSTEXIST
                : MetisFieldRequired.NOTALLOWED;
    }

    /**
     * Determine if a price infoSet class is required.
     *
     * @param pCategory the category
     * @return the status
     */
    private static MetisFieldRequired isPriceClassRequired(final MoneyWiseTransCategoryClass pCategory) {
        /* Only allowed for stockSplit and UnitsAdjust */
        switch (pCategory) {
            case STOCKSPLIT:
            case UNITSADJUST:
                return MetisFieldRequired.CANEXIST;
            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    /**
     * Determine if a Commission infoSet class is required.
     *
     * @param pCategory the category
     * @return the status
     */
    private static MetisFieldRequired isCommissionClassRequired(final MoneyWiseTransCategoryClass pCategory) {
        /* Don't allow yet */
        return MetisFieldRequired.NOTALLOWED;
        /* Account or Partner must be security holding
         if (!(pAccount instanceof SecurityHolding)
         && !(pPartner instanceof SecurityHolding)) {
         return MetisFieldRequired.NOTALLOWED;
         }
         switch (pCategory) {
         case TRANSFER:
         return MetisFieldRequired.CANEXIST;
         case DIVIDEND:
         return MetisDataDifference.isEqual(pAccount, pPartner)
         ? MetisFieldRequired.CANEXIST
         : MetisFieldRequired.NOTALLOWED;
         default:
         return MetisFieldRequired.NOTALLOWED;
         } */
    }

    @Override
    public void validateClass(final MoneyWiseTransInfo pInfo,
                              final PrometheusDataInfoClass pClass) {
        /* Switch on class */
        switch ((MoneyWiseTransInfoClass) pClass) {
            case QUALIFYYEARS:
                validateQualifyYears(pInfo);
                break;
            case TAXCREDIT:
                validateTaxCredit(pInfo);
                break;
            case EMPLOYEENATINS:
            case EMPLOYERNATINS:
            case DEEMEDBENEFIT:
            case WITHHELD:
                validateOptionalTaxCredit(pInfo);
                break;
            case PARTNERAMOUNT:
                validatePartnerAmount(pInfo);
                break;
            case RETURNEDCASHACCOUNT:
                validateReturnedCashAccount(pInfo);
                break;
            case RETURNEDCASH:
                validateReturnedCash(pInfo);
                break;
            case ACCOUNTDELTAUNITS:
            case PARTNERDELTAUNITS:
                validateDeltaUnits(pInfo);
                break;
            case REFERENCE:
            case COMMENTS:
                validateInfoLength(pInfo);
                break;
            case PRICE:
                validatePrice(pInfo);
                break;
            case TRANSTAG:
            case DILUTION:
            default:
                break;
        }
    }

    /**
     * Validate the qualifyingYears.
     *
     * @param pInfo the info
     */
    private void validateQualifyYears(final MoneyWiseTransInfo pInfo) {
        final Integer myYears = pInfo.getValue(Integer.class);
        if (myYears == 0) {
            getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.QUALIFYYEARS));
        } else if (myYears < 0) {
            getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.QUALIFYYEARS));
        }
    }

    /**
     * Validate the taxCredit.
     *
     * @param pInfo the info
     */
    private void validateTaxCredit(final MoneyWiseTransInfo pInfo) {
        final OceanusMoney myAmount = pInfo.getValue(OceanusMoney.class);
        final Currency myCurrency = getOwner().getAccount().getCurrency();
        if (!myAmount.isPositive()) {
            getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.TAXCREDIT));
        } else if (!myAmount.getCurrency().equals(myCurrency)) {
            getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.TAXCREDIT));
        }
    }

    /**
     * Validate the optional taxCredits.
     *
     * @param pInfo the info
     */
    private void validateOptionalTaxCredit(final MoneyWiseTransInfo pInfo) {
        final OceanusMoney myAmount = pInfo.getValue(OceanusMoney.class);
        final Currency myCurrency = getOwner().getAccount().getCurrency();
        if (myAmount.isZero()) {
            getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
        } else if (!myAmount.isPositive()) {
            getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
        } else if (!myAmount.getCurrency().equals(myCurrency)) {
            getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
        }
    }

    /**
     * Validate the partnerAmount.
     *
     * @param pInfo the info
     */
    private void validatePartnerAmount(final MoneyWiseTransInfo pInfo) {
        final MoneyWiseTransAsset myPartner = getOwner().getPartner();
        final OceanusMoney myAmount = pInfo.getValue(OceanusMoney.class);
        if (!myAmount.isPositive()) {
            getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
        } else if (!myAmount.getCurrency().equals(myPartner.getCurrency())) {
            getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
        }
    }

    /**
     * Validate the returnedCashAccount.
     *
     * @param pInfo the info
     */
    private void validateReturnedCashAccount(final MoneyWiseTransInfo pInfo) {
        final MoneyWiseTransAsset myThirdParty = pInfo.getTransAsset();
        final Currency myCurrency = getOwner().getAccount().getCurrency();
        if (!myCurrency.equals(myThirdParty.getCurrency())) {
            getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASHACCOUNT));
        }
    }

    /**
     * Validate the returnedCash.
     *
     * @param pInfo the info
     */
    private void validateReturnedCash(final MoneyWiseTransInfo pInfo) {
        final MoneyWiseTransAsset myThirdParty = getOwner().getReturnedCashAccount();
        final OceanusMoney myAmount = pInfo.getValue(OceanusMoney.class);
        if (myAmount.isZero()) {
            getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
        } else if (!myAmount.isPositive()) {
            getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
        } else if (!myAmount.getCurrency().equals(myThirdParty.getCurrency())) {
            getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
        }
    }

    /**
     * Validate the price.
     *
     * @param pInfo the info
     */
    private void validatePrice(final MoneyWiseTransInfo pInfo) {
        final OceanusPrice myPrice = pInfo.getValue(OceanusPrice.class);
        final Currency myCurrency = getOwner().getAccount().getCurrency();
        if (myPrice.isZero()) {
            getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
        } else if (!myPrice.isPositive()) {
            getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
        } else if (!myPrice.getCurrency().equals(myCurrency)) {
            getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
        }
    }

    /**
     * Validate the deltaUnits.
     *
     * @param pInfo the info
     */
    private void validateDeltaUnits(final MoneyWiseTransInfo pInfo) {
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseAssetDirection myDir = myTrans.getDirection();
        final MoneyWiseTransCategoryClass myCatClass = myTrans.getCategoryClass();
        final MoneyWiseTransInfoClass myInfoClass = pInfo.getInfoClass();
        final MetisFieldRequired isRequired = myInfoClass == MoneyWiseTransInfoClass.ACCOUNTDELTAUNITS
                ? isAccountUnitsPositive(myDir, myCatClass)
                : isPartnerUnitsPositive(myDir, myCatClass);
        final OceanusUnits myUnits = pInfo.getValue(OceanusUnits.class);
        if (myUnits.isZero()) {
            getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(myInfoClass));
        } else if (myUnits.isPositive() && isRequired.notAllowed()) {
            getOwner().addError(PrometheusDataItem.ERROR_POSITIVE, MoneyWiseTransInfoSet.getFieldForClass(myInfoClass));
        } else if (!myUnits.isPositive() && isRequired.mustExist()) {
            getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(myInfoClass));
        }
    }

    /**
     * Validate the info length.
     *
     * @param pInfo the info
     */
    private void validateInfoLength(final MoneyWiseTransInfo pInfo) {
        final String myInfo = pInfo.getValue(String.class);
        final MoneyWiseTransInfoClass myClass = pInfo.getInfoClass();
        if (myInfo.length() > myClass.getMaximumLength()) {
            getOwner().addError(PrometheusDataItem.ERROR_LENGTH, MoneyWiseTransInfoSet.getFieldForClass(myClass));
        }
    }

    /**
     * Determine if AccountDeltaUnits can/mustBe/mustNotBe positive.
     *
     * @param pDir   the direction
     * @param pClass the category class
     * @return the status
     */
    public static MetisFieldRequired isAccountUnitsPositive(final MoneyWiseAssetDirection pDir,
                                                            final MoneyWiseTransCategoryClass pClass) {
        switch (pClass) {
            case TRANSFER:
                return pDir.isFrom()
                        ? MetisFieldRequired.MUSTEXIST
                        : MetisFieldRequired.NOTALLOWED;
            case UNITSADJUST:
            case STOCKSPLIT:
                return MetisFieldRequired.CANEXIST;
            case INHERITED:
            case DIVIDEND:
            case STOCKRIGHTSISSUE:
                return MetisFieldRequired.MUSTEXIST;
            case STOCKDEMERGER:
            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    /**
     * Determine if PartnerDeltaUnits can/mustBe/mustNotBe positive.
     *
     * @param pDir   the direction
     * @param pClass the category class
     * @return the status
     */
    public static MetisFieldRequired isPartnerUnitsPositive(final MoneyWiseAssetDirection pDir,
                                                            final MoneyWiseTransCategoryClass pClass) {
        switch (pClass) {
            case TRANSFER:
                return pDir.isTo()
                        ? MetisFieldRequired.MUSTEXIST
                        : MetisFieldRequired.NOTALLOWED;
            case STOCKDEMERGER:
            case SECURITYREPLACE:
            case STOCKTAKEOVER:
            case STOCKRIGHTSISSUE:
                return MetisFieldRequired.MUSTEXIST;
            default:
                return MetisFieldRequired.NOTALLOWED;
        }
    }

    @Override
    protected void setDefault(final PrometheusDataInfoClass pClass) throws OceanusException {
        /* Switch on the class */
        switch ((MoneyWiseTransInfoClass) pClass) {
            case ACCOUNTDELTAUNITS:
                getInfoSet().setValue(pClass, getDefaultAccountUnits());
                break;
            case PARTNERDELTAUNITS:
                getInfoSet().setValue(pClass, getDefaultPartnerUnits());
                break;
            case DILUTION:
                getInfoSet().setValue(pClass, OceanusRatio.ONE);
                break;
            case QUALIFYYEARS:
                getInfoSet().setValue(pClass, 1);
                break;
            case TAXCREDIT:
                getInfoSet().setValue(pClass, getDefaultTaxCredit());
                break;
            case PARTNERAMOUNT:
                getInfoSet().setValue(pClass, getDefaultPartnerAmount());
                break;
            case RETURNEDCASHACCOUNT:
                getInfoSet().setValue(pClass, getDefaultReturnedCashAccount());
                break;
            case RETURNEDCASH:
                getInfoSet().setValue(pClass, getDefaultReturnedCash());
                break;
            default:
                break;
        }
    }

    /**
     * Obtain default accountUnits.
     *
     * @return the default deltaUnits
     */
    private OceanusUnits getDefaultAccountUnits() {
        /* Determine whether the units must be +ve or -ve */
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseAssetDirection myDir = myTrans.getDirection();
        final MoneyWiseTransCategoryClass myCategoryClass = myTrans.getCategoryClass();
        final MetisFieldRequired isRequired = isAccountUnitsPositive(myDir, myCategoryClass);
        return isRequired.notAllowed()
                ? OceanusUnits.getWholeUnits(-1)
                : OceanusUnits.getWholeUnits(1);
    }

    /**
     * Obtain default partnerUnits.
     *
     * @return the default deltaUnits
     */
    private OceanusUnits getDefaultPartnerUnits() {
        /* Determine whether the units must be +ve or -ve */
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseAssetDirection myDir = myTrans.getDirection();
        final MoneyWiseTransCategoryClass myCategoryClass = myTrans.getCategoryClass();
        final MetisFieldRequired isRequired = isPartnerUnitsPositive(myDir, myCategoryClass);
        return isRequired.notAllowed()
                ? OceanusUnits.getWholeUnits(-1)
                : OceanusUnits.getWholeUnits(1);
    }

    /**
     * Obtain default taxCredit.
     *
     * @return the default taxCredit
     */
    private OceanusMoney getDefaultTaxCredit() {
        /* Access the account */
        final MoneyWiseTransAsset myAsset = getOwner().getAccount();
        final MoneyWiseCurrency myCurrency = myAsset.getAssetCurrency();

        /* Return zero cash in the appropriate currency */
        return new OceanusMoney(myCurrency.getCurrency());
    }

    /**
     * Obtain default partnerAmount.
     *
     * @return the default partnerAmount
     */
    private OceanusMoney getDefaultPartnerAmount() {
        /* Access the partner */
        final MoneyWiseTransAsset myAsset = getOwner().getPartner();
        final MoneyWiseCurrency myCurrency = myAsset.getAssetCurrency();

        /* Return zero cash in the appropriate currency */
        return new OceanusMoney(myCurrency.getCurrency());
    }

    /**
     * Obtain default account for ReturnedCashAccount.
     *
     * @return the default returnedCashAccount
     */
    private MoneyWiseTransAsset getDefaultReturnedCashAccount() {
        /* loop through the deposits */
        final MoneyWiseDepositList myDeposits
                = getEditSet().getDataList(MoneyWiseBasicDataType.DEPOSIT, MoneyWiseDepositList.class);
        final Iterator<MoneyWiseDeposit> myDepIterator = myDeposits.iterator();
        while (myDepIterator.hasNext()) {
            final MoneyWiseDeposit myDeposit = myDepIterator.next();

            /* Use if not deleted or closed */
            if (!myDeposit.isDeleted() && Boolean.FALSE.equals(myDeposit.isClosed())) {
                return myDeposit;
            }
        }

        /* loop through the portfolios */
        final MoneyWisePortfolioList myPortfolios
                = getEditSet().getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
        final Iterator<MoneyWisePortfolio> myPortIterator = myPortfolios.iterator();
        while (myPortIterator.hasNext()) {
            final MoneyWisePortfolio myPortfolio = myPortIterator.next();

            /* Use if not deleted or closed */
            if (!myPortfolio.isDeleted() && Boolean.FALSE.equals(myPortfolio.isClosed())) {
                return myPortfolio;
            }
        }

        /* Return no account */
        return null;
    }

    /**
     * Obtain default returnedCash.
     *
     * @return the default returnedCash
     */
    private OceanusMoney getDefaultReturnedCash() {
        /* Access the returned cash account */
        final MoneyWiseTransAsset myAsset = getOwner().getReturnedCashAccount();
        final MoneyWiseCurrency myCurrency = Objects.requireNonNull(myAsset).getAssetCurrency();

        /* Return zero cash in the appropriate currency */
        return new OceanusMoney(myCurrency.getCurrency());
    }

    @Override
    protected void autoCorrect(final PrometheusDataInfoClass pClass) throws OceanusException {
        /* Switch on class */
        final MoneyWiseTransInfoClass myClass = (MoneyWiseTransInfoClass) pClass;
        switch (myClass) {
            case ACCOUNTDELTAUNITS:
                autoCorrectAccountDeltaUnits();
                break;
            case PARTNERDELTAUNITS:
                autoCorrectPartnerDeltaUnits();
                break;
            case TAXCREDIT:
            case EMPLOYERNATINS:
            case EMPLOYEENATINS:
            case DEEMEDBENEFIT:
            case WITHHELD:
                autoCorrectTaxCredit(myClass);
                break;
            case PARTNERAMOUNT:
                autoCorrectPartnerAmount();
                break;
            case RETURNEDCASH:
                autoCorrectReturnedCash();
                break;
            case PRICE:
                autoCorrectPrice();
                break;
            default:
                break;
        }
    }

    /**
     * AutoCorrect accountDeltaUnits.
     *
     * @throws OceanusException on error
     */
    private void autoCorrectAccountDeltaUnits() throws OceanusException {
        /* Determine whether the units must be +ve or -ve */
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseAssetDirection myDir = myTrans.getDirection();
        final MoneyWiseTransCategoryClass myCategoryClass = myTrans.getCategoryClass();
        final MetisFieldRequired isRequired = isAccountUnitsPositive(myDir, myCategoryClass);
        OceanusUnits myUnits = Objects.requireNonNull(myTrans.getAccountDeltaUnits());

        /* If the units are negative and must be positive or are positive and must be negative */
        if ((isRequired.mustExist() && !myUnits.isPositive())
                || (isRequired.notAllowed() && myUnits.isPositive())) {
            /* Reverse the sign */
            myUnits = new OceanusUnits(myUnits);
            myUnits.negate();
            getInfoSet().setValue(MoneyWiseTransInfoClass.ACCOUNTDELTAUNITS, myUnits);
        }
    }


    /**
     * AutoCorrect partnerDeltaUnits.
     *
     * @throws OceanusException on error
     */
    private void autoCorrectPartnerDeltaUnits() throws OceanusException {
        /* Determine whether the units must be +ve or -ve */
        final MoneyWiseTransaction myTrans = getOwner();
        final MoneyWiseAssetDirection myDir = myTrans.getDirection();
        final MoneyWiseTransCategoryClass myCategoryClass = myTrans.getCategoryClass();
        final MetisFieldRequired isRequired = isPartnerUnitsPositive(myDir, myCategoryClass);
        OceanusUnits myUnits = Objects.requireNonNull(myTrans.getPartnerDeltaUnits());

        /* If the units are negative and must be positive or are positive and must be negative */
        if ((isRequired.mustExist() && !myUnits.isPositive())
                || (isRequired.notAllowed() && myUnits.isPositive())) {
            /* Reverse the sign */
            myUnits = new OceanusUnits(myUnits);
            myUnits.negate();
            getInfoSet().setValue(MoneyWiseTransInfoClass.PARTNERDELTAUNITS, myUnits);
        }
    }

    /**
     * AutoCorrect taxCredit.
     *
     * @param pClass the InfoClass
     * @throws OceanusException on error
     */
    private void autoCorrectTaxCredit(final MoneyWiseTransInfoClass pClass) throws OceanusException {
        /* Obtain the existing value */
        OceanusMoney myValue = getInfoSet().getValue(pClass, OceanusMoney.class);
        final MoneyWiseCurrency myAssetCurrency = getOwner().getAccount().getAssetCurrency();
        final Currency myCurrency = myAssetCurrency.getCurrency();

        /* If the value is not the correct currency */
        if (!myValue.getCurrency().equals(myCurrency)) {
            myValue = myValue.changeCurrency(myCurrency);
            getInfoSet().setValue(pClass, myValue);
        }
    }

    /**
     * AutoCorrect partnerAmount.
     *
     * @throws OceanusException on error
     */
    private void autoCorrectPartnerAmount() throws OceanusException {
        /* Obtain the existing value */
        final MoneyWiseTransaction myTrans = getOwner();
        OceanusMoney myValue = Objects.requireNonNull(myTrans.getPartnerAmount());
        final MoneyWiseCurrency myAssetCurrency = myTrans.getPartner().getAssetCurrency();
        final Currency myCurrency = myAssetCurrency.getCurrency();

        /* If the value is not the correct currency */
        if (!myCurrency.equals(myValue.getCurrency())) {
            myValue = myValue.changeCurrency(myCurrency);
            getInfoSet().setValue(MoneyWiseTransInfoClass.PARTNERAMOUNT, myValue);
        }
    }

    /**
     * AutoCorrect returnedCash.
     *
     * @throws OceanusException on error
     */
    private void autoCorrectReturnedCash() throws OceanusException {
        /* Obtain the existing value */
        final MoneyWiseTransaction myTrans = getOwner();
        OceanusMoney myValue = Objects.requireNonNull(myTrans.getReturnedCash());
        final MoneyWiseTransAsset myAsset = Objects.requireNonNull(myTrans.getReturnedCashAccount());
        final MoneyWiseCurrency myAssetCurrency = myAsset.getAssetCurrency();
        final Currency myCurrency = myAssetCurrency.getCurrency();

        /* If the value is not the correct currency */
        if (!myCurrency.equals(myValue.getCurrency())) {
            myValue = myValue.changeCurrency(myCurrency);
            getInfoSet().setValue(MoneyWiseTransInfoClass.RETURNEDCASH, myValue);
        }
    }

    /**
     * AutoCorrect price.
     *
     * @throws OceanusException on error
     */
    private void autoCorrectPrice() throws OceanusException {
        /* Obtain the existing value */
        final MoneyWiseTransaction myTrans = getOwner();
        OceanusPrice myPrice = Objects.requireNonNull(myTrans.getPrice());
        final MoneyWiseCurrency myAssetCurrency = getOwner().getAccount().getAssetCurrency();
        final Currency myCurrency = myAssetCurrency.getCurrency();

        /* If the value is not the correct currency */
        if (!myPrice.getCurrency().equals(myCurrency)) {
            myPrice = myPrice.changeCurrency(myCurrency);
            getInfoSet().setValue(MoneyWiseTransInfoClass.PRICE, myPrice);
        }
    }
}