/*
 * 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.module.tem.document.authorization;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.krad.document.Document;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.TemConstants.TravelAuthorizationStatusCodeKeys;
import org.kuali.kfs.module.tem.TemConstants.TravelDocTypes;
import org.kuali.kfs.module.tem.TemPropertyConstants;
import org.kuali.kfs.module.tem.TemWorkflowConstants;
import org.kuali.kfs.module.tem.document.TravelAuthorizationDocument;
import org.kuali.kfs.module.tem.document.TravelReimbursementDocument;
import org.kuali.kfs.module.tem.document.service.TravelDocumentService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kim.api.identity.Person;

import java.util.List;
import java.util.Set;

/**
 * Travel Reimbursement Document Presentation Controller
 */
public class TravelAuthorizationDocumentPresentationController extends TravelAuthorizationFamilyDocumentPresentationController {
    /**
     * @see org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentPresentationControllerBase#getEditModes(org.kuali.rice.kns.document.Document)
     */
    @Override
    public Set<String> getEditModes(Document document) {

        WorkflowDocument wfDocument = document.getDocumentHeader().getWorkflowDocument();

        Set<String> editModes = super.getEditModes(document);
        addFullEntryEditMode(document, editModes);
        editModes.remove(TemConstants.EditModes.CHECK_AMOUNT_ENTRY);
        editModes.add(TemConstants.TravelEditMode.ADVANCE_PAYMENT_ENTRY);
        if (shouldAllowBlanketTravelEntry(document)) {
            editModes.add(TemConstants.EditModes.BLANKET_TRAVEL_ENTRY);
        }
        if (!wfDocument.isInitiated() && !wfDocument.isSaved()) {
            editModes.add(TemConstants.EditModes.BLANKET_TRAVEL_VIEW);
        }

        if (document instanceof TravelAuthorizationDocument && ((TravelAuthorizationDocument) document).shouldProcessAdvanceForDocument() && isAtTravelerNode(wfDocument) || wfDocument.isInitiated() || wfDocument.isSaved()) {
            editModes.add(TemConstants.TravelEditMode.ADVANCE_POLICY_ENTRY);
        }
        if (document instanceof TravelAuthorizationDocument && ((TravelAuthorizationDocument) document).shouldProcessAdvanceForDocument() && isAtTravelNode(wfDocument)) {
            editModes.add(TemConstants.TravelEditMode.CLEAR_ADVANCE_MODE);
        }

        final Set<String> nodeNames = document.getDocumentHeader().getWorkflowDocument().getNodeNames();
        if (wfDocument.isInitiated() || wfDocument.isSaved() || (nodeNames != null && !nodeNames.isEmpty() && (nodeNames.contains(TemWorkflowConstants.RouteNodeNames.TAX) || nodeNames.contains(TemWorkflowConstants.RouteNodeNames.AP_TRAVEL)))) {
            editModes.add(TemConstants.EditModes.EXPENSE_TAXABLE_MODE);
        }

        if (wfDocument.isInitiated() || wfDocument.isSaved() || (nodeNames != null && !nodeNames.isEmpty() && nodeNames.contains(TemWorkflowConstants.RouteNodeNames.AP_TRAVEL))) {
            editModes.add(TemConstants.EditModes.EXPENSE_LIMIT_ENTRY);
        }

        return editModes;
    }

    /**
     * Determines whether blanket travel selection will be given at all on the travel authorization document.  If a trip type is not
     * selected, or the trip type does not allow blanket travel, then the blanket travel indicator will not be shown; otherwise, if
     * there is a trip type and it allows blanket travel, the selection option will be shown
     *
     * @return true if the blanket travel selection should be shown, false otherwise
     */
    protected boolean shouldAllowBlanketTravelEntry(Document document) {
        if (!(document instanceof TravelAuthorizationDocument)) {
            return false; // also, you're using the wrong damn authorizer
        }
        final TravelAuthorizationDocument travelAuthorization = (TravelAuthorizationDocument) document;
        if ((!travelAuthorization.getDocumentHeader().getWorkflowDocument().isInitiated() && !travelAuthorization.getDocumentHeader().getWorkflowDocument().isSaved()) || StringUtils.isBlank(travelAuthorization.getTripTypeCode())) {
            return false;
        }
        if (ObjectUtils.isNull(travelAuthorization.getTripType())) {
            travelAuthorization.refreshReferenceObject(TemPropertyConstants.TRIP_TYPE);
        }
        return !ObjectUtils.isNull(travelAuthorization.getTripType()) && travelAuthorization.getTripType().isBlanketTravel();

    }

    /**
     * Determines if the current workflow document is at the Travel node
     *
     * @param workflowDocument the workflow document to check the node of
     * @return true if the document is at the Travel node, false otherwise
     */
    public boolean isAtTravelNode(WorkflowDocument workflowDocument) {
        return workflowDocument.getCurrentNodeNames().contains(TemWorkflowConstants.RouteNodeNames.AP_TRAVEL);
    }


    /**
     * Determines if the current workflow document is at the Traveler node
     *
     * @param workflowDocument the workflow document to check the node of
     * @return true if the document is at the Traveler node, false otherwise
     */
    public boolean isAtTravelerNode(WorkflowDocument workflowDocument) {
        return workflowDocument.getCurrentNodeNames().contains(TemWorkflowConstants.RouteNodeNames.TRAVELER);
    }

    /**
     * Overridden to handle travel authorization specific actions, such as amend, hold, remove hold, close TA, cancel TA, and pay vendor
     *
     * @see org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentPresentationControllerBase#getDocumentActions(org.kuali.rice.krad.document.Document)
     */
    @Override
    public Set<String> getDocumentActions(Document document) {
        Set<String> actions = super.getDocumentActions(document);
        final TravelAuthorizationDocument travelAuth = (TravelAuthorizationDocument) document;
        if (canAmend(travelAuth)) {
            actions.add(TemConstants.TravelAuthorizationActions.CAN_AMEND);
        }
        if (canHold(travelAuth)) {
            actions.add(TemConstants.TravelAuthorizationActions.CAN_HOLD);
        }
        if (canRemoveHold(travelAuth)) {
            actions.add(TemConstants.TravelAuthorizationActions.CAN_REMOVE_HOLD);
        }
        if (canCloseAuthorization(travelAuth)) {
            actions.add(TemConstants.TravelAuthorizationActions.CAN_CLOSE_TA);
        }
        if (canCancelAuthorization(travelAuth)) {
            actions.add(TemConstants.TravelAuthorizationActions.CAN_CANCEL_TA);
        }
        if (canNewReimbursement(travelAuth)) {
            actions.add(TemConstants.TravelAuthorizationActions.CAN_NEW_REIMBURSEMENT);
        }
        if (canPayVendor(travelAuth)) {
            actions.add(TemConstants.TravelAuthorizationActions.CAN_PAY_VENDOR);
        }
        return actions;
    }

    /**
     * Determines whether the authorization is in a state where it can be amended
     *
     * @param document the authorization to test
     * @return true if the authorization can be amended, false otherwise
     */
    public boolean canAmend(TravelAuthorizationDocument document) {
        boolean can = isOpen(document) && (isFinalOrProcessed(document));
        if (can) {
            //If there are TR's that are not canceled or disapproved, disabled amend
            final TravelDocumentService travelDocumentService = SpringContext.getBean(TravelDocumentService.class);
            final List<Document> trRelatedDocumentList = travelDocumentService.getDocumentsRelatedTo(document, TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT);
            for (Document trDocument : trRelatedDocumentList) {
                can &= trDocument.getDocumentHeader().getWorkflowDocument().isCanceled() || trDocument.getDocumentHeader().getWorkflowDocument().isDisapproved();
            }
        }

        return can;
    }

    /**
     * Determines if the authorization can be held
     *
     * @param document the authorization to check
     * @return true if the authorization can be held, false otherwise
     */
    public boolean canHold(TravelAuthorizationDocument document) {
        return isOpen(document) && (isFinalOrProcessed(document));
    }

    /**
     * Determines if the authorization can remove a hold on it
     *
     * @param document the authorization to check
     * @return true if the authorization is held and that hold can be removed, false otherwise
     */
    public boolean canRemoveHold(TravelAuthorizationDocument document) {
        return isHeld(document) && (isFinalOrProcessed(document));
    }

    /**
     * Determines if the authorization can be closed
     *
     * @param document the authorization to check
     * @return true if the authorization can be closed, false otherwise
     */
    public boolean canCloseAuthorization(TravelAuthorizationDocument document) {
        return isOpen(document) && (isFinalOrProcessed(document)) && hasReimbursements(document) && !hasEnrouteReimbursements(document);
    }

    /**
     * Determines if the authorization can be canceled.  Default logic verifies that the authorization is open and has not yet been reimbursed
     *
     * @param document the authorization to check
     * @return true if the authorization can be canceled, false otherwise
     */
    public boolean canCancelAuthorization(TravelAuthorizationDocument document) {
        boolean can = isOpen(document) && (isFinalOrProcessed(document));

        // verify that there are no reimbursements out there for this doc
        if (can) {
            if (hasReimbursements(document)) {
                can = false;
            }
        }
        return can;
    }

    /**
     * Determines if a reimbursement can be initiated for this document. This is done for {@link TravelAuthorizationDocument} instances
     * that have a workflow document status of FINAL or PROCESSED and on documents that do not have a workflow
     * App Doc Status of REIMB_HELD, CANCELLED, PEND_AMENDMENT, CLOSED, or RETIRED_VERSION. Also checks if the person has permission to
     * initiate the target documents. Will not show the new Reimbursement link if a TA already has a TR enroute.
     * <p>
     * If the document is a TAC, the workflow document status must be FINAL and the App Doc Status must be CLOSED.
     * <p>
     * check status of document and don't create if the status is not final or processed
     *
     * @param document
     * @return
     */
    public boolean canNewReimbursement(TravelAuthorizationDocument document) {
        final String documentType = document.getDocumentTypeName();

        boolean documentStatusCheck = isFinalOrProcessed(document);

        final String appDocStatus = document.getApplicationDocumentStatus();
        boolean appDocStatusCheck = (!appDocStatus.equals(TravelAuthorizationStatusCodeKeys.REIMB_HELD)
            && !appDocStatus.equals(TravelAuthorizationStatusCodeKeys.CANCELLED)
            && !appDocStatus.equals(TravelAuthorizationStatusCodeKeys.PEND_AMENDMENT)
            && !appDocStatus.equals(TravelAuthorizationStatusCodeKeys.RETIRED_VERSION)
            && !appDocStatus.equals(TravelAuthorizationStatusCodeKeys.CLOSED));

        boolean statusCheck = documentStatusCheck && appDocStatusCheck;

        Person user = GlobalVariables.getUserSession().getPerson();
        boolean hasInitAccess = false;
        if (getTemRoleService().canAccessTravelDocument(document, user) && !ObjectUtils.isNull(document.getTraveler()) && document.getTemProfileId() != null && !ObjectUtils.isNull(document.getTemProfile())) {
            //check if user also can init other docs
            hasInitAccess = user.getPrincipalId().equals(document.getTraveler().getPrincipalId()) || getTemRoleService().isTravelDocumentArrangerForProfile(documentType, user.getPrincipalId(), document.getTemProfileId()) || getTemRoleService().isTravelArranger(user, document.getTemProfile().getHomeDepartment(), document.getTemProfileId().toString(), documentType);
        }

        boolean checkRelatedDocs = true;

        //check whether there is already an ENROUTE TR
        List<Document> docs = getTravelDocumentService().getDocumentsRelatedTo(document, TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT);
        for (Document doc : docs) {
            TravelReimbursementDocument trDoc = (TravelReimbursementDocument) doc;
            if (trDoc.getDocumentHeader().getWorkflowDocument().isEnroute()) {
                checkRelatedDocs &= false;
            }
        }

        //a TR document can be processed against a closed TA. If the TAC is Final/Closed display the TR link.
        if (StringUtils.equalsIgnoreCase(documentType, TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_CLOSE_DOCUMENT)) {
            documentStatusCheck = document.getDocumentHeader().getWorkflowDocument().isFinal();
            appDocStatusCheck = appDocStatus.equals(TravelAuthorizationStatusCodeKeys.CLOSED);
            statusCheck = documentStatusCheck && appDocStatusCheck;
        }

        return statusCheck && hasInitAccess && checkRelatedDocs;
    }

    /**
     * is this document on hold for reimbursement workflow state?
     *
     * @param document the travel authorization to check
     * @return true if the document is in held status, false otherwise
     */
    protected boolean isHeld(TravelAuthorizationDocument document) {
        return TemConstants.TravelAuthorizationStatusCodeKeys.REIMB_HELD.equals(document.getAppDocStatus());
    }

    /**
     * Determines if the given TravelAuthorizationDocument has reimbursements or not
     *
     * @param document the authorization to check
     * @return true if the authorization has reimbursements against it, false otherwise
     */
    protected boolean hasReimbursements(TravelAuthorizationDocument document) {
        List<TravelReimbursementDocument> reimbursements = getTravelDocumentService().findReimbursementDocuments(document.getTravelDocumentIdentifier());
        return reimbursements != null && !reimbursements.isEmpty();
    }

    /**
     * Determines if the given travel authorization document has any enroute reimbursements
     *
     * @param document the travel authorization to find enroute reimbursements for
     * @return true if there are any enroute reimbursements, false otherwise
     */
    protected boolean hasEnrouteReimbursements(TravelAuthorizationDocument document) {
        List<TravelReimbursementDocument> reimbursements = getTravelDocumentService().findReimbursementDocuments(document.getTravelDocumentIdentifier());
        for (TravelReimbursementDocument reimbursement : reimbursements) {
            if (StringUtils.equals(KFSConstants.DocumentStatusCodes.ENROUTE, reimbursement.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode())) {
                return true;
            }
        }
        return false;
    }


}
