@Override
  public List<String> validateResponseAttributes(
      List<FeedbackResponseAttributes> responses, int numRecipients) {
    if (responses.isEmpty()) {
      return new ArrayList<String>();
    }

    if (areDuplicatesAllowed) {
      return new ArrayList<String>();
    } else {
      List<String> errors = new ArrayList<>();

      for (FeedbackResponseAttributes response : responses) {
        FeedbackRankOptionsResponseDetails frd =
            (FeedbackRankOptionsResponseDetails) response.getResponseDetails();
        Set<Integer> responseRank = new HashSet<>();

        for (int answer : frd.getFilteredSortedAnswerList()) {
          if (responseRank.contains(answer)) {
            errors.add("Duplicate rank " + answer);
          }
          responseRank.add(answer);
        }
      }

      return errors;
    }
  }
  /**
   * From the feedback responses, generate a mapping of the option to a list of ranks received for
   * that option. The key of the map returned is the option name. The values of the map are list of
   * ranks received by the key.
   *
   * @param responses a list of responses
   */
  private Map<String, List<Integer>> generateOptionRanksMapping(
      List<FeedbackResponseAttributes> responses) {
    Map<String, List<Integer>> optionRanks = new HashMap<>();
    for (FeedbackResponseAttributes response : responses) {
      FeedbackRankOptionsResponseDetails frd =
          (FeedbackRankOptionsResponseDetails) response.getResponseDetails();

      List<Integer> answers = frd.getAnswerList();
      Map<String, Integer> mapOfOptionToRank = new HashMap<>();

      Assumption.assertEquals(answers.size(), options.size());

      for (int i = 0; i < options.size(); i++) {
        int rankReceived = answers.get(i);
        mapOfOptionToRank.put(options.get(i), rankReceived);
      }

      Map<String, Integer> normalisedRankForOption =
          obtainMappingToNormalisedRanksForRanking(mapOfOptionToRank, options);

      for (int i = 0; i < options.size(); i++) {
        String optionReceivingRanks = options.get(i);
        int rankReceived = normalisedRankForOption.get(optionReceivingRanks);

        if (rankReceived != Const.POINTS_NOT_SUBMITTED) {
          updateOptionRanksMapping(optionRanks, optionReceivingRanks, rankReceived);
        }
      }
    }
    return optionRanks;
  }
  @Override
  public String getQuestionWithExistingResponseSubmissionFormHtml(
      boolean sessionIsOpen,
      int qnIdx,
      int responseIdx,
      String courseId,
      int totalNumRecipients,
      FeedbackResponseDetails existingResponseDetails) {

    FeedbackRankOptionsResponseDetails existingResponse =
        (FeedbackRankOptionsResponseDetails) existingResponseDetails;
    StringBuilder optionListHtml = new StringBuilder();
    String optionFragmentTemplate =
        FeedbackQuestionFormTemplates.RANK_SUBMISSION_FORM_OPTIONFRAGMENT;

    for (int i = 0; i < options.size(); i++) {
      String optionFragment =
          FeedbackQuestionFormTemplates.populateTemplate(
              optionFragmentTemplate,
              "${qnIdx}",
              Integer.toString(qnIdx),
              "${responseIdx}",
              Integer.toString(responseIdx),
              "${optionIdx}",
              Integer.toString(i),
              "${disabled}",
              sessionIsOpen ? "" : "disabled=\"disabled\"",
              "${rankOptionVisibility}",
              "",
              "${options}",
              getSubmissionOptionsHtmlForRankingOptions(existingResponse.getAnswerList().get(i)),
              "${Const.ParamsNames.FEEDBACK_RESPONSE_TEXT}",
              Const.ParamsNames.FEEDBACK_RESPONSE_TEXT,
              "${rankOptionValue}",
              Sanitizer.sanitizeForHtml(options.get(i)));
      optionListHtml.append(optionFragment + Const.EOL);
    }

    String html =
        FeedbackQuestionFormTemplates.populateTemplate(
            FeedbackQuestionFormTemplates.RANK_SUBMISSION_FORM,
            "${rankSubmissionFormOptionFragments}",
            optionListHtml.toString(),
            "${qnIdx}",
            Integer.toString(qnIdx),
            "${responseIdx}",
            Integer.toString(responseIdx),
            "${rankOptionVisibility}",
            "",
            "${Const.ParamsNames.FEEDBACK_QUESTION_RANKTORECIPIENTS}",
            Const.ParamsNames.FEEDBACK_QUESTION_RANKTORECIPIENTS,
            "${rankToRecipientsValue}",
            "false",
            "${Const.ParamsNames.FEEDBACK_QUESTION_RANKNUMOPTION}",
            Const.ParamsNames.FEEDBACK_QUESTION_RANKNUMOPTIONS,
            "${rankNumOptionValue}",
            Integer.toString(options.size()),
            "${Const.ParamsNames.FEEDBACK_QUESTION_RANKISDUPLICATESALLOWED}",
            Const.ParamsNames.FEEDBACK_QUESTION_RANKISDUPLICATESALLOWED,
            "${areDuplicatesAllowedValue}",
            Boolean.toString(areDuplicatesAllowed));

    return html;
  }