MoneyWiseQIFAccount.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.quicken.file;

import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCash;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoan;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCashCategoryClass;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseDepositCategoryClass;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseLoanCategoryClass;
import io.github.tonywasher.joceanus.moneywise.quicken.definitions.MoneyWiseQAccountLineType;
import io.github.tonywasher.joceanus.moneywise.quicken.definitions.MoneyWiseQLineType;
import io.github.tonywasher.joceanus.moneywise.quicken.file.MoneyWiseQIFLine.MoneyWiseQIFMoneyLine;
import io.github.tonywasher.joceanus.moneywise.quicken.file.MoneyWiseQIFLine.MoneyWiseQIFStringLine;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;

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

/**
 * Class representing a QIF Account record.
 */
public class MoneyWiseQIFAccount
        extends MoneyWiseQIFRecord<MoneyWiseQAccountLineType>
        implements Comparable<MoneyWiseQIFAccount> {
    /**
     * Item type.
     */
    protected static final String QIF_HDR = "!Account";

    /**
     * Bank Account Type.
     */
    protected static final String QIFACT_BANK = "Bank";

    /**
     * Cash Account Type.
     */
    protected static final String QIFACT_CASH = "Cash";

    /**
     * Investment Account Type.
     */
    protected static final String QIFACT_INVST = "Invst";

    /**
     * Credit Card Account Type.
     */
    protected static final String QIFACT_CCARD = "CCard";

    /**
     * Asset Account Type.
     */
    protected static final String QIFACT_ASSET = "Oth A";

    /**
     * Loan Account Type.
     */
    protected static final String QIFACT_LOAN = "Oth L";

    /**
     * Category Map.
     */
    protected static final Map<Enum<?>, String> QIF_ACTCATMAP = createClassMap();

    /**
     * The Account Name.
     */
    private final String theName;

    /**
     * The Account Description.
     */
    private final String theDesc;

    /**
     * The Account CategoryClass.
     */
    private final Enum<?> theClass;

    /**
     * Constructor.
     *
     * @param pAccount the Account
     */
    public MoneyWiseQIFAccount(final MoneyWiseTransAsset pAccount) {
        /* Call super-constructor */
        super(MoneyWiseQAccountLineType.class);

        /* Store data */
        theName = pAccount.getName();

        /* Switch on account */
        switch (pAccount) {
            case MoneyWiseDeposit myDeposit -> {
                theClass = myDeposit.getCategoryClass();
                theDesc = myDeposit.getDesc();
            }
            case MoneyWiseCash myCash -> {
                theClass = myCash.getCategoryClass();
                theDesc = myCash.getDesc();
            }
            case MoneyWiseLoan myLoan -> {
                theClass = myLoan.getCategoryClass();
                theDesc = myLoan.getDesc();
            }
            case MoneyWisePortfolio myPortfolio -> {
                theClass = MoneyWiseBasicDataType.PORTFOLIO;
                theDesc = myPortfolio.getDesc();
            }
            default -> throw new IllegalArgumentException();
        }

        /* Build lines */
        addLine(new MoneyWiseQIFAccountNameLine(theName));
        if (theDesc != null) {
            addLine(new MoneyWiseQIFAccountDescLine(theDesc));
        }
        addLine(new MoneyWiseQIFAccountTypeLine(theClass));
    }

    /**
     * Constructor for holding account.
     *
     * @param pName the Portfolio Name
     */
    protected MoneyWiseQIFAccount(final String pName) {
        /* Call super-constructor */
        super(MoneyWiseQAccountLineType.class);

        /* Store data */
        theName = pName;
        theClass = MoneyWiseDepositCategoryClass.SAVINGS;
        theDesc = null;

        /* Build lines */
        addLine(new MoneyWiseQIFAccountNameLine(theName));
        addLine(new MoneyWiseQIFAccountTypeLine(theClass));
    }

    /**
     * Constructor.
     *
     * @param pFormatter the formatter
     * @param pLines     the data lines
     */
    protected MoneyWiseQIFAccount(final OceanusDataFormatter pFormatter,
                                  final List<String> pLines) {
        /* Call super-constructor */
        super(MoneyWiseQAccountLineType.class);

        /* Determine details */
        String myName = null;
        String myDesc = null;
        Enum<?> myClass = null;

        /* Loop through the lines */
        for (String myLine : pLines) {
            /* Determine the category */
            final MoneyWiseQAccountLineType myType = MoneyWiseQAccountLineType.parseLine(myLine);
            if (myType != null) {
                /* Access data */
                final String myData = myLine.substring(myType.getSymbol().length());

                /* Switch on line type */
                switch (myType) {
                    case NAME:
                        addLine(new MoneyWiseQIFAccountNameLine(myData));
                        myName = myData;
                        break;
                    case DESCRIPTION:
                        addLine(new MoneyWiseQIFAccountDescLine(myData));
                        myDesc = myData;
                        break;
                    case TYPE:
                        final MoneyWiseQIFAccountTypeLine myQLine = new MoneyWiseQIFAccountTypeLine(myData);
                        addLine(myQLine);
                        myClass = myQLine.getAccountClass();
                        break;
                    case CREDITLIMIT:
                        final OceanusMoney myMoney = pFormatter.getDecimalParser().parseMoneyValue(myData);
                        addLine(new MoneyWiseQIFAccountLimitLine(myMoney));
                        break;
                    default:
                        break;
                }
            }
        }

        /* Build details */
        theName = myName;
        theDesc = myDesc;
        theClass = myClass;
    }

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

    /**
     * Obtain the Name.
     *
     * @return the Name
     */
    public String getName() {
        return theName;
    }

    /**
     * Obtain the description.
     *
     * @return the Name
     */
    public String getDesc() {
        return theDesc;
    }

    /**
     * Obtain the account type.
     *
     * @return the Type
     */
    public String getType() {
        return QIF_ACTCATMAP.get(theClass);
    }

    /**
     * Create the CategoryClass to type map.
     *
     * @return the map
     */
    private static Map<Enum<?>, String> createClassMap() {
        /* Create the map */
        final Map<Enum<?>, String> myMap = new HashMap<>();

        /* Add the entries */
        myMap.put(MoneyWiseDepositCategoryClass.CHECKING, QIFACT_BANK);
        myMap.put(MoneyWiseDepositCategoryClass.SAVINGS, QIFACT_BANK);
        myMap.put(MoneyWiseDepositCategoryClass.PEER2PEER, QIFACT_BANK);
        myMap.put(MoneyWiseDepositCategoryClass.BOND, QIFACT_BANK);
        myMap.put(MoneyWiseCashCategoryClass.CASH, QIFACT_CASH);
        myMap.put(MoneyWiseCashCategoryClass.AUTOEXPENSE, QIFACT_CASH);
        myMap.put(MoneyWiseLoanCategoryClass.CREDITCARD, QIFACT_CCARD);
        myMap.put(MoneyWiseBasicDataType.PORTFOLIO, QIFACT_INVST);
        myMap.put(MoneyWiseLoanCategoryClass.PRIVATELOAN, QIFACT_ASSET);
        myMap.put(MoneyWiseLoanCategoryClass.LOAN, QIFACT_LOAN);

        /* Return the map */
        return myMap;
    }

    @Override
    public int compareTo(final MoneyWiseQIFAccount pThat) {
        return theName.compareTo(pThat.getName());
    }

    /**
     * The Account Name line.
     */
    public static class MoneyWiseQIFAccountNameLine
            extends MoneyWiseQIFStringLine<MoneyWiseQAccountLineType> {
        /**
         * Constructor.
         *
         * @param pName the Name
         */
        protected MoneyWiseQIFAccountNameLine(final String pName) {
            /* Call super-constructor */
            super(pName);
        }

        @Override
        public MoneyWiseQAccountLineType getLineType() {
            return MoneyWiseQAccountLineType.NAME;
        }

        /**
         * Obtain name.
         *
         * @return the name
         */
        public String getName() {
            return getValue();
        }
    }

    /**
     * The Security Symbol line.
     */
    public static class MoneyWiseQIFAccountDescLine
            extends MoneyWiseQIFStringLine<MoneyWiseQAccountLineType> {
        /**
         * Constructor.
         *
         * @param pDesc the Description
         */
        protected MoneyWiseQIFAccountDescLine(final String pDesc) {
            /* Call super-constructor */
            super(pDesc);
        }

        @Override
        public MoneyWiseQAccountLineType getLineType() {
            return MoneyWiseQAccountLineType.DESCRIPTION;
        }

        /**
         * Obtain description.
         *
         * @return the description
         */
        public String getDesc() {
            return getValue();
        }
    }

    /**
     * The Account Type line.
     */
    public static class MoneyWiseQIFAccountTypeLine
            extends MoneyWiseQIFStringLine<MoneyWiseQAccountLineType> {
        /**
         * The Account Category Class.
         */
        private final Enum<?> theClass;

        /**
         * Constructor.
         *
         * @param pClass the Account Class
         */
        protected MoneyWiseQIFAccountTypeLine(final Enum<?> pClass) {
            /* Call super-constructor */
            super(QIF_ACTCATMAP.get(pClass));

            /* Record the class */
            theClass = pClass;
        }

        /**
         * Constructor.
         *
         * @param pType the Account Type
         */
        protected MoneyWiseQIFAccountTypeLine(final String pType) {
            /* Call super-constructor */
            super(pType);

            /* Loop through the map entries */
            Enum<?> myClass = null;
            for (Entry<Enum<?>, String> myEntry : QIF_ACTCATMAP.entrySet()) {
                /* If we have a match */
                if (pType.equals(myEntry.getValue())) {
                    myClass = myEntry.getKey();
                    break;
                }
            }

            /* Store the class */
            theClass = myClass;
        }

        @Override
        public MoneyWiseQAccountLineType getLineType() {
            return MoneyWiseQAccountLineType.TYPE;
        }

        @Override
        public String toString() {
            return theClass.toString();
        }

        /**
         * Obtain account class.
         *
         * @return the account class
         */
        public Enum<?> getAccountClass() {
            return theClass;
        }
    }

    /**
     * The Account Credit Limit line.
     */
    public static class MoneyWiseQIFAccountLimitLine
            extends MoneyWiseQIFMoneyLine<MoneyWiseQAccountLineType> {
        /**
         * Constructor.
         *
         * @param pLimit the Credit Limit
         */
        protected MoneyWiseQIFAccountLimitLine(final OceanusMoney pLimit) {
            /* Call super-constructor */
            super(pLimit);
        }

        @Override
        public MoneyWiseQAccountLineType getLineType() {
            return MoneyWiseQAccountLineType.CREDITLIMIT;
        }

        /**
         * Obtain credit limit.
         *
         * @return the credit limit
         */
        public OceanusMoney getCreditLimit() {
            return getMoney();
        }
    }

    /**
     * The Account line.
     *
     * @param <X> the line type
     */
    public abstract static class MoneyWiseQIFXferAccountLine<X extends MoneyWiseQLineType>
            extends MoneyWiseQIFLine<X> {
        /**
         * The account.
         */
        private final MoneyWiseQIFAccount theAccount;

        /**
         * The class list.
         */
        private final List<MoneyWiseQIFClass> theClasses;

        /**
         * Constructor.
         *
         * @param pAccount the Account
         */
        protected MoneyWiseQIFXferAccountLine(final MoneyWiseQIFAccount pAccount) {
            this(pAccount, null);
        }

        /**
         * Constructor.
         *
         * @param pAccount the Account
         * @param pClasses the classes
         */
        protected MoneyWiseQIFXferAccountLine(final MoneyWiseQIFAccount pAccount,
                                              final List<MoneyWiseQIFClass> pClasses) {
            /* Store data */
            theAccount = pAccount;
            theClasses = pClasses;
        }

        @Override
        public String toString() {
            return theAccount.toString();
        }

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

        /**
         * Obtain class list.
         *
         * @return the class list
         */
        public List<MoneyWiseQIFClass> getClassList() {
            return theClasses;
        }

        @Override
        protected void formatData(final OceanusDataFormatter pFormatter,
                                  final StringBuilder pBuilder) {
            /* Append the string data */
            pBuilder.append(MoneyWiseQIFConstants.QIF_XFERSTART);
            pBuilder.append(theAccount.getName());
            pBuilder.append(MoneyWiseQIFConstants.QIF_XFEREND);

            /* If we have classes */
            if (theClasses != null) {
                /* Add class indicator */
                pBuilder.append(MoneyWiseQIFConstants.QIF_CLASS);

                /* Iterate through the list */
                final Iterator<MoneyWiseQIFClass> myIterator = theClasses.iterator();
                while (myIterator.hasNext()) {
                    final MoneyWiseQIFClass myClass = myIterator.next();

                    /* Add to the list */
                    pBuilder.append(myClass.getName());
                    if (myIterator.hasNext()) {
                        pBuilder.append(MoneyWiseQIFConstants.QIF_CLASSSEP);
                    }
                }
            }
        }

        @Override
        public boolean equals(final Object pThat) {
            /* Handle trivial cases */
            if (this == pThat) {
                return true;
            }
            if (pThat == null) {
                return false;
            }

            /* Check class */
            if (!getClass().equals(pThat.getClass())) {
                return false;
            }

            /* Cast correctly */
            final MoneyWiseQIFXferAccountLine<?> myLine = (MoneyWiseQIFXferAccountLine<?>) pThat;

            /* Check line type */
            if (!getLineType().equals(myLine.getLineType())) {
                return false;
            }

            /* Check account */
            if (!theAccount.equals(myLine.getAccount())) {
                return false;
            }

            /* Check classes */
            final List<MoneyWiseQIFClass> myClasses = myLine.getClassList();
            if (theClasses == null) {
                return myClasses == null;
            } else if (myClasses == null) {
                return true;
            }
            return theClasses.equals(myClasses);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getLineType(), theClasses, theAccount);
        }
    }
}