/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.coa.businessobject;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.krad.bo.GlobalBusinessObject;
import org.kuali.kfs.krad.bo.GlobalBusinessObjectDetail;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.bo.PersistableBusinessObjectBase;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.PersistenceStructureService;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;

import java.sql.Date;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * This class simply acts as a container to hold the List of Delegate Changes and the list of Account entries, for the Global
 * Delegate Change Document.
 */
public class AccountDelegateGlobal extends PersistableBusinessObjectBase implements GlobalBusinessObject {

    protected String documentNumber;

    protected String modelName;
    protected String modelChartOfAccountsCode;
    protected String modelOrganizationCode;

    protected AccountDelegateModel model;

    protected List<AccountGlobalDetail> accountGlobalDetails;
    protected List<AccountDelegateGlobalDetail> delegateGlobals;

    /**
     * Constructs a DelegateGlobal.java.
     */
    public AccountDelegateGlobal() {
        super();
        accountGlobalDetails = new ArrayList<AccountGlobalDetail>();
        delegateGlobals = new ArrayList<AccountDelegateGlobalDetail>();
    }

    /**
     * This method adds a single AccountGlobalDetail instance to the list. If one is already present in the list with the same
     * chartCode and accountNumber, then this new one will not be added.
     *
     * @param accountGlobalDetail - populated AccountGlobalDetail instance
     */
    public void addAccount(AccountGlobalDetail accountGlobalDetail) {

        // validate the argument
        if (accountGlobalDetail == null) {
            throw new IllegalArgumentException("The accountGlobalDetail instanced passed in was null.");
        } else if (StringUtils.isBlank(accountGlobalDetail.getChartOfAccountsCode())) {
            throw new IllegalArgumentException("The chartOfAccountsCode member of the accountGlobalDetail object was not populated.");
        } else if (StringUtils.isBlank(accountGlobalDetail.getAccountNumber())) {
            throw new IllegalArgumentException("The accountNumber member of the accountGlobalDetail object was not populated.");
        }

        // add the object if one doesnt already exist, otherwise silently do nothing
        AccountGlobalDetail testObject = getAccount(accountGlobalDetail.getChartOfAccountsCode(), accountGlobalDetail.getAccountNumber());
        if (testObject == null) {
            this.accountGlobalDetails.add(accountGlobalDetail);
        }
    }

    /**
     * This method retrieves the specific AccountGlobalDetail object that corresponds to your requested chartCode and accountNumber
     * (or a null object if there is no match).
     *
     * @param chartCode
     * @param accountNumber
     * @return returns the AccountGlobalDetail instance matching the chartCode & accountNumber passed in, or Null if none match
     */
    public AccountGlobalDetail getAccount(String chartCode, String accountNumber) {

        // validate the argument
        if (StringUtils.isBlank(chartCode)) {
            throw new IllegalArgumentException("The chartCode argument was null or empty.");
        } else if (StringUtils.isBlank(accountNumber)) {
            throw new IllegalArgumentException("The accountNumber argument was null or empty.");
        }

        // walk the list of AccountGlobalDetail objects
        for (Iterator iter = this.accountGlobalDetails.iterator(); iter.hasNext(); ) {
            AccountGlobalDetail accountGlobalDetail = (AccountGlobalDetail) iter.next();

            // if this one is a match, then quit
            if (chartCode.equalsIgnoreCase(accountGlobalDetail.getChartOfAccountsCode()) && accountNumber.equalsIgnoreCase(accountGlobalDetail.getAccountNumber())) {
                return accountGlobalDetail;
            }
        }

        // we return null if one is not found
        return null;
    }

    /**
     * @see org.kuali.rice.krad.document.GlobalBusinessObject#getGlobalChangesToDelete()
     */
    @Override
    public List<PersistableBusinessObject> generateDeactivationsToPersist() {
        BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class);

        // retreive all the existing delegates for these accounts
        List<AccountDelegate> bosToDeactivate = new ArrayList();
        Map<String, Object> fieldValues;
        Collection existingDelegates;
        for (AccountGlobalDetail accountDetail : getAccountGlobalDetails()) {
            fieldValues = new HashMap();
            fieldValues.put("chartOfAccountsCode", accountDetail.getChartOfAccountsCode());
            fieldValues.put("accountNumber", accountDetail.getAccountNumber());
            fieldValues.put("active", true);
            existingDelegates = boService.findMatching(AccountDelegate.class, fieldValues);
            bosToDeactivate.addAll(existingDelegates);
        }

        // mark all the delegates as inactive
        for (AccountDelegate accountDelegate : bosToDeactivate) {
            accountDelegate.setActive(false);
        }
        return new ArrayList<PersistableBusinessObject>(bosToDeactivate);
    }

    /**
     * @see org.kuali.rice.krad.document.GlobalBusinessObject#applyGlobalChanges(org.kuali.rice.krad.bo.BusinessObject)
     */
    @Override
    @SuppressWarnings("deprecation")
    public List<PersistableBusinessObject> generateGlobalChangesToPersist() {

        BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class);
        List<AccountDelegate> persistables = new ArrayList();

        List<AccountDelegateGlobalDetail> changeDocuments = this.getDelegateGlobals();
        List<AccountGlobalDetail> accountDetails = this.getAccountGlobalDetails();

        for (AccountDelegateGlobalDetail changeDocument : changeDocuments) {
            for (AccountGlobalDetail accountDetail : accountDetails) {

                Account account = boService.findByPrimaryKey(Account.class, accountDetail.getPrimaryKeys());

                // if the account doesnt exist, fail fast, as this should never happen,
                // the busines rules for this document should have caught this.
                if (account == null) {
                    throw new RuntimeException("Account [" + accountDetail.getChartOfAccountsCode() + "-" + accountDetail.getAccountNumber() + "] was not present in the database. " + "This should never happen under normal circumstances, as an invalid account should have " + "been caught by the Business Rules infrastructure.");
                }

                // attempt to load the existing Delegate from the DB, if it exists. we do this to avoid
                // versionNumber conflicts if we tried to just insert a new record that already existed.
                Map pkMap = new HashMap();
                pkMap.putAll(accountDetail.getPrimaryKeys()); // chartOfAccountsCode & accountNumber
                pkMap.put("financialDocumentTypeCode", changeDocument.getFinancialDocumentTypeCode());
                pkMap.put("accountDelegateSystemId", changeDocument.getAccountDelegateUniversalId());
                AccountDelegate delegate = boService.findByPrimaryKey(AccountDelegate.class, pkMap);

                // if there is no existing Delegate with these primary keys, then we're creating a new one,
                // so lets populate it with the primary keys
                if (delegate == null) {
                    delegate = new AccountDelegate();
                    delegate.setChartOfAccountsCode(accountDetail.getChartOfAccountsCode());
                    delegate.setAccountNumber(accountDetail.getAccountNumber());
                    delegate.setAccountDelegateSystemId(changeDocument.getAccountDelegateUniversalId());
                    delegate.setFinancialDocumentTypeCode(changeDocument.getFinancialDocumentTypeCode());
                    delegate.setActive(true);
                } else {
                    delegate.setActive(true);
                }

                // APPROVAL FROM AMOUNT
                delegate.setFinDocApprovalFromThisAmt(changeDocument.getApprovalFromThisAmount());

                // APPROVAL TO AMOUNT
                delegate.setFinDocApprovalToThisAmount(changeDocument.getApprovalToThisAmount());

                // PRIMARY ROUTING
                delegate.setAccountsDelegatePrmrtIndicator(changeDocument.getAccountDelegatePrimaryRoutingIndicator());

                // START DATE
                if (changeDocument.getAccountDelegateStartDate() != null) {
                    delegate.setAccountDelegateStartDate(new Date(changeDocument.getAccountDelegateStartDate().getTime()));
                }

                persistables.add(delegate);

            }
        }

        return new ArrayList<PersistableBusinessObject>(persistables);
    }

    /**
     * @see org.kuali.rice.krad.bo.BusinessObjectBase#toStringMapper()
     */

    protected LinkedHashMap toStringMapper_RICE20_REFACTORME() {

        LinkedHashMap m = new LinkedHashMap();

        m.put(KFSPropertyConstants.DOCUMENT_NUMBER, this.documentNumber);
        return m;
    }

    /**
     * @see org.kuali.rice.krad.document.GlobalBusinessObject#getDocumentNumber()
     */
    @Override
    public String getDocumentNumber() {
        return documentNumber;
    }

    /**
     * @see org.kuali.rice.krad.document.GlobalBusinessObject#setDocumentNumber(java.lang.String)
     */
    @Override
    public void setDocumentNumber(String documentNumber) {
        this.documentNumber = documentNumber;

    }

    /**
     * Gets the accountGlobalDetails attribute.
     *
     * @return Returns the accountGlobalDetails.
     */
    public final List<AccountGlobalDetail> getAccountGlobalDetails() {
        return accountGlobalDetails;
    }

    /**
     * Sets the accountGlobalDetails attribute value.
     *
     * @param accountGlobalDetails The accountGlobalDetails to set.
     */
    public final void setAccountGlobalDetails(List<AccountGlobalDetail> accountGlobalDetails) {
        this.accountGlobalDetails = accountGlobalDetails;
    }

    /**
     * Gets the delegateGlobals attribute.
     *
     * @return Returns the delegateGlobals.
     */
    public final List<AccountDelegateGlobalDetail> getDelegateGlobals() {
        return delegateGlobals;
    }

    /**
     * Sets the delegateGlobals attribute value.
     *
     * @param delegateGlobals The delegateGlobals to set.
     */
    public final void setDelegateGlobals(List<AccountDelegateGlobalDetail> delegateGlobals) {
        this.delegateGlobals = delegateGlobals;
    }

    /**
     * @see org.kuali.rice.krad.document.GlobalBusinessObject#isPersistable()
     */
    @Override
    public boolean isPersistable() {
        PersistenceStructureService persistenceStructureService = SpringContext.getBean(PersistenceStructureService.class);

        // fail if the PK for this object is emtpy
        if (StringUtils.isBlank(documentNumber)) {
            return false;
        }

        // fail if the PKs for any of the contained objects are empty
        for (AccountDelegateGlobalDetail delegateGlobals : getDelegateGlobals()) {
            if (!persistenceStructureService.hasPrimaryKeyFieldValues(delegateGlobals)) {
                return false;
            }
        }
        for (AccountGlobalDetail account : getAccountGlobalDetails()) {
            if (!persistenceStructureService.hasPrimaryKeyFieldValues(account)) {
                return false;
            }
        }

        // otherwise, its all good
        return true;
    }

    public String getModelName() {
        return modelName;
    }

    public void setModelName(String loadModelName) {
        this.modelName = loadModelName;
    }

    public String getModelChartOfAccountsCode() {
        return modelChartOfAccountsCode;
    }

    public void setModelChartOfAccountsCode(String loadModelChartOfAccountsCode) {
        this.modelChartOfAccountsCode = loadModelChartOfAccountsCode;
    }

    public String getModelOrganizationCode() {
        return modelOrganizationCode;
    }

    public void setModelOrganizationCode(String loadModelOrganizationCode) {
        this.modelOrganizationCode = loadModelOrganizationCode;
    }

    public AccountDelegateModel getModel() {
        return model;
    }

    public void setModel(AccountDelegateModel loadModel) {
        this.model = loadModel;
    }

    @Override
    public List<? extends GlobalBusinessObjectDetail> getAllDetailObjects() {
        ArrayList<GlobalBusinessObjectDetail> details = new ArrayList<GlobalBusinessObjectDetail>(accountGlobalDetails.size() + delegateGlobals.size());
        details.addAll(accountGlobalDetails);
        details.addAll(delegateGlobals);
        return details;
    }

    @Override
    public void linkEditableUserFields() {
        super.linkEditableUserFields();
        if (this == null) {
            throw new IllegalArgumentException("globalDelegate parameter passed in was null");
        }
        List<PersistableBusinessObject> bos = new ArrayList<PersistableBusinessObject>();
        bos.addAll(getDelegateGlobals());
        SpringContext.getBean(BusinessObjectService.class).linkUserFields(bos);
    }

    /**
     * @see org.kuali.rice.krad.bo.PersistableBusinessObjectBase#buildListOfDeletionAwareLists()
     */
    @Override
    public List<Collection<PersistableBusinessObject>> buildListOfDeletionAwareLists() {
        List<Collection<PersistableBusinessObject>> managedLists = super.buildListOfDeletionAwareLists();

        managedLists.add(new ArrayList<PersistableBusinessObject>(getAccountGlobalDetails()));
        managedLists.add(new ArrayList<PersistableBusinessObject>(getDelegateGlobals()));

        return managedLists;
    }
}
