/**
   * Update the calculated person data. This method and updateCalculatedPersonOnDeleteOfSor need to
   * be generalized to handle recalculations.
   *
   * @param toPerson
   * @param fromPerson
   * @param sorPerson Adjust calculated roles... Point prc_role to prc_person receiving role Add the
   *     role to the set of roles in receiving prc_person Remove role from prc person losing role
   */
  protected void updateCalculatedPersonsOnMoveOfSor(
      final Person toPerson, final Person fromPerson, final SorPerson sorPerson) {
    Assert.notNull(toPerson, "toPerson cannot be null");
    Assert.notNull(fromPerson, "fromPerson cannot be null");
    logger.info("UpdateCalculated: recalculating person data for move.");

    final List<Role> rolesToDelete = new ArrayList<Role>();

    final List<SorRole> sorRoles = new ArrayList<SorRole>(sorPerson.getRoles());
    for (final SorRole sorRole : sorRoles) {
      for (final Role role : fromPerson.getRoles()) {
        if (sorRole.getId().equals(role.getSorRoleId())) {
          sorRoleElector.addSorRole(sorRole, toPerson);
          rolesToDelete.add(role);
        }
      }
    }
    for (final Role role : rolesToDelete) {
      sorRoleElector.removeCalculatedRole(
          fromPerson, role, this.personRepository.getSoRRecordsForPerson(fromPerson));
      fromPerson.getRoles().remove(role);
    }

    // TODO recalculate names for person receiving role? Anything else?
    // TODO recalculate names for person losing role? Anything else?
    //        this.personRepository.savePerson(fromPerson);
    //        this.personRepository.savePerson(toPerson);
  }
  public ServiceExecutionResult<ReconciliationResult> reconcile(
      final ReconciliationCriteria reconciliationCriteria) throws IllegalArgumentException {
    Assert.notNull(reconciliationCriteria, "reconciliationCriteria cannot be null");
    logger.info("reconcile start");
    final Set validationErrors = this.validator.validate(reconciliationCriteria);

    if (!validationErrors.isEmpty()) {
      Iterator iter = validationErrors.iterator();
      while (iter.hasNext()) {
        logger.info("validation errors: " + iter.next());
      }
      logger.info("reconcile start");
      // since because of existing design we cannot raise exception, we can only rollback the
      // transaction through code
      // OR-384
      if (TransactionAspectSupport.currentTransactionStatus() != null) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }

      return new GeneralServiceExecutionResult<ReconciliationResult>(validationErrors);
    }

    final ReconciliationResult result = this.reconciler.reconcile(reconciliationCriteria);
    // (reconciliationCriteria, result);
    return new GeneralServiceExecutionResult<ReconciliationResult>(result);
  }
  /**
   * Persists an SorPerson on update.
   *
   * @param sorPerson the person to update.
   * @return serviceExecutionResult.
   */
  public ServiceExecutionResult<SorPerson> updateSorPerson(final SorPerson sorPerson) {
    final Set validationErrors = this.validator.validate(sorPerson);

    if (!validationErrors.isEmpty()) {
      Iterator iter = validationErrors.iterator();
      while (iter.hasNext()) {
        logger.info("validation errors: " + iter.next());
      }
      // since because of existing design we cannot raise exception, we can only rollback the
      // transaction through code
      // OR-384
      if (TransactionAspectSupport.currentTransactionStatus() != null) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }

      return new GeneralServiceExecutionResult<SorPerson>(validationErrors);
    }

    // do reconciliationCheck to make sure that modifications do not cause person to reconcile to a
    // different person
    if (!this.reconciler.reconcilesToSamePerson(sorPerson)) {
      throw new IllegalStateException();
    }

    // Iterate over any sorRoles setting sorid and source id if not specified by SoR.
    for (final SorRole sorRole : sorPerson.getRoles()) {
      setRoleIdAndSource(sorRole, sorPerson.getSourceSor());
    }

    // Save Sor Person
    final SorPerson savedSorPerson = this.personRepository.saveSorPerson(sorPerson);

    Person person = this.findPersonById(savedSorPerson.getPersonId());

    Assert.notNull(person, "person cannot be null.");

    logger.info(
        "Verifying Number of calculated Roles before the calculate: " + person.getRoles().size());

    // Iterate over sorRoles. SorRoles may be new or updated.
    for (final SorRole savedSorRole : savedSorPerson.getRoles()) {
      logger.info(
          "DefaultPersonService: savedSorPersonRole Found, savedSorRoleID: "
              + savedSorRole.getId());
      logger.info("DefaultPersonService: savedSorPersonRole Found, Role Must be newly added.");
      // let sor role elector decide if this new role can be converted to calculated one
      sorRoleElector.addSorRole(savedSorRole, person);
      logger.info(
          "Verifying Number of calculated Roles after calculate: " + person.getRoles().size());
    }

    for (final IdentifierAssigner ia : this.identifierAssigners) {
      ia.addIdentifierTo(sorPerson, person);
    }

    person = recalculatePersonBiodemInfo(person, savedSorPerson, RecalculationType.UPDATE, false);
    person = this.personRepository.savePerson(person);

    return new GeneralServiceExecutionResult<SorPerson>(savedSorPerson);
  }
  @PreAuthorize("hasPermission(#sorRole, 'admin')")
  public ServiceExecutionResult<SorRole> validateAndSaveRoleForSorPerson(
      final SorPerson sorPerson, final SorRole sorRole) {
    logger.info(" validateAndSaveRoleForSorPerson start");
    Assert.notNull(sorPerson, "SorPerson cannot be null.");
    Assert.notNull(sorRole, "SorRole cannot be null.");

    // check if the SoR Role has an ID assigned to it already and assign source sor
    setRoleIdAndSource(sorRole, sorPerson.getSourceSor());

    final Set validationErrors = this.validator.validate(sorRole);

    if (!validationErrors.isEmpty()) {
      // since because of existing design we cannot raise exception, we can only rollback the
      // transaction through code
      // OR-384
      if (TransactionAspectSupport.currentTransactionStatus() != null) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }

      return new GeneralServiceExecutionResult<SorRole>(validationErrors);
    }

    final SorPerson newSorPerson = this.personRepository.saveSorPerson(sorPerson);
    Person person = this.personRepository.findByInternalId(newSorPerson.getPersonId());
    final SorRole newSorRole = newSorPerson.findSorRoleBySorRoleId(sorRole.getSorId());
    // let sor role elector decide if this new role can be converted to calculated one
    sorRoleElector.addSorRole(newSorRole, person);
    person = recalculatePersonBiodemInfo(person, newSorPerson, RecalculationType.UPDATE, false);
    this.personRepository.savePerson(person);
    logger.info("validateAndSaveRoleForSorPerson end");
    return new GeneralServiceExecutionResult<SorRole>(newSorRole);
  }
  public ServiceExecutionResult<Person> addPerson(
      final ReconciliationCriteria reconciliationCriteria)
      throws ReconciliationException, IllegalArgumentException, SorPersonAlreadyExistsException {
    Assert.notNull(reconciliationCriteria, "reconciliationCriteria cannot be null");
    logger.info("addPerson start");
    if (reconciliationCriteria.getSorPerson().getSorId() != null
        && this.findBySorIdentifierAndSource(
                reconciliationCriteria.getSorPerson().getSourceSor(),
                reconciliationCriteria.getSorPerson().getSorId())
            != null) {
      // throw new IllegalStateException("CANNOT ADD SAME SOR RECORD.");
      throw new SorPersonAlreadyExistsException(
          this.findBySorIdentifierAndSource(
              reconciliationCriteria.getSorPerson().getSourceSor(),
              reconciliationCriteria.getSorPerson().getSorId()));
    }

    final Set validationErrors = this.validator.validate(reconciliationCriteria);

    if (!validationErrors.isEmpty()) {
      Iterator iter = validationErrors.iterator();
      while (iter.hasNext()) {
        logger.info("validation errors: " + iter.next());
      }
      // since because of existing design we cannot raise exception, we can only rollback the
      // transaction through code
      // OR-384
      if (TransactionAspectSupport.currentTransactionStatus() != null) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }

      return new GeneralServiceExecutionResult<Person>(validationErrors);
    }

    final ReconciliationResult result = this.reconciler.reconcile(reconciliationCriteria);

    switch (result.getReconciliationType()) {
      case NONE:
        return new GeneralServiceExecutionResult<Person>(
            saveSorPersonAndConvertToCalculatedPerson(reconciliationCriteria));

      case EXACT:
        return new GeneralServiceExecutionResult<Person>(
            addNewSorPersonAndLinkWithMatchedCalculatedPerson(reconciliationCriteria, result));
    }

    this.criteriaCache.put(reconciliationCriteria, result);
    logger.info("addPerson start");
    throw new ReconciliationException(result);
  }
  public ServiceExecutionResult<Person> forceAddPerson(
      final ReconciliationCriteria reconciliationCriteria)
      throws IllegalArgumentException, IllegalStateException {
    Assert.notNull(reconciliationCriteria, "reconciliationCriteria cannot be null.");
    logger.info("forceAddPerson start");
    final ReconciliationResult result = this.criteriaCache.get(reconciliationCriteria);

    if (result == null) {
      throw new IllegalStateException("No ReconciliationResult found for provided criteria.");
    }

    this.criteriaCache.remove(reconciliationCriteria);
    logger.info("forceAddPerson end");
    return new GeneralServiceExecutionResult<Person>(
        saveSorPersonAndConvertToCalculatedPerson(reconciliationCriteria));
  }
  /**
   * Current workflow for converting an SorPerson into the actual Person.
   *
   * @param reconciliationCriteria the original search criteria.
   * @return the newly saved Person.
   */
  protected Person saveSorPersonAndConvertToCalculatedPerson(
      final ReconciliationCriteria reconciliationCriteria) {
    if (!StringUtils.hasText(reconciliationCriteria.getSorPerson().getSorId())) {
      reconciliationCriteria.getSorPerson().setSorId(this.identifierGenerator.generateNextString());
    }

    logger.info("Executing new code!!!!!!");

    // Iterate over any sorRoles setting sorid and source id
    for (final SorRole sorRole : reconciliationCriteria.getSorPerson().getRoles()) {
      setRoleIdAndSource(sorRole, reconciliationCriteria.getSorPerson().getSourceSor());
    }

    logger.info(
        "Creating sorPerson: person_id: " + reconciliationCriteria.getSorPerson().getPersonId());
    // Save Sor Person
    final SorPerson sorPerson =
        this.personRepository.saveSorPerson(reconciliationCriteria.getSorPerson());
    Person savedPerson =
        recalculatePersonBiodemInfo(
            this.personObjectFactory.getObject(), sorPerson, RecalculationType.ADD, false);
    savedPerson = this.personRepository.savePerson(savedPerson);

    for (final IdentifierAssigner ia : this.identifierAssigners) {
      ia.addIdentifierTo(sorPerson, savedPerson);
    }
    idCardGenerator.addIDCard(savedPerson);

    // Create calculated roles.
    for (final SorRole newSorRole : sorPerson.getRoles()) {
      // let sor role elector decide if this new role can be converted to calculated one
      sorRoleElector.addSorRole(newSorRole, savedPerson);
    }

    final Person newPerson = this.personRepository.savePerson(savedPerson);

    logger.info("Verifying Number of calculated Roles: " + newPerson.getRoles().size());

    // Now connect the SorPerson to the actual person
    sorPerson.setPersonId(newPerson.getId());
    this.personRepository.saveSorPerson(sorPerson);
    logger.info("Created sorPerson: person_id: " + sorPerson.getPersonId());

    return newPerson;
  }
  public ServiceExecutionResult<Person> validateAndSavePersonAndRole(
      final ReconciliationCriteria reconciliationCriteria) {
    logger.info(" validateAndSavePersonAndRole start");
    SorPerson sorPerson = reconciliationCriteria.getSorPerson();
    if (sorPerson == null || sorPerson.getRoles() == null)
      throw new IllegalArgumentException("Sor Person not found in provided criteria.");
    SorRole sorRole = reconciliationCriteria.getSorPerson().getRoles().get(0);
    if (sorRole == null)
      throw new IllegalArgumentException("Sor Role not found for provided criteria.");

    setRoleIdAndSource(sorRole, sorPerson.getSourceSor());

    final Set validationErrors = this.validator.validate(sorRole);

    if (!validationErrors.isEmpty()) {
      // since because of existing design we cannot raise exception, we can only rollback the
      // transaction through code
      // OR-384
      if (TransactionAspectSupport.currentTransactionStatus() != null) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }

      return new GeneralServiceExecutionResult<Person>(validationErrors);
    }

    Long personId = sorPerson.getPersonId();
    if (personId == null) {
      logger.info("calling saveSorPersonAndConvertToCalculatedPerson");
      // add new Sor Person and roles, create calculated person
      return new GeneralServiceExecutionResult<Person>(
          saveSorPersonAndConvertToCalculatedPerson(reconciliationCriteria));
    } else { // Add new Sor Person and role and link to the existing person
      Person thisPerson = this.personRepository.findByInternalId(personId);
      try {
        logger.info("calling addSorPersonAndLink");
        Person savedPerson = this.addSorPersonAndLink(reconciliationCriteria, thisPerson);
        return new GeneralServiceExecutionResult<Person>(savedPerson);
      } catch (SorPersonAlreadyExistsException sorE) {
        throw new IllegalArgumentException(
            "If a sor Person of the same source already exists, should call the other method to add the role only");
      }
    }
  }
  public ServiceExecutionResult<Person> addPersonAndLink(
      final ReconciliationCriteria reconciliationCriteria, final Person person)
      throws IllegalArgumentException, IllegalStateException, SorPersonAlreadyExistsException {
    Assert.notNull(reconciliationCriteria, "reconciliationCriteria cannot be null.");
    Assert.notNull(person, "person cannot be null.");
    logger.info(" addPersonAndLink start");
    final ReconciliationResult result = this.criteriaCache.get(reconciliationCriteria);

    if (result == null) {
      throw new IllegalStateException("No ReconciliationResult found for provided criteria.");
    }

    for (final PersonMatch personMatch : result.getMatches()) {
      if (personMatch.getPerson().getId().equals(person.getId())) {
        addSorPersonAndLink(reconciliationCriteria, person);
        final Person savedPerson = this.personRepository.findByInternalId(person.getId());
        return new GeneralServiceExecutionResult<Person>(savedPerson);
      }
    }
    logger.info("addPersonAndLink end");
    throw new IllegalStateException("Person not found in ReconciliationResult.");
  }
  /**
   * Move All Sor Records from one person to another.
   *
   * @param fromPerson person losing sor records.
   * @param toPerson person receiving sor records.
   * @return Result of move. Validation errors if they occurred or the Person receiving sor records.
   */
  public boolean moveAllSystemOfRecordPerson(Person fromPerson, Person toPerson) {
    // get the list of sor person records that will be moving.
    List<SorPerson> sorPersonList = personRepository.getSoRRecordsForPerson(fromPerson);

    // move each sorRecord
    for (final SorPerson sorPerson : sorPersonList) {
      moveSystemOfRecordPerson(fromPerson, toPerson, sorPerson);
    }

    Set<? extends Identifier> oldIdentifiers = fromPerson.getIdentifiers();
    Set<? extends IdCard> oldIdCards = fromPerson.getIdCards();

    this.personRepository.deletePerson(fromPerson);
    logger.info("moveAllSystemOfRecordPerson: Deleted From Person");
    for (Identifier identifier : oldIdentifiers) {

      if (toPerson.getIdentifiersByType().get(identifier.getType().getName()) == null) {
        Identifier oldIdentifierAttachedTotoPerson =
            toPerson.addIdentifier(identifier.getType(), identifier.getValue());
        /// if type of this identifier don't exist then add this identifier as primary and not
        // deleted

        oldIdentifierAttachedTotoPerson.setDeleted(false);
        oldIdentifierAttachedTotoPerson.setPrimary(true);
      }
      // and if this exist then add this identifier as deleted and no primary
      else {
        Identifier oldIdentifierAttachedTotoPerson =
            toPerson.addIdentifier(identifier.getType(), identifier.getValue());
        /// if type of this identifier don't exist then add this identifier as primary and not
        // deleted

        oldIdentifierAttachedTotoPerson.setDeleted(true);
        oldIdentifierAttachedTotoPerson.setPrimary(false);
        ;
      }
    }
    for (IdCard oldIdCard : oldIdCards) {
      if (toPerson.getPrimaryIdCard() == null) {
        toPerson.addIDCard(oldIdCard);
      } else {
        if (oldIdCard.isPrimary()) oldIdCard.setPrimary(false);
        toPerson.addIDCard(oldIdCard);
      }
    }

    this.personRepository.savePerson(toPerson);

    return true;
  }
  protected Person recalculatePersonBiodemInfo(
      final Person person,
      final SorPerson sorPerson,
      final RecalculationType recalculationType,
      boolean mistake) {
    final List<SorPerson> sorPersons = this.personRepository.getSoRRecordsForPerson(person);
    logger.info("recalculatePersonBiodemInfo: start");
    if (recalculationType == RecalculationType.ADD
        || (recalculationType == RecalculationType.DELETE && !mistake)) {
      sorPersons.add(sorPerson);
    }

    copySorNamesToPerson(person, sorPersons);

    final Date birthDate =
        this.birthDateFieldElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);
    final String gender =
        this.genderFieldElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);
    final SorName preferredName =
        this.preferredNameFieldElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);
    final SorName officialName =
        this.officialNameFieldElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);
    final EmailAddress emailAddress =
        this.preferredContactEmailAddressFieldElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);
    final Phone phone =
        this.preferredContactPhoneNumberFieldElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);
    final Map<String, String> attributes =
        this.attributesElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);
    final SorDisclosureSettings disclosure =
        this.disclosureFieldElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);

    final String ssn =
        this.ssnFieldElector.elect(
            sorPerson, sorPersons, recalculationType == RecalculationType.DELETE);
    Identifier primarySSN = person.getPrimaryIdentifiersByType().get("SSN");
    // check if the elector elcted some ssn and person does have previous ssn assigned to it
    if (!org.apache.commons.lang.StringUtils.isEmpty(ssn) && primarySSN != null) {
      try {
        this.identifierChangeService.change(person.getPrimaryIdentifiersByType().get("SSN"), ssn);
      } catch (IllegalArgumentException e) {
        logger.debug(e.getStackTrace().toString());
      } // all other exception should be propogated
    }

    person.setDateOfBirth(birthDate);
    person.setGender(gender);
    person.getPreferredContactEmailAddress().update(emailAddress);
    person.getPreferredContactPhoneNumber().update(phone);
    person.calculateDisclosureSettings(disclosure);
    person.setAttributes(attributes);

    String affiliation = "";
    Type affiliationType = null;
    if (disclosure != null) {
      logger.info(
          "after person.calculateDisclosureSettings, disclosure code : "
              + disclosure.getDisclosureCode());
    } else {
      logger.info("Disclosure is null");
    }
    List<SorRole> sorroles = sorPerson.getRoles();
    for (SorRole role : sorroles) {
      if (role != null) {
        logger.info("Role = " + role.getTitle());
        if (role.getAffiliationType() != null) {
          logger.info("Role desc= " + role.getAffiliationType().getDescription());
          affiliation = role.getAffiliationType().getDescription();

          if (person.getDisclosureSettings() != null) {
            logger.info("recalculating disclosure setting 1...");
            // person.getDisclosureSettings().recalculate(this.strategyRepository.getDisclosureRecalculationStrategy());
            person
                .getDisclosureSettings()
                .recalculate(
                    this.strategyRepository.getDisclosureRecalculationStrategy(),
                    affiliation,
                    referenceRepository);
          }
        }
      }
    }

    // SSN election is happening in the ssn identifier assigner.

    boolean preferred = false;
    boolean official = false;

    for (final Name name : person.getNames()) {
      if (!preferred && name.sameAs(preferredName)) {
        name.setPreferredName(true);
        preferred = true;
      }

      if (!official && name.sameAs(officialName)) {
        name.setOfficialName(true);
        official = true;
      }

      if (official && preferred) {
        break;
      }
    }
    logger.info("recalculatePersonBiodemInfo: end");
    //        return this.personRepository.savePerson(person);
    return person;
  }