/*
 * 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.fp.document.web.struts;

import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelper;
import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelperBase;
import org.kuali.kfs.fp.document.VoucherDocument;
import org.kuali.kfs.kns.question.ConfirmationQuestion;
import org.kuali.kfs.kns.util.KNSGlobalVariables;
import org.kuali.kfs.kns.web.struts.form.KualiDocumentFormBase;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AmountTotaling;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.core.web.format.CurrencyFormatter;
import org.kuali.rice.kew.api.exception.WorkflowException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;

/**
 * This class piggy backs on all of the functionality in the FinancialSystemTransactionalDocumentActionBase but is necessary for this document
 * type. Vouchers are unique in that they define several fields that aren't typically used by the other financial transaction
 * processing eDocs (i.e. external system fields, object type override, credit and debit amounts).
 */
public class VoucherAction extends KualiAccountingDocumentActionBase {
    // used to determine which way the change balance type action is switching
    // these are local constants only used within this action class
    // these should not be used outside of this class

    /**
     * Overrides to call super, and then to repopulate the credit/debit amounts b/c the credit/debit code might change during a
     * voucher error correction.
     *
     * @see org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentActionBase#correct(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward correct(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ActionForward actionForward = super.correct(mapping, form, request, response);

        VoucherForm vForm = (VoucherForm) form;

        // now make sure to repopulate credit/debit amounts
        populateAllVoucherAccountingLineHelpers(vForm);

        return actionForward;
    }

    /**
     * Overrides parent to first populate the new source line with the correct debit or credit value, then it calls the parent's
     * implementation.
     *
     * @see org.kuali.module.financial.web.struts.action.KualiFinancialDocumentActionBase#insertSourceLine(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward insertSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // cast the form to the right pojo
        VoucherForm voucherForm = (VoucherForm) form;

        // call the super's method
        ActionForward actionForward = super.insertSourceLine(mapping, form, request, response);

        if (GlobalVariables.getMessageMap().getErrorCount() == 0) {
            // since no exceptions were thrown, the add succeeded, so we have to re-init the new credit and debit
            // attributes, and add a new instance of a helperLine to the helperLines list
            VoucherAccountingLineHelper helperLine = populateNewVoucherAccountingLineHelper(voucherForm);
            voucherForm.getVoucherLineHelpers().add(helperLine);

            // now reset the debit and credit fields for adds
            voucherForm.setNewSourceLineDebit(KualiDecimal.ZERO);
            voucherForm.setNewSourceLineCredit(KualiDecimal.ZERO);
        }

        return actionForward;
    }

    /**
     * Overrides parent to remove the associated helper line also, and then it call the parent's implementation.
     *
     * @see org.kuali.module.financial.web.struts.action.KualiFinancialDocumentActionBase#deleteSourceLine(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward deleteSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // cast the form to the right pojo
        VoucherForm voucherForm = (VoucherForm) form;

        // call the super's method
        ActionForward actionForward = super.deleteSourceLine(mapping, voucherForm, request, response);

        // now remove the associated helper line
        int index = getLineToDelete(request);
        if (voucherForm.getVoucherLineHelpers() != null && voucherForm.getVoucherLineHelpers().size() > index) {
            voucherForm.getVoucherLineHelpers().remove(getLineToDelete(request));
        }

        return actionForward;
    }

    /**
     * Overrides the parent to make sure that the AV specific accounting line helper forms are properly populated when the document
     * is first loaded. This first calls super, then populates the helper objects.
     *
     * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
     */
    @Override
    protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
        super.loadDocument(kualiDocumentFormBase);
        VoucherForm voucherForm = (VoucherForm) kualiDocumentFormBase;

        populateAllVoucherAccountingLineHelpers(voucherForm);
        voucherForm.setNewSourceLineCredit(KualiDecimal.ZERO);
        voucherForm.setNewSourceLineDebit(KualiDecimal.ZERO);

        // always wipe out the new source line
        voucherForm.setNewSourceLine(null);

        // reload the accounting period selections since now we have data in the document bo
        populateSelectedAccountingPeriod(voucherForm.getVoucherDocument(), voucherForm);
    }

    /**
     * This method parses the accounting period value from the bo and builds the right string to pass to the form object as the
     * selected value.
     *
     * @param voucherDocument
     * @param voucherForm
     */
    protected void populateSelectedAccountingPeriod(VoucherDocument voucherDocument, VoucherForm voucherForm) {
        if (StringUtils.isNotBlank(voucherDocument.getPostingPeriodCode())) {
            String selectedAccountingPeriod = voucherDocument.getPostingPeriodCode();
            if (null != voucherDocument.getPostingYear()) {
                selectedAccountingPeriod += voucherDocument.getPostingYear().toString();
            } else {
                selectedAccountingPeriod += SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear().toString();
            }
            voucherForm.setSelectedAccountingPeriod(selectedAccountingPeriod);
        }
    }

    /**
     * This populates a new helperLine instance with the one that was just added so that the new instance can be added to the
     * helperLines list.
     *
     * @param voucherForm
     * @return VoucherAccountingLineHelper
     */
    protected VoucherAccountingLineHelper populateVoucherAccountingLineHelper(VoucherForm voucherForm) {
        VoucherAccountingLineHelper helperLine = new VoucherAccountingLineHelperBase();

        KualiDecimal debitAmount = voucherForm.getNewSourceLineDebit();
        if (debitAmount != null && StringUtils.isNotBlank(debitAmount.toString())) {
            helperLine.setDebit(debitAmount);
        }

        KualiDecimal creditAmount = voucherForm.getNewSourceLineCredit();
        if (creditAmount != null && StringUtils.isNotBlank(creditAmount.toString())) {
            helperLine.setCredit(creditAmount);
        }

        return helperLine;
    }

    /**
     * This method builds the corresponding list of voucher acounting line helper objects so that a user can differentiate between
     * credit and debit fields. It does this by iterating over each source accounting line (what the voucher uses) looking at the
     * debit/credit code and then populateingLineHelpers a corresponding helper form instance with the amount in the appropriate
     * amount field - credit or debit.
     *
     * @param voucherForm
     */
    protected void populateAllVoucherAccountingLineHelpers(VoucherForm voucherForm) {
        // make sure the journal voucher accounting line helper form list is populated properly
        ArrayList voucherLineHelpers = (ArrayList) voucherForm.getVoucherLineHelpers();

        // make sure the helper list is the right size
        VoucherDocument vDoc = (VoucherDocument) voucherForm.getTransactionalDocument();
        int size = vDoc.getSourceAccountingLines().size();
        voucherLineHelpers.ensureCapacity(size);

        // iterate through each source accounting line and initialize the helper form lines appropriately
        for (int i = 0; i < size; i++) {
            // get the bo's accounting line at the right index
            SourceAccountingLine sourceAccountingLine = vDoc.getSourceAccountingLine(i);

            // instantiate a new helper form to use for populating the helper form list
            VoucherAccountingLineHelper avAcctLineHelperForm = voucherForm.getVoucherLineHelper(i);

            // figure whether we need to set the credit amount or the debit amount
            if (StringUtils.isNotBlank(sourceAccountingLine.getDebitCreditCode())) {
                if (sourceAccountingLine.getDebitCreditCode().equals(KFSConstants.GL_DEBIT_CODE)) {
                    avAcctLineHelperForm.setDebit(sourceAccountingLine.getAmount());
                    avAcctLineHelperForm.setCredit(KualiDecimal.ZERO);
                } else if (sourceAccountingLine.getDebitCreditCode().equals(KFSConstants.GL_CREDIT_CODE)) {
                    avAcctLineHelperForm.setCredit(sourceAccountingLine.getAmount());
                    avAcctLineHelperForm.setDebit(KualiDecimal.ZERO);
                }
            }
        }
    }


    /**
     * This helper method determines from the request object instance whether or not the user has been prompted about the journal
     * being out of balance. If they haven't, then the method will build the appropriate message given the state of the document and
     * return control to the question component so that the user receives the "yes"/"no" prompt. If the question has been asked, the
     * we evaluate the user's answer and direct the flow appropriately. If they answer with a "No", then we build out a message
     * stating that they chose that value and return an ActionForward of a MAPPING_BASIC which keeps them at the same page that they
     * were on. If they choose "Yes", then we return a null ActionForward, which the calling action method recognizes as a "Yes" and
     * continues on processing the "Route."
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return ActionForward
     * @throws Exception
     */
    protected ActionForward processRouteOutOfBalanceDocumentConfirmationQuestion(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        VoucherForm vForm = (VoucherForm) form;
        VoucherDocument avDoc = vForm.getVoucherDocument();

        String question = request.getParameter(KFSConstants.QUESTION_INST_ATTRIBUTE_NAME);
        ConfigurationService kualiConfiguration = SpringContext.getBean(ConfigurationService.class);

        if (question == null) { // question hasn't been asked
            String currencyFormattedDebitTotal = (String) new CurrencyFormatter().format(avDoc.getDebitTotal());
            String currencyFormattedCreditTotal = (String) new CurrencyFormatter().format(avDoc.getCreditTotal());
            String currencyFormattedTotal = (String) new CurrencyFormatter().format(((AmountTotaling) avDoc).getTotalDollarAmount());
            String message = "";
            message = StringUtils.replace(kualiConfiguration.getPropertyValueAsString(KFSKeyConstants.QUESTION_ROUTE_OUT_OF_BALANCE_JV_DOC), "{0}", currencyFormattedDebitTotal);
            message = StringUtils.replace(message, "{1}", currencyFormattedCreditTotal);

            // now transfer control over to the question component
            return this.performQuestionWithoutInput(mapping, form, request, response, KFSConstants.JOURNAL_VOUCHER_ROUTE_OUT_OF_BALANCE_DOCUMENT_QUESTION, message, KFSConstants.CONFIRMATION_QUESTION, KFSConstants.ROUTE_METHOD, "");
        } else {
            String buttonClicked = request.getParameter(KFSConstants.QUESTION_CLICKED_BUTTON);
            if ((KFSConstants.JOURNAL_VOUCHER_ROUTE_OUT_OF_BALANCE_DOCUMENT_QUESTION.equals(question)) && ConfirmationQuestion.NO.equals(buttonClicked)) {
                KNSGlobalVariables.getMessageList().add(KFSKeyConstants.MESSAGE_JV_CANCELLED_ROUTE);
                return mapping.findForward(KFSConstants.MAPPING_BASIC);
            }
        }
        return null;
    }

    /**
     * This populates a new helperLine instance with the one that was just added so that the new instance can be added to the
     * helperLines list.
     *
     * @param voucherForm
     * @return voucherAccountingLineHelper
     */
    protected VoucherAccountingLineHelper populateNewVoucherAccountingLineHelper(VoucherForm voucherForm) {
        VoucherAccountingLineHelper helperLine = new VoucherAccountingLineHelperBase();

        KualiDecimal debitAmount = voucherForm.getNewSourceLineDebit();
        if (debitAmount != null && StringUtils.isNotBlank(debitAmount.toString())) {
            helperLine.setDebit(debitAmount);
        }

        KualiDecimal creditAmount = voucherForm.getNewSourceLineCredit();
        if (creditAmount != null && StringUtils.isNotBlank(creditAmount.toString())) {
            helperLine.setCredit(creditAmount);
        }

        return helperLine;
    }

    /**
     * This action executes a call to upload CSV accounting line values as SourceAccountingLines for a given transactional document.
     * The "uploadAccountingLines()" method handles the multi-part request.
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return ActionForward
     * @throws FileNotFoundException
     * @throws IOException
     */
    @Override
    public ActionForward uploadSourceLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws FileNotFoundException, IOException {
        // call method that sourceform and destination list
        uploadAccountingLines(true, form);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * This method determines whether we are uploading source or target lines, and then calls uploadAccountingLines directly on the
     * document object. This method handles retrieving the actual upload file as an input stream into the document.
     *
     * @param isSource
     * @param form
     * @throws FileNotFoundException
     * @throws IOException
     */
    @Override
    protected void uploadAccountingLines(boolean isSource, ActionForm form) throws FileNotFoundException, IOException {
        super.uploadAccountingLines(isSource, form);

        populateAllVoucherAccountingLineHelpers((VoucherForm) form);
    }

    /**
     * Overridden to reset the available and selected accounting periods on the form, so that copies are moved forward to the current accounting period correctly
     *
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#copy(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward copy(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ActionForward forward = super.copy(mapping, form, request, response);
        VoucherForm voucherForm = (VoucherForm) form;
        voucherForm.populateAccountingPeriodListForRendering();
        voucherForm.populateDefaultSelectedAccountingPeriod();
        return forward;
    }
}
