/*
 * 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.cab.document.validation.impl;


import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.kns.maintenance.rules.MaintenanceDocumentRuleBase;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.maintenance.MaintenanceDocumentAuthorizer;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.module.cab.CabKeyConstants;
import org.kuali.kfs.module.cab.CabPropertyConstants;
import org.kuali.kfs.module.cab.businessobject.Pretag;
import org.kuali.kfs.module.cab.businessobject.PretagDetail;
import org.kuali.kfs.module.cab.document.service.PurApInfoService;
import org.kuali.kfs.module.cam.CamsKeyConstants;
import org.kuali.kfs.module.cam.businessobject.Asset;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.Building;
import org.kuali.kfs.sys.businessobject.Room;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.core.api.util.RiceKeyConstants;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;


/**
 * This class represents the business rules for the maintenance of {@link AccountGlobal} business objects
 */
public class PretagRule extends MaintenanceDocumentRuleBase {
    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PretagRule.class);
    protected PersistableBusinessObject bo;
    protected Pretag newPretag;

    public PretagRule() {
        super();
    }

    /**
     * This method sets the convenience objects like newPretag and oldPretag, so you have short and easy handles to the new and old
     * objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load all
     * sub-objects from the DB by their primary keys, if available.
     */

    @Override
    public void setupConvenienceObjects() {

        // setup Pretag convenience objects, make sure all possible detail lines are populated
        newPretag = (Pretag) super.getNewBo();
        for (PretagDetail dtl : newPretag.getPretagDetails()) {
            dtl.refreshNonUpdateableReferences();
        }
    }

    /**
     * Does not fail on rules failure
     *
     * @see org.kuali.kfs.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.kfs.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
        boolean success = processPretagValidation();

        return success & super.processCustomSaveDocumentBusinessRules(document); // always return true on save
    }

    /**
     * @see org.kuali.kfs.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.kfs.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {

        // get the documentAuthorizer for this document
        MaintenanceDocumentAuthorizer documentAuthorizer = (MaintenanceDocumentAuthorizer) getDocumentHelperService().getDocumentAuthorizer(document);

        WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument();
        boolean success = true;
        if (workflowDocument.isInitiated() || workflowDocument.isSaved()) {
            success &= documentAuthorizer.canCreateOrMaintain((MaintenanceDocument) document, GlobalVariables.getUserSession().getPerson());
            if (!success) {
                putFieldError(CabPropertyConstants.Pretag.CHART_OF_ACCOUNTS_CODE, CabKeyConstants.CHART_ORG_DISALLOWED_BY_CURRENT_USER);
            }
        }

        return success & super.processCustomRouteDocumentBusinessRules(document);
    }

    /**
     * Validates Pretag and its PretagDetail.
     *
     * @return boolean false or true
     */
    public boolean processPretagValidation() {
        boolean success = true;
        boolean newDetailLine = false;

        setupConvenienceObjects();
        success &= checkPurchaseOrderItemExists();
        success &= checkAssetRepresentativePrincipalNameExists();

        if (newPretag.isActive()) {
            success &= checkTotalDetailCount(newPretag, newDetailLine);
            success &= isAllCampusBuildingRoomValid(newPretag.getPretagDetails());
        } else {
            deactivePretagDetails(newPretag);
        }

        return success;
    }

    /**
     * validate the asset representative principal name.
     *
     * @return boolean false or true
     */
    protected boolean checkAssetRepresentativePrincipalNameExists() {
        boolean valid = true;
        if (StringUtils.isNotBlank(newPretag.getPersonUniversal().getPrincipalName())) {
            PersonService personService = SpringContext.getBean(org.kuali.rice.kim.api.identity.PersonService.class);
            Person person = personService.getPersonByPrincipalName(newPretag.getPersonUniversal().getPrincipalName());
            if (person != null) {
                newPretag.setPersonUniversal(person);
                newPretag.setRepresentativeUniversalIdentifier(person.getPrincipalId());
            } else {
                putFieldError(CabPropertyConstants.Pretag.REPRESENTATIVE_ID, CamsKeyConstants.PreTag.ERROR_PRE_TAG_INVALID_REPRESENTATIVE_ID, newPretag.getPersonUniversal().getPrincipalName());
                newPretag.setPersonUniversal(null);
                newPretag.setRepresentativeUniversalIdentifier(null);
                valid &= false;
            }
        }
        return valid;
    }

    /**
     * validate the purchase order item existence in PurAp.
     *
     * @return boolean false or true
     */
    protected boolean checkPurchaseOrderItemExists() {
        boolean valid = true;
        if (StringUtils.isNotBlank(newPretag.getPurchaseOrderNumber()) && newPretag.getItemLineNumber() != null) {
            PurchaseOrderDocument purchaseOrderDoc = getPurApInfoService().getCurrentDocumentForPurchaseOrderIdentifier(Integer.valueOf(newPretag.getPurchaseOrderNumber()));
            if (purchaseOrderDoc == null) {
                String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(Pretag.class.getName()).getAttributeDefinition(CabPropertyConstants.Pretag.PURCHASE_ORDER_NUMBER).getLabel();
                putFieldError(CabPropertyConstants.Pretag.PURCHASE_ORDER_NUMBER, RiceKeyConstants.ERROR_EXISTENCE, label);
                valid = false;
            } else if (getItemByLineNumber(purchaseOrderDoc, newPretag.getItemLineNumber()) == null) {
                String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(Pretag.class.getName()).getAttributeDefinition(CabPropertyConstants.Pretag.ITEM_LINE_NUMBER).getLabel();
                putFieldError(CabPropertyConstants.Pretag.ITEM_LINE_NUMBER, RiceKeyConstants.ERROR_EXISTENCE, label);
                valid = false;
            }
        }
        return valid;
    }

    /**
     * Get PurchaseOrderItem by given item line number
     *
     * @param items
     * @param lineNumber
     * @return
     */
    protected PurApItem getItemByLineNumber(PurchaseOrderDocument purchaseOrderDocument, int lineNumber) {
        List items = purchaseOrderDocument.getItems();
        for (Iterator iter = items.iterator(); iter.hasNext(); ) {
            PurApItem item = (PurApItem) iter.next();
            if (item.getItemLineNumber() != null && item.getItemLineNumber().intValue() == lineNumber) {
                return item;
            }
        }
        return null;
    }

    /**
     * This method loops through the list of {@link pretagDetail}s and passes them off to isAllCampusBuildingRoomValid for further
     * rule analysis
     *
     * @param document
     * @param details
     * @return true if the collection of {@link pretagDetail}s passes the sub-rules
     */
    public boolean isAllCampusBuildingRoomValid(List<PretagDetail> details) {
        boolean success = true;

        // check if there are any pretag details
        if (details.size() != 0) {

            // check each CampusBuildingRoom
            int index = 0;
            for (PretagDetail dtl : details) {
                String errorPath = MAINTAINABLE_ERROR_PREFIX + "pretagDetails[" + index + "]";
                GlobalVariables.getMessageMap().addToErrorPath(errorPath);
                success &= isCampusBuildingRoomValid(dtl);
                GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
                index++;
            }
        }

        return success;
    }

    /**
     * This method calls isCampusTagNumberValid whenever a new {@link PretagDetail} is added to Pretag
     *
     * @see org.kuali.kfs.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.kfs.kns.document.MaintenanceDocument,
     * java.lang.String, org.kuali.kfs.krad.bo.PersistableBusinessObject)
     */
    @Override
    public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) {
        setupConvenienceObjects();

        Pretag pretag = (Pretag) document.getNewMaintainableObject().getBusinessObject();
        PretagDetail detail = (PretagDetail) bo;

        boolean success = true;
        boolean newDetailLine = true;
        if (detail.isActive()) {

            detail.setPurchaseOrderNumber(pretag.getPurchaseOrderNumber());
            detail.setItemLineNumber(pretag.getItemLineNumber());

            success &= checkDuplicateTagNumber(pretag, detail.getCampusTagNumber());
            success &= checkTotalDetailCount(pretag, newDetailLine);
            success &= isCampusTagNumberValid(detail);
            success &= isCampusBuildingRoomValid(detail);
        }

        return success;
    }

    /**
     * This method check to see if duplicate tag exists
     *
     * @return boolean indicating if validation succeeded
     */
    protected boolean checkDuplicateTagNumber(Pretag pretag, String tagNumber) {
        boolean success = true;

        for (PretagDetail dtl : pretag.getPretagDetails()) {
            if (dtl.getCampusTagNumber().equals(tagNumber) && dtl.isActive()) {
                GlobalVariables.getMessageMap().putError(CabPropertyConstants.Pretag.CAMPUS_TAG_NUMBER, CamsKeyConstants.ERROR_TAG_NUMBER_DUPLICATE, new String[]{tagNumber});
                success &= false;
            }
        }

        return success;
    }

    /**
     * This method ensures that total {@link pretagDetail} tag details does not excees in quantity invoiced
     *
     * @param dtl
     * @return true if the detail tag doesn't exist in Asset
     */
    public boolean checkTotalDetailCount(Pretag pretag, boolean addLine) {
        boolean success = true;
        if (pretag.getQuantityInvoiced() != null) {
            int totalActiveDetails = getActiveDetailsCount(pretag, addLine);
            KualiDecimal totalNumerOfDetails = new KualiDecimal(totalActiveDetails);

            if (pretag.getQuantityInvoiced().compareTo(totalNumerOfDetails) < 0) {
                GlobalVariables.getMessageMap().putError(CabPropertyConstants.Pretag.CAMPUS_TAG_NUMBER, CamsKeyConstants.PreTag.ERROR_PRE_TAG_DETAIL_EXCESS, new String[]{pretag.getQuantityInvoiced().toString() + "" + " Total number of detail lines " + totalNumerOfDetails.toString()});
                success &= false;
            }
        }

        return success;
    }

    /**
     * This method reply that total active detail in {@link pretag}
     *
     * @param pretag and newDetailLine
     * @return total number of active pretagDetails
     */
    public int getActiveDetailsCount(Pretag pretag, boolean newDetailLine) {

        Collection<PretagDetail> pretagDetails = pretag.getPretagDetails();
        if (newDetailLine) {
            return countActive(pretagDetails) + 1;
        } else {
            return countActive(pretagDetails);
        }
    }

    /**
     * This method ensures that each {@link pretagDetail} tag number does not exist in Asset table
     *
     * @param dtl
     * @return true if the detail tag doesn't exist in Asset
     */
    public boolean isCampusTagNumberValid(PretagDetail dtl) {
        boolean success = true;

        if ((dtl.getCampusTagNumber() != null) && (dtl.isActive() && !dtl.getCampusTagNumber().equalsIgnoreCase("N"))) {
            Map<String, String> tagMap = new HashMap<String, String>();
            tagMap.put(CabPropertyConstants.Pretag.CAMPUS_TAG_NUMBER, dtl.getCampusTagNumber());
            int matchDetailCount = getMatchDetailCount(tagMap);
            if ((getBoService().countMatching(Asset.class, tagMap) != 0) || (matchDetailCount > 0)) {
                GlobalVariables.getMessageMap().putError(CabPropertyConstants.Pretag.CAMPUS_TAG_NUMBER, CamsKeyConstants.PreTag.ERROR_PRE_TAG_NUMBER, new String[]{dtl.getCampusTagNumber()});
                success &= false;
            }
        }

        return success;
    }

    /**
     * This method ensures that each {@link pretagDetail} buildingCode and buildingRoomNumber does exist in bulding and room tables
     *
     * @param dtl
     * @return true if the detail buildingCode and buildingRoomNumber does exist in building and room
     */
    public boolean isCampusBuildingRoomValid(PretagDetail dtl) {
        boolean success = true;

        int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount();
        getDictionaryValidationService().validateBusinessObject(dtl);

        if (StringUtils.isNotBlank(dtl.getCampusCode()) && StringUtils.isNotBlank(dtl.getBuildingCode())) {
            Map<String, String> preTagMap = new HashMap<String, String>();
            preTagMap.put(KFSPropertyConstants.CAMPUS_CODE, dtl.getCampusCode());
            preTagMap.put(KFSPropertyConstants.BUILDING_CODE, dtl.getBuildingCode());

            bo = (Building) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Building.class, preTagMap);
            if (bo == null) {
                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.BUILDING_CODE, CamsKeyConstants.ERROR_INVALID_BUILDING_CODE, new String[]{dtl.getCampusCode(), dtl.getBuildingCode()});
            }

            if (StringUtils.isNotBlank(dtl.getBuildingRoomNumber())) {
                preTagMap.put(KFSPropertyConstants.BUILDING_ROOM_NUMBER, dtl.getBuildingRoomNumber());
                bo = (Room) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Room.class, preTagMap);
                if (bo == null) {
                    GlobalVariables.getMessageMap().putError(KFSPropertyConstants.BUILDING_ROOM_NUMBER, CamsKeyConstants.ERROR_INVALID_ROOM_NUMBER, new String[]{dtl.getCampusCode(), dtl.getBuildingCode(), dtl.getBuildingRoomNumber()});
                }
            }
        }
        success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount;

        return success;
    }

    /**
     * This method returns number of matched active campusTagNumber
     *
     * @param map
     * @return active pretagDetail with same campusTagNumber
     */

    public int getMatchDetailCount(Map<String, String> tagMap) {

        Collection<PretagDetail> pretagDetails = SpringContext.getBean(BusinessObjectService.class).findMatching(PretagDetail.class, tagMap);

        return countActive(pretagDetails);
    }

    /**
     * This method ensures that count {@link pretagDetail} active detail lines
     *
     * @param collection
     * @return active pretagDetail count
     */
    public int countActive(Collection<PretagDetail> pretagDetails) {
        int activeCount = 0;

        for (PretagDetail dtl : pretagDetails) {
            if (dtl.isActive()) {
                activeCount++;
            }
        }

        return activeCount;
    }

    /**
     * This method ensures that all {@link pretag} detail lines deactivated
     *
     * @param pretag
     * @return deactive pretagDetails
     */
    public void deactivePretagDetails(Pretag pretag) {
        boolean inActive = false;

        for (PretagDetail dtl : pretag.getPretagDetails()) {
            dtl.setActive(inActive);
        }
    }

    /**
     * Gets the purchaseOrderService attribute.
     *
     * @return Returns the purchaseOrderService.
     */
    protected PurApInfoService getPurApInfoService() {
        return SpringContext.getBean(PurApInfoService.class);
    }
}
