/**
   * 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);
  }
  /**
   * This does not explicitly delete the names because its assumed the recalculation will clean it
   * up.
   */
  public boolean deleteSystemOfRecordPerson(
      final SorPerson sorPerson, final boolean mistake, final String terminationTypes) {
    Assert.notNull(sorPerson, "sorPerson cannot be null.");
    final String terminationTypeToUse =
        terminationTypes != null ? terminationTypes : Type.TerminationTypes.UNSPECIFIED.name();

    final Person person = this.personRepository.findByInternalId(sorPerson.getPersonId());
    Assert.notNull(person, "person cannot be null.");

    if (mistake) {
      Set<Role> rolesToDelete = new HashSet<Role>();

      for (final SorRole sorRole : sorPerson.getRoles()) {
        for (final Role role : person.getRoles()) {
          if (sorRole.getId().equals(role.getSorRoleId())) {
            rolesToDelete.add(role);
          }
        }
      }

      for (final Role role : rolesToDelete) {
        // let sorRoleElector delete the role and add another role if required
        sorRoleElector.removeCalculatedRole(
            person, role, this.personRepository.getSoRRecordsForPerson(person));
      }

      final Number number = this.personRepository.getCountOfSoRRecordsForPerson(person);

      if (number.intValue() == 1) {
        this.personRepository.deletePerson(person);
      }

      this.personRepository.deleteSorPerson(sorPerson);
      return true;
    }

    // we do this explicitly here because once they're gone we can't re-calculate?  We might move to
    // this to the recalculateCalculatedPerson method.
    final Type terminationReason =
        this.referenceRepository.findType(Type.DataTypes.TERMINATION, terminationTypeToUse);

    for (final SorRole sorRole : sorPerson.getRoles()) {
      for (final Role role : person.getRoles()) {
        if (!role.isTerminated() && sorRole.getId().equals(role.getSorRoleId())) {
          role.expireNow(terminationReason, true);
        }
      }
    }

    this.personRepository.deleteSorPerson(sorPerson);
    this.personRepository.savePerson(person);

    Person p = recalculatePersonBiodemInfo(person, sorPerson, RecalculationType.DELETE, mistake);
    this.personRepository.savePerson(p);
    return true;
  }
  public ServiceExecutionResult<SorRole> updateSorRole(
      final SorPerson sorPerson, final SorRole sorRole) {
    Assert.notNull(sorPerson, "sorPerson cannot be null.");
    Assert.notNull(sorRole, "sorRole cannot be null.");

    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 SorRole savedSorRole = this.personRepository.saveSorRole(sorRole);

    final Person person = this.personRepository.findByInternalId(sorPerson.getPersonId());
    final Role role = person.findRoleBySoRRoleId(savedSorRole.getId());
    if (role != null) {
      // update calculated role only if that role was previously converted to calculated one by
      // sorRoleElector
      role.recalculate(savedSorRole);
      this.personRepository.savePerson(person);
    }
    // else //do nothing i.e. don't update the calculated role if SorRoleElector Previously decided
    // not to convert this sor role to calculated role

    return new GeneralServiceExecutionResult<SorRole>(savedSorRole);
  }
  /**
   * 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);
  }
  public boolean deleteSystemOfRecordRole(
      final SorPerson sorPerson,
      final SorRole sorRole,
      final boolean mistake,
      final String terminationTypes)
      throws IllegalArgumentException {
    Assert.notNull(sorRole, "sorRole cannot be null.");
    Assert.notNull(sorPerson, "soPerson cannot be null.");
    final String terminationTypeToUse =
        terminationTypes != null ? terminationTypes : Type.TerminationTypes.UNSPECIFIED.name();

    final Person person = this.personRepository.findByInternalId(sorPerson.getPersonId());
    Assert.notNull(person, "person cannot be null.");

    final Role role = person.findRoleBySoRRoleId(sorRole.getId());
    // delete and expire role only if it exist at calculated level
    if (role != null)
      if (mistake) {

        // let SorRoleElector remove the role
        sorRoleElector.removeCalculatedRole(
            person, role, this.personRepository.getSoRRecordsForPerson(person));

      } else {
        final Type terminationReason =
            this.referenceRepository.findType(Type.DataTypes.TERMINATION, terminationTypeToUse);
        if (!role.isTerminated()) {
          role.expireNow(terminationReason, true);
        }
      }

    sorPerson.getRoles().remove(sorRole);
    this.personRepository.saveSorPerson(sorPerson);
    this.personRepository.savePerson(person);
    return true;
  }