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

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.kns.kim.role.RoleTypeServiceBase;
import org.kuali.rice.core.api.membership.MemberType;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.group.GroupMember;
import org.kuali.rice.kim.api.group.GroupService;
import org.kuali.rice.kim.api.role.RoleMembership;
import org.kuali.rice.kim.api.role.RoleService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Base role type for roles which will exclude their members based on certain criteria.  This base provides methods to
 * remove a single approver for any members
 */
public class ExclusionRoleTypeServiceBase extends RoleTypeServiceBase {
    protected RoleService roleManagementService;
    protected GroupService groupService;

    /**
     * Remove the excluded principal from any matching memberships in the role
     *
     * @param excludedPrincipalId the principal id to exclude
     * @param qualification       the qualification for the role
     * @param membershipInfos     the role members
     * @return the filtered role memberships
     */
    protected List<RoleMembership> excludePrincipalAsNeeded(String excludedPrincipalId, Map<String, String> qualification, List<RoleMembership> membershipInfos) {
        if (StringUtils.isBlank(excludedPrincipalId)) {
            return membershipInfos;
        }

        String topLevelRoleId = null;
        if (!membershipInfos.isEmpty()) {
            topLevelRoleId = membershipInfos.get(0).getRoleId();
        }

        Set<String> checkedMembers = new HashSet<String>();
        final String documentId = new String(qualification.get(KimConstants.AttributeConstants.DOCUMENT_NUMBER));

        List<RoleMembership> qualifiedRoleMembers = new ArrayList<RoleMembership>();
        for (RoleMembership member : membershipInfos) {
            String topLevelRoleMemberId = member.getId();
            if (MemberType.PRINCIPAL.equals(member.getType())) {
                if (!excludedPrincipalId.equals(member.getMemberId())) {
                    qualifiedRoleMembers.add(member);
                }
            } else if (MemberType.ROLE.equals(member.getType())) {
                // get members of role
                RoleMembership.Builder membership = RoleMembership.Builder.create(member.getRoleId(), member.getId(), member.getMemberId(), member.getType(), qualification);
                checkRoleMemberShip(excludedPrincipalId, membership.build(), qualification, qualifiedRoleMembers, checkedMembers, documentId, topLevelRoleId, topLevelRoleMemberId);

            } else if (MemberType.GROUP.equals(member.getType())) {
                RoleMembership.Builder membership = RoleMembership.Builder.create(member.getRoleId(), member.getId(), member.getMemberId(), member.getType(), qualification);
                checkGroupMemberShip(excludedPrincipalId, membership.build(), qualification, qualifiedRoleMembers, checkedMembers, documentId, topLevelRoleId, topLevelRoleMemberId);
            }
        }
        return qualifiedRoleMembers;
    }

    /**
     * This method is to check role members of given role and skip the single approver if exists from the role.
     *
     * @param approverOrInitiator
     * @param member
     * @param qualification
     * @param qualifiedRoleMembers
     * @param checkedRoletypeMember
     * @param documentId
     * @param topLevelRoleId
     * @param topLevelRoleMemberId
     */
    protected void checkRoleMemberShip(String excludedPrincipalId, RoleMembership member, Map<String, String> qualification, List<RoleMembership> qualifiedRoleMembers, Set<String> checkedRoletypeMember, String documentId, String topLevelRoleId, String topLevelRoleMemberId) {
        String key = member.getType().getCode() + "~" + member.getMemberId();
        if (!checkedRoletypeMember.contains(key)) {
            checkedRoletypeMember.add(key);
            List<RoleMembership> roleMembers = getRoleManagementService().getRoleMembers(Collections.singletonList(member.getMemberId()), qualification);
            for (RoleMembership membershipInfo : roleMembers) {
                if (MemberType.PRINCIPAL.getCode().equals(membershipInfo.getType().getCode())) {
                    if (!excludedPrincipalId.equals(membershipInfo.getMemberId())) {
                        RoleMembership.Builder updatedMembershipInfo = RoleMembership.Builder.create(topLevelRoleId, membershipInfo.getId(), membershipInfo.getMemberId(), MemberType.PRINCIPAL, qualification);
                        qualifiedRoleMembers.add(updatedMembershipInfo.build());
                    }
                } else if (MemberType.ROLE.getCode().equals(membershipInfo.getType().getCode())) {
                    checkRoleMemberShip(excludedPrincipalId, membershipInfo, qualification, qualifiedRoleMembers, checkedRoletypeMember, documentId, topLevelRoleId, topLevelRoleMemberId);
                } else if (MemberType.GROUP.getCode().equals(membershipInfo.getType().getCode())) {
                    checkGroupMemberShip(excludedPrincipalId, membershipInfo, qualification, qualifiedRoleMembers, checkedRoletypeMember, documentId, topLevelRoleId, topLevelRoleMemberId);
                }
            }
        }
    }

    /**
     * This method is to check group members of given group and skip the single approver
     * member from the group.
     *
     * @param approverOrInitiator
     * @param member
     * @param qualification
     * @param qualifiedRoleMembers
     * @param checkedMembers
     * @param documentId
     * @param topLevelRoleId
     * @param topLevelRoleMemberId
     */
    protected void checkGroupMemberShip(String excludedPrincipalId, RoleMembership member, Map<String, String> qualification, List<RoleMembership> qualifiedRoleMembers, Set<String> checkedMembers, String documentId, String topLevelRoleId, String topLevelRoleMemberId) {
        String key = member.getType().getCode() + "~" + member.getMemberId();
        if (!checkedMembers.contains(key)) {
            checkedMembers.add(key);
            List<GroupMember> GroupMembers = (List<GroupMember>) getGroupService().getMembersOfGroup(member.getMemberId());
            for (GroupMember membershipInfo : GroupMembers) {
                if (MemberType.PRINCIPAL.getCode().equals(membershipInfo.getType().getCode())) {
                    if (!excludedPrincipalId.equals(membershipInfo.getMemberId())) {
                        RoleMembership.Builder updatedMembershipInfo = RoleMembership.Builder.create(topLevelRoleId, membershipInfo.getId(), membershipInfo.getMemberId(), MemberType.PRINCIPAL, qualification);
                        qualifiedRoleMembers.add(updatedMembershipInfo.build());
                    }
                } else if (MemberType.GROUP.getCode().equals(membershipInfo.getType().getCode())) {
                    checkGroupMemberShip(excludedPrincipalId, member, qualification, qualifiedRoleMembers, checkedMembers, documentId, topLevelRoleId, topLevelRoleMemberId);
                }
            }
        }
    }

    protected RoleService getRoleManagementService() {
        if (roleManagementService == null) {
            roleManagementService = KimApiServiceLocator.getRoleService();
        }
        return roleManagementService;
    }

    protected GroupService getGroupService() {
        if (groupService == null) {
            groupService = KimApiServiceLocator.getGroupService();
        }
        return groupService;
    }
}
