/**
   * Validates a collection of validation rules.
   *
   * @param period the period to validate for.
   * @param source the source to validate for.
   * @param validationRules the rules to validate.
   * @param dataElementsInRules the data elements which are part of the rules expressions.
   * @param constantMap the constants which are part of the rule expressions.
   * @param currentSize the current number of validation violations.
   * @returns a collection of rules that did not pass validation.
   */
  private Collection<ValidationResult> validateInternal(
      Period period,
      OrganisationUnit unit,
      Collection<ValidationRule> validationRules,
      Set<DataElement> dataElementsInRules,
      Map<String, Double> constantMap,
      int currentSize) {
    Map<DataElementOperand, Double> valueMap =
        dataValueService.getDataValueMap(dataElementsInRules, period, unit);

    final Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();

    if (currentSize < MAX_VIOLATIONS) {
      Double leftSide = null;
      Double rightSide = null;

      boolean violation = false;

      for (final ValidationRule validationRule : validationRules) {
        if (validationRule.getPeriodType() != null
            && validationRule.getPeriodType().equals(period.getPeriodType())) {
          Operator operator = validationRule.getOperator();

          leftSide =
              expressionService.getExpressionValue(
                  validationRule.getLeftSide(), valueMap, constantMap, null);

          if (leftSide != null || Operator.compulsory_pair.equals(operator)) {
            rightSide =
                expressionService.getExpressionValue(
                    validationRule.getRightSide(), valueMap, constantMap, null);

            if (rightSide != null || Operator.compulsory_pair.equals(operator)) {
              if (Operator.compulsory_pair.equals(operator)) {
                violation =
                    (leftSide != null && rightSide == null)
                        || (leftSide == null && rightSide != null);
              } else if (leftSide != null && rightSide != null) {
                violation = !expressionIsTrue(leftSide, operator, rightSide);
              }

              if (violation) {
                validationViolations.add(
                    new ValidationResult(
                        period,
                        unit,
                        validationRule,
                        getRounded(zeroIfNull(leftSide), DECIMALS),
                        getRounded(zeroIfNull(rightSide), DECIMALS)));
              }
            }
          }
        }
      }
    }

    return validationViolations;
  }
  public Collection<DataElement> getDataElementsInValidationRules() {
    Set<DataElement> dataElements = new HashSet<DataElement>();

    for (ValidationRule rule : getAllValidationRules()) {
      dataElements.addAll(rule.getLeftSide().getDataElementsInExpression());
      dataElements.addAll(rule.getRightSide().getDataElementsInExpression());
    }

    return dataElements;
  }
  /**
   * Returns all validation rules referred to in the left and right side expressions of the given
   * validation rules.
   *
   * @param validationRules the validation rules.
   * @return a collection of data elements.
   */
  private Set<DataElement> getDataElementsInValidationRules(
      Collection<ValidationRule> validationRules) {
    Set<DataElement> dataElements = new HashSet<DataElement>();

    for (ValidationRule rule : validationRules) {
      dataElements.addAll(rule.getLeftSide().getDataElementsInExpression());
      dataElements.addAll(rule.getRightSide().getDataElementsInExpression());
    }

    return dataElements;
  }
  /**
   * Generate and send an alert message containing a list of validation results to a set of users.
   *
   * @param results results to put in this message
   * @param users users to receive these results
   * @param scheduledRunStart date/time when the scheduled run started
   */
  private void sendAlertmessage(
      SortedSet<ValidationResult> results, Set<User> users, Date scheduledRunStart) {
    StringBuilder builder = new StringBuilder();

    SimpleDateFormat dateTimeFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm");

    Map<String, Integer> importanceCountMap = countResultsByImportanceType(results);

    String subject =
        "Alerts as of "
            + dateTimeFormatter.format(scheduledRunStart)
            + ": High "
            + (importanceCountMap.get("high") == null ? 0 : importanceCountMap.get("high"))
            + ", Medium "
            + (importanceCountMap.get("medium") == null ? 0 : importanceCountMap.get("medium"))
            + ", Low "
            + (importanceCountMap.get("low") == null ? 0 : importanceCountMap.get("low"));

    // TODO use velocity template for message

    for (ValidationResult result : results) {
      ValidationRule rule = result.getValidationRule();

      builder
          .append(result.getOrgUnit().getName())
          .append(" ")
          .append(result.getPeriod().getName())
          .append(
              result.getAttributeOptionCombo().isDefault()
                  ? ""
                  : " " + result.getAttributeOptionCombo().getName())
          .append(LN)
          .append(rule.getName())
          .append(" (")
          .append(rule.getImportance())
          .append(") ")
          .append(LN)
          .append(rule.getLeftSide().getDescription())
          .append(": ")
          .append(result.getLeftsideValue())
          .append(LN)
          .append(rule.getRightSide().getDescription())
          .append(": ")
          .append(result.getRightsideValue())
          .append(LN)
          .append(LN);
    }

    log.info("Alerting users: " + users.size() + ", subject: " + subject);

    messageService.sendMessage(subject, builder.toString(), null, users);
  }
  /**
   * Returns all validation rules which have data elements assigned to it which are members of the
   * given data set.
   *
   * @param dataSet the data set.
   * @return all validation rules which have data elements assigned to it which are members of the
   *     given data set.
   */
  private Collection<ValidationRule> getRelevantValidationRules(Set<DataElement> dataElements) {
    Set<ValidationRule> relevantValidationRules = new HashSet<ValidationRule>();

    Set<DataElement> validationRuleElements = new HashSet<DataElement>();

    for (ValidationRule validationRule : getAllValidationRules()) {
      validationRuleElements.clear();
      validationRuleElements.addAll(validationRule.getLeftSide().getDataElementsInExpression());
      validationRuleElements.addAll(validationRule.getRightSide().getDataElementsInExpression());

      if (dataElements.containsAll(validationRuleElements)) {
        relevantValidationRules.add(validationRule);
      }
    }

    return relevantValidationRules;
  }
  @Override
  public Collection<ValidationRule> getValidationTypeRulesForDataElements(
      Set<DataElement> dataElements) {
    Set<ValidationRule> rulesForDataElements = new HashSet<>();

    for (ValidationRule validationRule : getAllValidationRules()) {
      if (validationRule.getRuleType().equals(ValidationRule.RULE_TYPE_VALIDATION)) {
        Set<DataElement> validationRuleElements = new HashSet<>();
        validationRuleElements.addAll(validationRule.getLeftSide().getDataElementsInExpression());
        validationRuleElements.addAll(validationRule.getRightSide().getDataElementsInExpression());

        if (dataElements.containsAll(validationRuleElements)) {
          rulesForDataElements.add(validationRule);
        }
      }
    }

    return rulesForDataElements;
  }
  public Collection<ValidationRule> getRelevantValidationRules(DataSet dataSet) {
    Set<ValidationRule> relevantValidationRules = new HashSet<ValidationRule>();

    Set<DataElementOperand> operands = dataEntryFormService.getOperandsInDataEntryForm(dataSet);

    Set<DataElementOperand> validationRuleOperands = new HashSet<DataElementOperand>();

    for (ValidationRule validationRule : getAllValidationRules()) {
      validationRuleOperands.clear();
      validationRuleOperands.addAll(
          expressionService.getOperandsInExpression(validationRule.getLeftSide().getExpression()));
      validationRuleOperands.addAll(
          expressionService.getOperandsInExpression(validationRule.getRightSide().getExpression()));

      if (operands.containsAll(validationRuleOperands)) {
        relevantValidationRules.add(validationRule);
      }
    }

    return relevantValidationRules;
  }