/**
 * Copyright 2005-2018 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.kim.document.rule;

import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;

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

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.rice.core.api.criteria.QueryByCriteria;
import org.kuali.rice.core.api.uif.RemotableAttributeError;
import org.kuali.rice.core.api.util.RiceKeyConstants;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.identity.IdentityService;
import org.kuali.rice.kim.api.identity.entity.EntityDefault;
import org.kuali.rice.kim.api.identity.principal.Principal;
import org.kuali.rice.kim.api.role.RoleMember;
import org.kuali.rice.kim.api.role.RoleService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kim.api.type.KimAttributeField;
import org.kuali.rice.kim.api.type.KimType;
import org.kuali.rice.kim.bo.ui.KimDocumentRoleMember;
import org.kuali.rice.kim.bo.ui.KimDocumentRoleQualifier;
import org.kuali.rice.kim.bo.ui.PersonDocumentAffiliation;
import org.kuali.rice.kim.bo.ui.PersonDocumentBoDefaultBase;
import org.kuali.rice.kim.bo.ui.PersonDocumentEmploymentInfo;
import org.kuali.rice.kim.bo.ui.PersonDocumentGroup;
import org.kuali.rice.kim.bo.ui.PersonDocumentName;
import org.kuali.rice.kim.bo.ui.PersonDocumentRole;
import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMember;
import org.kuali.rice.kim.document.IdentityManagementPersonDocument;
import org.kuali.rice.kim.document.authorization.IdentityManagementKimDocumentAuthorizer;
import org.kuali.rice.kim.framework.services.KimFrameworkServiceLocator;
import org.kuali.rice.kim.framework.type.KimTypeService;
import org.kuali.rice.kim.impl.KIMPropertyConstants;
import org.kuali.rice.kim.impl.identity.affiliation.EntityAffiliationTypeBo;
import org.kuali.rice.kim.impl.identity.principal.PrincipalBo;
import org.kuali.rice.kim.impl.type.KimTypeBo;
import org.kuali.rice.kim.rule.event.ui.AddGroupEvent;
import org.kuali.rice.kim.rule.event.ui.AddPersonDelegationMemberEvent;
import org.kuali.rice.kim.rule.event.ui.AddRoleEvent;
import org.kuali.rice.kim.rule.ui.AddGroupRule;
import org.kuali.rice.kim.rule.ui.AddPersonDelegationMemberRule;
import org.kuali.rice.kim.rule.ui.AddPersonDocumentRoleQualifierRule;
import org.kuali.rice.kim.rule.ui.AddRoleRule;
import org.kuali.rice.kim.rules.ui.PersonDocumentDelegationMemberRule;
import org.kuali.rice.kim.rules.ui.PersonDocumentGroupRule;
import org.kuali.rice.kim.rules.ui.PersonDocumentRoleRule;
import org.kuali.rice.kns.kim.type.DataDictionaryTypeServiceHelper;
import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
import org.kuali.rice.krad.data.KradDataServiceLocator;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADConstants;

/**
 * This is a description of what this class does - shyu don't forget to fill this in.
 *
 * @author Kuali Rice Team (rice.collab@kuali.org)
 *
 */
public class IdentityManagementPersonDocumentRule extends TransactionalDocumentRuleBase implements AddGroupRule, AddRoleRule, AddPersonDocumentRoleQualifierRule, AddPersonDelegationMemberRule {

	protected AddGroupRule addGroupRule;
	protected AddRoleRule  addRoleRule;
	protected AddPersonDelegationMemberRule addPersonDelegationMemberRule;
	protected IdentityManagementKimDocumentAuthorizer authorizer;
	private IdentityService identityService;
	private RoleService roleService;
	protected Class<? extends AddGroupRule> addGroupRuleClass = PersonDocumentGroupRule.class;
	protected Class<? extends AddRoleRule> addRoleRuleClass = PersonDocumentRoleRule.class;
	protected Class<? extends AddPersonDelegationMemberRule> addPersonDelegationMemberRuleClass = PersonDocumentDelegationMemberRule.class;
    protected ActiveRoleMemberHelper activeRoleMemberHelper = new ActiveRoleMemberHelper();
	protected AttributeValidationHelper attributeValidationHelper = new AttributeValidationHelper();

    @Override
    protected boolean processCustomSaveDocumentBusinessRules(Document document) {
        if (!(document instanceof IdentityManagementPersonDocument)) {
            return false;
        }

        IdentityManagementPersonDocument personDoc = (IdentityManagementPersonDocument)document;
        boolean valid = true;

        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);

        //KRADServiceLocatorInternal.getDictionaryValidationService().validateDocument(document);
        getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document, getMaxDictionaryValidationDepth(), true, false);
        valid &= validDuplicatePrincipalName(personDoc);
        EntityDefault origEntity = getIdentityService().getEntityDefault(personDoc.getEntityId());
        boolean isCreatingNew = origEntity==null?true:false;
        if(canModifyEntity(GlobalVariables.getUserSession().getPrincipalId(), personDoc.getPrincipalId()) || isCreatingNew) {
        	valid &= validateEntityInformation(isCreatingNew, personDoc);
        }
        // kimtypeservice.validateAttributes is not working yet.
        valid &= validateRoleQualifier (personDoc.getRoles());
        valid &= validateDelegationMemberRoleQualifier(personDoc.getDelegationMembers());
        if (StringUtils.isNotBlank(personDoc.getPrincipalName())) {
        	valid &= doesPrincipalNameExist (personDoc.getPrincipalName(), personDoc.getPrincipalId());
        }

        valid &= validActiveDatesForRole (personDoc.getRoles());
        valid &= validActiveDatesForGroup (personDoc.getGroups());
        valid &= validActiveDatesForDelegations (personDoc.getDelegationMembers());


        // all failed at this point.
//        valid &= checkUnassignableRoles(personDoc);
//        valid &= checkUnpopulatableGroups(personDoc);

        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);

        return valid;
    }

    protected boolean validateEntityInformation(boolean isCreatingNew, IdentityManagementPersonDocument personDoc){
        boolean valid = true;
        boolean canOverridePrivacyPreferences = canOverrideEntityPrivacyPreferences(GlobalVariables.getUserSession().getPrincipalId(), personDoc.getPrincipalId());
        valid &= checkMultipleDefault (personDoc.getAffiliations(), "affiliations");
        if(isCreatingNew || canOverridePrivacyPreferences || !personDoc.getPrivacy().isSuppressName()) {
        	valid &= checkMultipleDefault (personDoc.getNames(), "names");
        }
        if(isCreatingNew || canOverridePrivacyPreferences || !personDoc.getPrivacy().isSuppressAddress()) {
        	valid &= checkMultipleDefault (personDoc.getAddrs(), "addrs");
        }
        if(isCreatingNew || canOverridePrivacyPreferences || !personDoc.getPrivacy().isSuppressPhone()) {
        	valid &= checkMultipleDefault (personDoc.getPhones(), "phones");
        }
        if(isCreatingNew || canOverridePrivacyPreferences || !personDoc.getPrivacy().isSuppressEmail()) {
        	valid &= checkMultipleDefault (personDoc.getEmails(), "emails");
        }
        valid &= checkPrimaryEmploymentInfo (personDoc.getAffiliations());
        valid &= validEmployeeIDForAffiliation(personDoc.getAffiliations());
        valid &= checkAffiliationTypeChange (personDoc.getAffiliations());
        valid &= checkUniqueAffiliationTypePerCampus(personDoc.getAffiliations());
    	return valid;
    }

	protected boolean validDuplicatePrincipalName(IdentityManagementPersonDocument personDoc){
    	List<PrincipalBo> prncplImpls = KradDataServiceLocator.getDataObjectService().findMatching(
    	        PrincipalBo.class,
    	        QueryByCriteria.Builder.forAttribute( KIMPropertyConstants.Principal.PRINCIPAL_NAME,
    	                personDoc.getPrincipalName()).build() ).getResults();
    	boolean rulePassed = true;
    	if( !prncplImpls.isEmpty() ){
    		if(prncplImpls.size() == 1
    		        && StringUtils.equals( prncplImpls.get(0).getPrincipalId(), personDoc.getPrincipalId()) ) {
    			rulePassed = true;
            } else {
	    		GlobalVariables.getMessageMap().putError("document.principalName",
	    				RiceKeyConstants.ERROR_DUPLICATE_ENTRY, new String[] {"Principal Name"});
	    		rulePassed = false;
    		}
    	}
    	return rulePassed;
    }

//	protected boolean checkUnassignableRoles(IdentityManagementPersonDocument document) {
//		boolean valid = true;
//    	Map<String,Set<String>> unassignableRoles = getAuthorizer( document ).getUnassignableRoles(document, GlobalVariables.getUserSession().getPerson());
//        for (String namespaceCode : unassignableRoles.keySet()) {
//        	for (String roleName : unassignableRoles.get(namespaceCode)) {
//        		int i = 0;
//        		for (PersonDocumentRole role : document.getRoles()) {
//        			if (role.isEditable() && namespaceCode.endsWith(role.getNamespaceCode()) && roleName.equals(role.getRoleName())) {
//        				GlobalVariables.getMessageMap().putError("roles["+i+"].roleId", RiceKeyConstants.ERROR_ASSIGN_ROLE, new String[] {namespaceCode, roleName});
//        	        	valid = false;
//        			}
//        			i++;
//        		}
//        	}
//        }
//        return valid;
//	}
//
//	protected boolean checkUnpopulatableGroups(IdentityManagementPersonDocument document) {
//		boolean valid = true;
//    	Map<String,Set<String>> unpopulatableGroups = getAuthorizer( document ).getUnpopulateableGroups(document, GlobalVariables.getUserSession().getPerson());
//        for (String namespaceCode : unpopulatableGroups.keySet()) {
//        	for (String groupName : unpopulatableGroups.get(namespaceCode)) {
//        		int i = 0;
//        		for (PersonDocumentGroup group : document.getGroups()) {
//        			if ( (group.getNamespaceCode() != null && namespaceCode.endsWith(group.getNamespaceCode())) && (group.getGroupName() != null && groupName.equals(group.getGroupName()))) {
//        				GlobalVariables.getMessageMap().putError("groups["+i+"].groupId", RiceKeyConstants.ERROR_POPULATE_GROUP, new String[] {namespaceCode, groupName});
//        			}
//        			i++;
//        		}
//        	}
//        	valid = false;
//        }
//        return valid;
//	}

    @Override
	protected boolean processCustomRouteDocumentBusinessRules(Document document) {
		super.processCustomRouteDocumentBusinessRules(document);
        IdentityManagementPersonDocument personDoc = (IdentityManagementPersonDocument)document;
        boolean valid = true;
        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
        valid &= validateAffiliationAndName( personDoc );
        valid &= checkAffiliationEithOneEMpInfo (personDoc.getAffiliations());
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);

        return valid;
	}


	protected boolean checkMultipleDefault (List <? extends PersonDocumentBoDefaultBase> boList, String listName) {
    	boolean valid = true;
    	boolean isDefaultSet = false;
    	int i = 0;
    	for (PersonDocumentBoDefaultBase item : boList) {
     		if (item.isDflt()) {
     			if (isDefaultSet) {
     				GlobalVariables.getMessageMap().putError(listName+"[" + i + "].dflt",RiceKeyConstants.ERROR_MULTIPLE_DEFAULT_SELETION);
     				valid = false;
     			} else {
     				isDefaultSet = true;
     			}
     		}
     		i++;
    	}
    	if (!boList.isEmpty() && !isDefaultSet) {
				GlobalVariables.getMessageMap().putError(listName+"[0].dflt",RiceKeyConstants.ERROR_NO_DEFAULT_SELETION);
    	}
    	return valid;
    }

    protected boolean checkPrimaryEmploymentInfo (List <PersonDocumentAffiliation> affiliations) {
    	boolean valid = true;
    	int i = 0;
    	int firstAfflnCounter = -1;
    	boolean isPrimarySet = false;
    	for (PersonDocumentAffiliation affiliation : affiliations) {
    		int j = 0;
    		for (PersonDocumentEmploymentInfo empInfo : affiliation.getEmpInfos()) {
     			if(firstAfflnCounter==-1) {
     				firstAfflnCounter = i;
                }
    			if (empInfo.isPrimary()) {
	     			if (isPrimarySet) {
	     				// primary per principal or primary per affiliation ?
	     				GlobalVariables.getMessageMap().putError("affiliations[" + i + "].empInfos["+ j +"].primary",RiceKeyConstants.ERROR_MULTIPLE_PRIMARY_EMPLOYMENT);
	     				valid = false;
	     			} else {
	     				isPrimarySet = true;
	     			}
	     			j++;
	     		}
    		}
     		i++;
    	}
    	if(!isPrimarySet && firstAfflnCounter!=-1){
    		GlobalVariables.getMessageMap().putError("affiliations[" + firstAfflnCounter + "].empInfos[0].primary",RiceKeyConstants.ERROR_NO_PRIMARY_EMPLOYMENT);
    		valid = false;
    	}
    	return valid;
    }

    protected boolean checkAffiliationTypeChange (List <PersonDocumentAffiliation> affiliations) {
    	boolean valid = true;
    	int i = 0;
    	for (PersonDocumentAffiliation affiliation : affiliations) {
    		if (affiliation.getAffiliationType() != null && !affiliation.getAffiliationTypeCode().equals(affiliation.getAffiliationType().getCode())) {
    			EntityAffiliationTypeBo newAffiliationType = KradDataServiceLocator.getDataObjectService().find(EntityAffiliationTypeBo.class, affiliation.getAffiliationTypeCode());
    			if (!newAffiliationType.isEmploymentAffiliationType()
    			        && affiliation.getAffiliationType().isEmploymentAffiliationType()
    			        && !affiliation.getEmpInfos().isEmpty()) {
		     		GlobalVariables.getMessageMap().putError("affiliations[" + i + "].affiliationTypeCode",RiceKeyConstants.ERROR_NOT_EMPLOYMENT_AFFILIATION_TYPE,new String[] {affiliation.getAffiliationType().getName(), newAffiliationType.getName()});
		     		valid = false;
	    		}
    		}
        	i++;
    	}
    	return valid;
    }

    protected boolean validEmployeeIDForAffiliation(List <PersonDocumentAffiliation> affiliations) {
    	boolean valid = true;
    	int i = 0;
    	int j = 0;
    	for(PersonDocumentAffiliation affiliation : affiliations) {
    		if(affiliation.getAffiliationType() != null && affiliation.getAffiliationType().isEmploymentAffiliationType()){
    			if(affiliation.getEmpInfos()!=null){
    	    		j = 0;
    	    		for (PersonDocumentEmploymentInfo empInfo : affiliation.getEmpInfos()) {
    	    			if (StringUtils.isEmpty(empInfo.getEmployeeId())) {
   		     				GlobalVariables.getMessageMap().putError("affiliations[" + i + "].empInfos["+ j +"].employeeId", RiceKeyConstants.ERROR_REQUIRED_CONDITIONALLY, new String[] {"Employee ID", "an employee"});
   		     				valid = false;
    		     			j++;
    		     		}
    	    		}
	    		}
    		}
        	i++;
    	}
    	return valid;
    }

    protected boolean isPersonAnEmployee(List<PersonDocumentAffiliation> affiliations){
    	boolean isEmployee = false;
    	for (PersonDocumentAffiliation affiliation : affiliations){
    		if (affiliation.getAffiliationType() != null && affiliation.getAffiliationType().isEmploymentAffiliationType()){
    			isEmployee = true;
    			break;
    		}
    	}
    	return isEmployee;
    }

    protected boolean checkUniqueAffiliationTypePerCampus (List <PersonDocumentAffiliation> affiliations) {
    	boolean valid = true;
    	int i = 0;
    	for (PersonDocumentAffiliation affiliation : affiliations) {
    		int j = 0;
        	for (PersonDocumentAffiliation affiliation1 : affiliations) {
	    		if (j > i && affiliation.getAffiliationTypeCode() .equals(affiliation1.getAffiliationTypeCode()) && affiliation.getCampusCode().equals(affiliation1.getCampusCode())) {
			     		GlobalVariables.getMessageMap().putError("affiliations[" + j + "].affiliationTypeCode",RiceKeyConstants.ERROR_NOT_UNIQUE_AFFILIATION_TYPE_PER_CAMPUS, affiliation.getAffiliationType().getName());
			     		valid = false;
	    		}
	    		j++;
        	}
        	i++;
    	}
    	return valid;
    }

    protected boolean checkAffiliationEithOneEMpInfo (List <PersonDocumentAffiliation> affiliations) {
    	boolean valid = true;
    	int i = 0;
    	for (PersonDocumentAffiliation affiliation : affiliations) {
	    		if (affiliation.getAffiliationType() .isEmploymentAffiliationType() && affiliation.getEmpInfos().isEmpty()) {
			     		GlobalVariables.getMessageMap().putError("affiliations[" + i + "].affiliationTypeCode",RiceKeyConstants.ERROR_ONE_ITEM_REQUIRED, "Employment Information");
			     		valid = false;
	    		}
        	i++;
    	}
    	return valid;
    }

    /*
     * Verify at least one affiliation and one default name
     */
    protected boolean validateAffiliationAndName(IdentityManagementPersonDocument personDoc) {
    	boolean valid = true;
    	if (personDoc.getAffiliations().isEmpty()) {
     		GlobalVariables.getMessageMap().putError("affiliations[0]",RiceKeyConstants.ERROR_ONE_ITEM_REQUIRED, "affiliation");
     		valid = false;
    	}
    	if (personDoc.getNames().isEmpty()) {
     		GlobalVariables.getMessageMap().putError("names[0]",RiceKeyConstants.ERROR_ONE_ITEM_REQUIRED, "name");
     		valid = false;
    	} else{
        	boolean activeExists = false;
        	for(PersonDocumentName name: personDoc.getNames()){
    	    	if(name.isActive()){
    	    		activeExists = true;
   	    		}
        	}
        	if(!activeExists){
        		GlobalVariables.getMessageMap().putError("names[0]", RiceKeyConstants.ERROR_ONE_ACTIVE_ITEM_REQUIRED, "name");
	     		valid = false;
        	}
        	return valid;

    	}
    	return valid;
    }

    protected boolean doesPrincipalNameExist (String principalName, String principalId) {
    	Principal principal = getIdentityService().getPrincipalByPrincipalName(principalName);
    	if (principal != null && (StringUtils.isBlank(principalId) || !principal.getPrincipalId().equals(principalId))) {
        	GlobalVariables.getMessageMap().putError(KIMPropertyConstants.Person.PRINCIPAL_NAME,RiceKeyConstants.ERROR_EXIST_PRINCIPAL_NAME, principalName);
			return false;
    	}
    	return true;
    }

    protected boolean validateRoleQualifier( List<PersonDocumentRole> roles ) {
		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
        int i = 0;
    	for(PersonDocumentRole role : roles ) {
    		KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(KimTypeBo.to(
                    role.getKimRoleType()));
        	if(CollectionUtils.isEmpty(role.getRolePrncpls()) && !role.getDefinitions().isEmpty()){
        		KimType kimTypeInfo = KimApiServiceLocator.getKimTypeInfoService().getKimType(role.getKimRoleType().getId());
        		Map<String, String> blankQualifiers = attributeValidationHelper.getBlankValueQualifiersMap(kimTypeInfo.getAttributeDefinitions());
        		List<RemotableAttributeError> localErrors = kimTypeService.validateAttributes(
        			role.getKimRoleType().getId(), blankQualifiers);
        		if(localErrors!=null && !localErrors.isEmpty()){
        			GlobalVariables.getMessageMap().putError("document.roles["+i+"].newRolePrncpl.qualifiers[0].attrVal",
        					RiceKeyConstants.ERROR_ONE_ITEM_REQUIRED, "Role Qualifier");
        			return false;
        		}
        	}

        	final List<KimAttributeField> attributeDefinitions = role.getDefinitions();
        	final Set<String> uniqueQualifierAttributes = findUniqueQualificationAttributes(role, attributeDefinitions);

	        if ( kimTypeService != null ) {
		        int j = 0;

	        	for ( KimDocumentRoleMember rolePrincipal : activeRoleMemberHelper.getActiveRoleMembers(role.getRolePrncpls()) ) {
	        		List<RemotableAttributeError> localErrors = kimTypeService.validateAttributes( role.getKimRoleType().getId(), attributeValidationHelper.convertQualifiersToMap( rolePrincipal.getQualifiers() ) );
			        validationErrors.addAll( attributeValidationHelper.convertErrors(
                            "roles[" + i + "].rolePrncpls[" + j + "]",
                            attributeValidationHelper.convertQualifiersToAttrIdxMap(rolePrincipal.getQualifiers()),
                            localErrors));

			        if (uniqueQualifierAttributes.size() > 0) {
			        	validateUniquePersonRoleQualifiersUniqueForMembership(role, rolePrincipal, j, uniqueQualifierAttributes, i, validationErrors);
			        }

			        j++;
		        }
	        }
        	i++;
    	}
        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
    	if (validationErrors.isEmpty()) {
    		return true;
    	} else {
    		attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
    		return false;
    	}
    }

    /**
     * Checks all the qualifiers for the given membership, so that all qualifiers which should be unique are guaranteed to be unique
     *
     * @param roleIndex the index of the role on the document (for error reporting purposes)
     * @param membershipToCheckIndex the index of the person's membership in the role (for error reporting purposes)
     * @return true if all unique values are indeed unique, false otherwise
     */
    protected boolean validateUniquePersonRoleQualifiersUniqueForMembership(PersonDocumentRole role, KimDocumentRoleMember membershipToCheck, int membershipToCheckIndex, Set<String> uniqueQualifierAttributes, int roleIndex, List<RemotableAttributeError> validationErrors) {
    	boolean foundError = false;
    	int count = 0;

    	for (KimDocumentRoleMember membership : role.getRolePrncpls()) {
            if (sameMembershipQualifications(membershipToCheck, membership, uniqueQualifierAttributes)) {
                if (count == 0 ) {
                    count +=1;
                } else {
                    count += 1;
                    foundError = true;

        		    int qualifierCount = 0;

		    	    for (KimDocumentRoleQualifier qualifier : membership.getQualifiers()) {
			    	    if (qualifier != null && uniqueQualifierAttributes.contains(qualifier.getKimAttrDefnId())) {
						    validationErrors.add(RemotableAttributeError.Builder.create("document.roles["+roleIndex+"].rolePrncpls["+membershipToCheckIndex+"].qualifiers["+qualifierCount+"].attrVal", RiceKeyConstants.ERROR_DOCUMENT_IDENTITY_MANAGEMENT_PERSON_QUALIFIER_VALUE_NOT_UNIQUE+":"+qualifier.getKimAttribute().getAttributeName()+";"+qualifier.getAttrVal()).build());
				   	    }
				        qualifierCount += 1;
				    }
                }
    		}
    	}
    	return foundError;
    }

    /**
     * Determines if two seperate memberships have the same qualifications
     * @param membershipA the first membership to check
     * @param membershipB the second membership to check
     * @param uniqueQualifierAttributes the set of qualifier attributes which need to be unique
     * @return true if equal, false if otherwise
     */
    protected boolean sameMembershipQualifications(KimDocumentRoleMember membershipA, KimDocumentRoleMember membershipB, Set<String> uniqueQualifierAttributes) {
    	boolean equalSoFar = true;
    	for (String uniqueQualifierAttributeDefinitionId : uniqueQualifierAttributes) {
    		final KimDocumentRoleQualifier qualifierA = membershipA.getQualifier(uniqueQualifierAttributeDefinitionId);
    		final KimDocumentRoleQualifier qualifierB = membershipB.getQualifier(uniqueQualifierAttributeDefinitionId);

    		if (qualifierA != null && qualifierB != null) {
    			equalSoFar &= (qualifierA.getAttrVal() == null && qualifierB.getAttrVal() == null) || (qualifierA.getAttrVal() == null || qualifierA.getAttrVal().equals(qualifierB.getAttrVal()));
    		}
    	}
    	return equalSoFar;
    }

    /**
     * Finds the set of unique qualification attributes for the given role
     *
     * @param role the role associated with this person
     * @param attributeDefinitions the Map of attribute definitions where we can find out if a KimAttribute is supposed to be unique
     * @return a Set of attribute definition ids for qualifications which are supposed to be unique
     */
    public Set<String> findUniqueQualificationAttributes(PersonDocumentRole role, List<KimAttributeField> attributeDefinitions) {
    	Set<String> uniqueQualifications = new HashSet<String>();

    	if (role.getRolePrncpls() != null && role.getRolePrncpls().size() > 1) {
    		final KimDocumentRoleMember membership = role.getRolePrncpls().get(0);
    		for (KimDocumentRoleQualifier qualifier: membership.getQualifiers()) {
    			if (qualifier != null && qualifier.getKimAttribute() != null && !StringUtils.isBlank(qualifier.getKimAttribute().getAttributeName())) {
    	    		final KimAttributeField relatedDefinition = DataDictionaryTypeServiceHelper.findAttributeField(
                            qualifier.getKimAttribute().getAttributeName(), attributeDefinitions);

    	    		if (relatedDefinition != null && relatedDefinition.isUnique()) {
    	    			uniqueQualifications.add(qualifier.getKimAttrDefnId());
    	    		}
    			}
    		}
    	}

    	return uniqueQualifications;
    }

    protected boolean validActiveDatesForRole (List<PersonDocumentRole> roles ) {
    	boolean valid = true;
		int i = 0;
    	for(PersonDocumentRole role : roles ) {
			int j = 0;
    		for (KimDocumentRoleMember principal : role.getRolePrncpls()) {
    			valid &= validateActiveDate("roles["+i+"].rolePrncpls["+j+"].activeToDate",principal.getActiveFromDate(), principal.getActiveToDate());
    			j++;
    		}
    		i++;
    	}
    	return valid;
    }

    protected boolean validActiveDatesForGroup (List<PersonDocumentGroup> groups ) {
    	boolean valid = true;
		int i = 0;
    	for(PersonDocumentGroup group : groups ) {
     		valid &= validateActiveDate("groups["+i+"].activeToDate",group.getActiveFromDate(), group.getActiveToDate());
    		i++;
    	}
    	return valid;
    }

    protected boolean validActiveDatesForDelegations(List<RoleDocumentDelegationMember> delegationMembers) {
    	boolean valid = true;
		int i = 0;
		for(RoleDocumentDelegationMember delegationMember: delegationMembers){
     		valid &= validateActiveDate("delegationMembers["+i+"].activeToDate", delegationMember.getActiveFromDate(), delegationMember.getActiveToDate());
    		i++;
		}
    	return valid;
    }

	protected boolean validateActiveDate(String errorPath, Timestamp activeFromDate, Timestamp activeToDate) {
		// TODO : do not have detail bus rule yet, so just check this for now.
		boolean valid = true;
		if (activeFromDate != null && activeToDate !=null && activeToDate.before(activeFromDate)) {
	        GlobalVariables.getMessageMap().putError(errorPath, RiceKeyConstants.ERROR_ACTIVE_TO_DATE_BEFORE_FROM_DATE);
            valid = false;
		}
		return valid;
	}

    @Override
    public boolean processAddGroup(AddGroupEvent addGroupEvent) {
        return getAddGroupRule().processAddGroup(addGroupEvent);
    }

    @Override
    public boolean processAddRole(AddRoleEvent addRoleEvent) {
        return getAddRoleRule().processAddRole(addRoleEvent);
    }

    @Override
    public boolean processAddPersonDelegationMember(AddPersonDelegationMemberEvent addPersonDelegationMemberEvent){
    	return getAddPersonDelegationMemberRule().processAddPersonDelegationMember(addPersonDelegationMemberEvent);
    }

    protected IdentityService getIdentityService() {
		if ( identityService == null ) {
			identityService = KimApiServiceLocator.getIdentityService();
		}
		return identityService;
	}

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


	protected IdentityManagementKimDocumentAuthorizer getAuthorizer(IdentityManagementPersonDocument document) {
		if ( authorizer == null ) {
			authorizer = (IdentityManagementKimDocumentAuthorizer) KRADServiceLocatorWeb.getDocumentDictionaryService().getDocumentAuthorizer(document);
		}
		return authorizer;
	}



	/**
	 * @return the addGroupRuleClass
	 */
	public Class<? extends AddGroupRule> getAddGroupRuleClass() {
		return this.addGroupRuleClass;
	}



	/**
	 * Can be overridden by subclasses to indicate the rule class to use when adding groups.
	 *
	 * @param addGroupRuleClass the addGroupRuleClass to set
	 */
	public void setAddGroupRuleClass(Class<? extends AddGroupRule> addGroupRuleClass) {
		this.addGroupRuleClass = addGroupRuleClass;
	}



	/**
	 * @return the addRoleRuleClass
	 */
	public Class<? extends AddRoleRule> getAddRoleRuleClass() {
		return this.addRoleRuleClass;
	}



	/**
	 * Can be overridden by subclasses to indicate the rule class to use when adding roles.
	 *
	 * @param addRoleRuleClass the addRoleRuleClass to set
	 */
	public void setAddRoleRuleClass(Class<? extends AddRoleRule> addRoleRuleClass) {
		this.addRoleRuleClass = addRoleRuleClass;
	}



	/**
	 * @return the addGroupRule
	 */
	public AddGroupRule getAddGroupRule() {
		if ( addGroupRule == null ) {
			try {
				addGroupRule = addGroupRuleClass.newInstance();
			} catch ( Exception ex ) {
				throw new RuntimeException( "Unable to create AddGroupRule instance using class: " + addGroupRuleClass, ex );
			}
		}
		return addGroupRule;
	}



	/**
	 * @return the addRoleRule
	 */
	public AddRoleRule getAddRoleRule() {
		if ( addRoleRule == null ) {
			try {
				addRoleRule = addRoleRuleClass.newInstance();
			} catch ( Exception ex ) {
				throw new RuntimeException( "Unable to create AddRoleRule instance using class: " + addRoleRuleClass, ex );
			}
		}
		return addRoleRule;
	}

	/**
	 * @return the addRoleRule
	 */
	public AddPersonDelegationMemberRule getAddPersonDelegationMemberRule() {
		if(addPersonDelegationMemberRule == null){
			try {
				addPersonDelegationMemberRule = addPersonDelegationMemberRuleClass.newInstance();
			} catch ( Exception ex ) {
				throw new RuntimeException( "Unable to create AddPersonDelegationMemberRuleClass instance using class: " + addPersonDelegationMemberRuleClass, ex );
			}
		}
		return addPersonDelegationMemberRule;
	}

	@Override
    public boolean processAddPersonDocumentRoleQualifier(IdentityManagementPersonDocument document, PersonDocumentRole role, KimDocumentRoleMember kimDocumentRoleMember, int selectedRoleIdx) {
		boolean dateValidationSuccess = validateActiveDate("document.roles[" + selectedRoleIdx + "].newRolePrncpl.activeFromDate", kimDocumentRoleMember.getActiveFromDate(), kimDocumentRoleMember.getActiveToDate());
		String errorPath = "roles[" + selectedRoleIdx + "].newRolePrncpl";
		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
        KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(KimTypeBo.to(role.getKimRoleType()));

		List<RemotableAttributeError> errorsAttributesAgainstExisting;
	    boolean rulePassed = true;
	    Map<String, String> newMemberQualifiers = attributeValidationHelper.convertQualifiersToMap(kimDocumentRoleMember.getQualifiers());
	    Map<String, String> oldMemberQualifiers;
	    List<String> roleIds = new ArrayList<String>();
	    roleIds.add(role.getRoleId());
	    for(KimDocumentRoleMember member: role.getRolePrncpls()){
	    	oldMemberQualifiers = member.getQualifierAsMap();
	    	errorsAttributesAgainstExisting = kimTypeService.validateUniqueAttributes(
	    			role.getKimRoleType().getId(), newMemberQualifiers, oldMemberQualifiers);
	    	validationErrors.addAll(
					attributeValidationHelper.convertErrors(errorPath, attributeValidationHelper
                            .convertQualifiersToAttrIdxMap(kimDocumentRoleMember.getQualifiers()),
                            errorsAttributesAgainstExisting));
	    }

        if ( kimTypeService != null ) {
        	List<RemotableAttributeError> localErrors = kimTypeService.validateAttributes( role.getKimRoleType().getId(), newMemberQualifiers );
    	    validationErrors.addAll( attributeValidationHelper.convertErrors(errorPath,
                    attributeValidationHelper.convertQualifiersToAttrIdxMap(kimDocumentRoleMember.getQualifiers()),
                    localErrors));
        }

        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
    	if (validationErrors.isEmpty()) {
    		rulePassed = dateValidationSuccess;
    	} else {
    		attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
    		rulePassed = false;
    	}
    	return rulePassed;
	}

    protected boolean validateDelegationMemberRoleQualifier(List<RoleDocumentDelegationMember> delegationMembers){
		List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
		boolean valid = false;
		int memberCounter = 0;
        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
		for(RoleDocumentDelegationMember delegationMember: delegationMembers) {
		    KimType kimType = KimTypeBo.to(delegationMember.getRoleBo().getKimRoleType());
		    KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
			String errorPath = "delegationMembers["+memberCounter+"]";
	        Map<String, String> mapToValidate = attributeValidationHelper.convertQualifiersToMap(delegationMember.getQualifiers());
	        List<RemotableAttributeError> errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
			validationErrors.addAll(
					attributeValidationHelper.convertErrors(errorPath,
                            attributeValidationHelper.convertQualifiersToAttrIdxMap(delegationMember.getQualifiers()),
                            errorsTemp));

			List<RoleMember> roleMembers = getRoleService().findRoleMembers(QueryByCriteria.Builder.fromPredicates(equal(KimConstants.PrimaryKeyConstants.ID, delegationMember.getRoleMemberId()))).getResults();
			if(roleMembers.isEmpty()){
				valid = false;
				GlobalVariables.getMessageMap().putError("document."+errorPath, RiceKeyConstants.ERROR_DELEGATE_ROLE_MEMBER_ASSOCIATION, new String[]{});
			} else{
				kimTypeService.validateUnmodifiableAttributes(kimType.getId(), roleMembers.get(0).getAttributes(), mapToValidate);
				validationErrors.addAll(
						attributeValidationHelper.convertErrors(errorPath, attributeValidationHelper
                                .convertQualifiersToAttrIdxMap(delegationMember.getQualifiers()), errorsTemp));
			}
	        memberCounter++;
    	}
		GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
    	if (validationErrors.isEmpty()) {
    		valid = true;
    	} else {
    		attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
    		valid = false;
    	}
    	return valid;
    }

    public boolean canModifyEntity( String currentUserPrincipalId, String toModifyPrincipalId ){
        return (StringUtils.isNotBlank(currentUserPrincipalId) && StringUtils.isNotBlank(toModifyPrincipalId) &&
                currentUserPrincipalId.equals(toModifyPrincipalId)) ||
                getPermissionService().isAuthorized(
                        currentUserPrincipalId,
                        KimConstants.NAMESPACE_CODE,
                        KimConstants.PermissionNames.MODIFY_ENTITY,
                        Collections.singletonMap(KimConstants.AttributeConstants.PRINCIPAL_ID, currentUserPrincipalId));
    }

    public boolean canOverrideEntityPrivacyPreferences( String currentUserPrincipalId, String toModifyPrincipalId ){
        return (StringUtils.isNotBlank(currentUserPrincipalId) && StringUtils.isNotBlank(toModifyPrincipalId) &&
                currentUserPrincipalId.equals(toModifyPrincipalId)) ||
                getPermissionService().isAuthorized(
                        currentUserPrincipalId,
                        KimConstants.NAMESPACE_CODE,
                        KimConstants.PermissionNames.OVERRIDE_ENTITY_PRIVACY_PREFERENCES,
                        Collections.singletonMap(KimConstants.AttributeConstants.PRINCIPAL_ID, currentUserPrincipalId) );
    }

}
