/*
 * 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.identity;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.coa.businessobject.Organization;
import org.kuali.kfs.coa.service.ChartService;
import org.kuali.kfs.coa.service.OrganizationService;
import org.kuali.kfs.kns.kim.role.RoleTypeServiceBase;
import org.kuali.kfs.sys.identity.KfsKimAttributes;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.role.RoleMembership;
import org.kuali.rice.kim.api.role.RoleMembership.Builder;
import org.kuali.rice.kim.framework.common.delegate.DelegationTypeService;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class OrganizationHierarchyAwareRoleTypeServiceBase extends RoleTypeServiceBase implements DelegationTypeService {
    private static final Logger LOG = Logger.getLogger(OrganizationHierarchyAwareRoleTypeServiceBase.class);
    protected ChartService chartService;
    protected OrganizationService organizationService;
    protected static final String DOCUMENT_TYPE_NAME = "ORR";

    private List<String> workflowRoutingAttributes = new ArrayList<String>(2);

    {
        workflowRoutingAttributes.add(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE);
        workflowRoutingAttributes.add(KfsKimAttributes.ORGANIZATION_CODE);
        workflowRoutingAttributes = Collections.unmodifiableList(workflowRoutingAttributes);
    }

    private List<String> uniqueAttributes = new ArrayList<String>(3);

    {
        uniqueAttributes.add(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME);
        uniqueAttributes.add(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE);
        uniqueAttributes.add(KfsKimAttributes.ORGANIZATION_CODE);
        uniqueAttributes = Collections.unmodifiableList(uniqueAttributes);
    }

    @Override
    public List<String> getUniqueAttributes(String kimTypeId) {
        return uniqueAttributes;
    }

    @Override
    public List<String> getWorkflowRoutingAttributes(String routeLevel) {
        return workflowRoutingAttributes;
    }

    @Override
    public String getWorkflowDocumentTypeName() {
        return DOCUMENT_TYPE_NAME;
    }

    protected boolean isParentOrg(String qualificationChartCode, String qualificationOrgCode, String roleChartCode, String roleOrgCode, boolean descendHierarchy) {
        if (StringUtils.isBlank(qualificationChartCode) || StringUtils.isBlank(qualificationOrgCode)) {
            if (LOG.isInfoEnabled()) {
                LOG.info("No chart/org qualifications passed into isParentOrg()");
            }
            return false;
        }
        if (roleChartCode == null && roleOrgCode == null) {
            LOG.warn("Call to " + this.getClass().getName() + " with no organization role qualifiers; both chart and organization code are null.  Please ensure that qualification data has organization information for this role.");
            return false;
        }
        if (roleOrgCode == null) {
            return roleChartCode.equals(qualificationChartCode)
                || (descendHierarchy && chartService.isParentChart(qualificationChartCode, roleChartCode));
        }
        return (roleChartCode.equals(qualificationChartCode) && roleOrgCode.equals(qualificationOrgCode))
            || (descendHierarchy && organizationService.isParentOrganization(qualificationChartCode, qualificationOrgCode, roleChartCode, roleOrgCode));
    }

    @Override
    public Map<String, String> convertQualificationForMemberRoles(String namespaceCode, String roleName, String memberRoleNamespaceCode, String memberRoleName, Map<String, String> qualification) {
        if (qualification == null) {
            return null;
        }
        // only attempt the conversion if :
        // (a) there is not already a campus code provided by the document
        // and (b) we have a chart and organization to resolve
        if (StringUtils.isBlank(qualification.get(KimConstants.AttributeConstants.CAMPUS_CODE))
            && StringUtils.isNotBlank(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE)
            && StringUtils.isNotBlank(KfsKimAttributes.ORGANIZATION_CODE)) {
            Map<String, String> newQualification = new HashMap<String, String>(qualification);
            try {
                Organization org = organizationService.getByPrimaryId(qualification.get(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE), qualification.get(KfsKimAttributes.ORGANIZATION_CODE));
                if (org != null) {
                    newQualification.put(KimConstants.AttributeConstants.CAMPUS_CODE, org.getOrganizationPhysicalCampusCode());
                } else {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Invalid Chart/Org passed to convertQualificationForMemberRoles: " + namespaceCode + "/" + roleName + "/" + memberRoleNamespaceCode + "/" + memberRoleName + "/" + qualification);
                    }
                }
            } catch (Exception ex) {
                LOG.warn("Unable to convert organization qualification to physical campus", ex);
            }
            return newQualification;
        } else {
            return qualification;
        }
    }

    /**
     * TODO BARLEY
     *
     * @Override public boolean doesDelegationQualifierMatchQualification(Map<String,String> qualification, Map<String,String> delegationQualifier) {
     * return performMatch(translateInputAttributes(qualification), delegationQualifier);
     * }
     * <p>
     * public List<DelegateMember> doDelegationQualifiersMatchQualification(Map<String,String> qualification, List<DelegateMemberBo> delegationMemberList) {
     * Map<String,String> translatedQualification = translateInputAttributes(qualification);
     * List<DelegateMember> matchingMemberships = new ArrayList<DelegateMember>();
     * //TODO: Spaghetti fix
     * <p>
     * //        for (DelegateMemberBo dmi : delegationMemberList) {
     * //             if (performMatch(translatedQualification, dmi.getQualifier())) {
     * //                matchingMemberships.add(DelegateMemberBo.to(dmi));
     * //            }
     * //        }
     * return matchingMemberships;
     * }
     */


    public void setOrganizationService(OrganizationService organizationService) {
        this.organizationService = organizationService;
    }

    public void setChartService(ChartService chartService) {
        this.chartService = chartService;
    }


    @Override
    public List<RoleMembership> sortRoleMembers(List<RoleMembership> roleMembers) {
        List<SortableRoleMembershipHolder> listToSort = new ArrayList<SortableRoleMembershipHolder>(roleMembers.size());
        // build the sortable list
        for (RoleMembership rmi : roleMembers) {
            listToSort.add(new SortableRoleMembershipHolder(rmi));
        }
        // sort it
        Collections.sort(listToSort);
        // restore them to the list in their sorted order
        List<RoleMembership> newRoleMembers = new ArrayList<RoleMembership>(); // need a new list, because roleMembers is not mutable
        int group = 0; // counter for the group number to add to the roleSortingCode
        String lastRoleSortingCode = "";
        for (SortableRoleMembershipHolder srmh : listToSort) {
            if (!srmh.rmi.getRoleSortingCode().equals(lastRoleSortingCode)) {
                group++;
                lastRoleSortingCode = srmh.rmi.getRoleSortingCode();
            }
            Builder builder = RoleMembership.Builder.create(srmh.rmi);
            builder.setRoleSortingCode(StringUtils.leftPad(Integer.toString(group), 3, '0') + "/" + srmh.rmi.getRoleSortingCode());
            newRoleMembers.add(builder.build());
        }
        return newRoleMembers;
    }

    protected class SortableRoleMembershipHolder implements Comparable<SortableRoleMembershipHolder> {

        public String chart;
        public String org;
        public RoleMembership rmi;

        public SortableRoleMembershipHolder(RoleMembership rmi) {
            chart = rmi.getQualifier().get(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE);
            org = rmi.getQualifier().get(KfsKimAttributes.ORGANIZATION_CODE);
            Builder builder = RoleMembership.Builder.create(rmi);
            builder.setRoleSortingCode(chart + "-" + org);
            this.rmi = builder.build();
        }

        @Override
        public int compareTo(SortableRoleMembershipHolder o) {
            if (chart == null || org == null || o.chart == null || o.org == null || (o.chart == chart && o.org == org)) {
                return 0;
            }
            if (organizationService.isParentOrganization(o.chart, o.org, chart, org)) {
                return 1;
            } else {
                return -1;
            }
        }
    }


}
