gt:         List<InstructorAttributes> instructors = isEmailNeeded
context: package teammates.logic.api;
  
  import java.util.ArrayList;
  import java.util.Calendar;
  import java.util.List;
  
  import com.google.appengine.api.log.AppLogLine;
  
  import teammates.common.datatransfer.attributes.AccountAttributes;
  import teammates.common.datatransfer.attributes.CourseAttributes;
  import teammates.common.datatransfer.attributes.FeedbackSessionAttributes;
  import teammates.common.datatransfer.attributes.InstructorAttributes;
  import teammates.common.datatransfer.attributes.StudentAttributes;
  import teammates.common.exception.EntityDoesNotExistException;
  import teammates.common.util.Config;
  import teammates.common.util.Const;
  import teammates.common.util.EmailType;
  import teammates.common.util.EmailWrapper;
  import teammates.common.util.Logger;
  import teammates.common.util.SanitizationHelper;
  import teammates.common.util.StringHelper;
  import teammates.common.util.Templates;
  import teammates.common.util.Templates.EmailTemplates;
  import teammates.common.util.TimeHelper;
  import teammates.logic.core.CoursesLogic;
  import teammates.logic.core.FeedbackSessionsLogic;
  import teammates.logic.core.InstructorsLogic;
  import teammates.logic.core.StudentsLogic;
  
  /**
   * Handles operations related to generating emails to be sent from provided templates.
   *
   * @see EmailTemplates
   * @see EmailType
   * @see EmailWrapper
   */
  public class EmailGenerator {
      // status-related strings
      private static final String FEEDBACK_STATUS_SESSION_OPEN = "is still open for submissions";
      private static final String FEEDBACK_STATUS_SESSION_OPENING = "is now open";
      private static final String FEEDBACK_STATUS_SESSION_CLOSING = "is closing soon";
      private static final String FEEDBACK_STATUS_SESSION_CLOSED =
              "is now closed. You can still view your submission by going to the link sent earlier, "
              + "but you will not be able to edit existing responses or submit new responses";
  
      // feedback action strings
      private static final String FEEDBACK_ACTION_SUBMIT = "submit";
      private static final String FEEDBACK_ACTION_VIEW = "view";
  
      private static final Logger log = Logger.getLogger();
      private static final CoursesLogic coursesLogic = CoursesLogic.inst();
      private static final FeedbackSessionsLogic fsLogic = FeedbackSessionsLogic.inst();
      private static final InstructorsLogic instructorsLogic = InstructorsLogic.inst();
      private static final StudentsLogic studentsLogic = StudentsLogic.inst();
  
      /**
       * Generates the feedback session opening emails for the given {@code session}.
       */
      public List<EmailWrapper> generateFeedbackSessionOpeningEmails(FeedbackSessionAttributes session) {
  
          String template = EmailTemplates.USER_FEEDBACK_SESSION;
  
          CourseAttributes course = coursesLogic.getCourse(session.getCourseId());
          boolean isEmailNeeded = fsLogic.isFeedbackSessionForStudentsToAnswer(session);
          List<InstructorAttributes> instructors = isEmailNeeded
                                                   ? instructorsLogic.getInstructorsForCourse(session.getCourseId())
                                                   : new ArrayList<InstructorAttributes>();
          List<StudentAttributes> students = isEmailNeeded
                                             ? studentsLogic.getStudentsForCourse(session.getCourseId())
                                             : new ArrayList<StudentAttributes>();
  
          List<EmailWrapper> emails = generateFeedbackSessionEmailBases(course, session, students, instructors, template,
                                                                        EmailType.FEEDBACK_OPENING.getSubject());
          for (EmailWrapper email : emails) {
              email.setContent(email.getContent().replace("${status}", FEEDBACK_STATUS_SESSION_OPENING));
          }
          return emails;
      }
  
      /**
       * Generates the feedback session reminder emails for the given {@code session} for {@code students}
       * and {@code instructorsToRemind}. In addition, the emails will also be forwarded to {@code instructorsToNotify}.
       */
      public List<EmailWrapper> generateFeedbackSessionReminderEmails(
              FeedbackSessionAttributes session, List<StudentAttributes> students,
              List<InstructorAttributes> instructorsToRemind, List<InstructorAttributes> instructorsToNotify) {
  
          CourseAttributes course = coursesLogic.getCourse(session.getCourseId());
          String template = EmailTemplates.USER_FEEDBACK_SESSION.replace("${status}", FEEDBACK_STATUS_SESSION_OPEN);
          List<EmailWrapper> emails =
                  generateFeedbackSessionEmailBasesForInstructorReminders(course, session, instructorsToRemind, template,
                          EmailType.FEEDBACK_SESSION_REMINDER.getSubject());
          emails.addAll(generateFeedbackSessionEmailBases(course, session, students, instructorsToNotify, template,
                                                          EmailType.FEEDBACK_SESSION_REMINDER.getSubject()));
  
          return emails;
      }
  
      /**
       * Generates the email containing the summary of the feedback sessions
       * email for the given {@code courseId} for {@code student}.
       * @param courseId - ID of the course
       * @param student - attributes of student to send feedback session summary to
       */
      public EmailWrapper generateFeedbackSessionSummaryOfCourse(String courseId, StudentAttributes student) {
  
          CourseAttributes course = coursesLogic.getCourse(courseId);
  
          List<FeedbackSessionAttributes> sessions = new ArrayList<>();
          List<FeedbackSessionAttributes> fsInCourse = fsLogic.getFeedbackSessionsForCourse(courseId);
  
          for (FeedbackSessionAttributes fsa : fsInCourse) {
              if (!fsa.isPrivateSession() && (fsa.isSentOpenEmail() || fsa.isSentPublishedEmail())) {
                  sessions.add(fsa);
              }
          }
  
          StringBuffer linksFragmentValue = new StringBuffer(1000);
          String joinUrl = Config.getAppUrl(student.getRegistrationUrl()).toAbsoluteString();
  
          String joinFragmentValue = isYetToJoinCourse(student)
                                     ? Templates.populateTemplate(EmailTemplates.FRAGMENT_STUDENT_COURSE_JOIN,
                                             "${joinUrl}", joinUrl,
                                             "${courseName}", SanitizationHelper.sanitizeForHtml(course.getName()))
                                     : "";
  
          for (FeedbackSessionAttributes fsa : sessions) {
  
              String submitUrlHtml = "(Feedback session is not yet opened)";
              String reportUrlHtml = "(Feedback session is not yet published)";
  
              if (fsa.isOpened() || fsa.isClosed()) {
                  String submitUrl = Config.getAppUrl(Const.ActionURIs.STUDENT_FEEDBACK_SUBMISSION_EDIT_PAGE)
                          .withCourseId(course.getId())
                          .withSessionName(fsa.getFeedbackSessionName())
                          .withRegistrationKey(StringHelper.encrypt(student.key))
                          .withStudentEmail(student.email)
                          .toAbsoluteString();
                  submitUrlHtml = "<a href=\"" + submitUrl + "\">" + submitUrl + "</a>";
              }
  
              if (fsa.isPublished()) {
                  String reportUrl = Config.getAppUrl(Const.ActionURIs.STUDENT_FEEDBACK_RESULTS_PAGE)
                          .withCourseId(course.getId())
                          .withSessionName(fsa.getFeedbackSessionName())
                          .withRegistrationKey(StringHelper.encrypt(student.key))
                          .withStudentEmail(student.email)
                          .toAbsoluteString();
                  reportUrlHtml = "<a href=\"" + reportUrl + "\">" + reportUrl + "</a>";
              }
  
              linksFragmentValue.append(Templates.populateTemplate(
                      EmailTemplates.FRAGMENT_SINGLE_FEEDBACK_SESSION_LINKS,
                      "${feedbackSessionName}", fsa.getFeedbackSessionName(),
                      "${deadline}", TimeHelper.formatTime12H(fsa.getEndTime()) + (fsa.isClosed() ? " (Passed)" : ""),
                      "${submitUrl}", submitUrlHtml,
                      "${reportUrl}", reportUrlHtml));
          }
  
          String emailBody = Templates.populateTemplate(EmailTemplates.USER_FEEDBACK_SESSION_RESEND_ALL_LINKS,
                  "${userName}", SanitizationHelper.sanitizeForHtml(student.name),
                  "${userEmail}", student.email,
                  "${courseName}", SanitizationHelper.sanitizeForHtml(course.getName()),
                  "${courseId}", course.getId(),
                  "${joinFragment}", joinFragmentValue,
                  "${linksFragment}", linksFragmentValue.toString(),
                  "${coOwnersEmails}", generateCoOwnersEmailsLine(courseId),
                  "${supportEmail}", Config.SUPPORT_EMAIL);
  
          EmailWrapper email = getEmptyEmailAddressedToEmail(student.email);
          email.setSubject(String.format(EmailType.STUDENT_EMAIL_CHANGED.getSubject(), course.getName(), course.getId()));
          email.setContent(emailBody);
  
          return email;
      }
  
      /**
       * Generates the feedback submission confirmation email for the given {@code session} for {@code student}.
       */
      public EmailWrapper generateFeedbackSubmissionConfirmationEmailForStudent(
              FeedbackSessionAttributes session, StudentAttributes student, Calendar timestamp) {
  
          CourseAttributes course = coursesLogic.getCourse(session.getCourseId());
          String submitUrl = Config.getAppUrl(Const.ActionURIs.STUDENT_FEEDBACK_SUBMISSION_EDIT_PAGE)
                  .withCourseId(course.getId())
                  .withSessionName(session.getFeedbackSessionName())
                  .withRegistrationKey(StringHelper.encrypt(student.key))
                  .withStudentEmail(student.email)
                  .toAbsoluteString();
          return generateSubmissionConfirmationEmail(course, session, submitUrl, student.name, student.email, timestamp);
      }
  
      /**
       * Generates the feedback submission confirmation email for the given {@code session} for {@code instructor}.
       */
      public EmailWrapper generateFeedbackSubmissionConfirmationEmailForInstructor(
              FeedbackSessionAttributes session, InstructorAttributes instructor, Calendar timestamp) {
  
          CourseAttributes course = coursesLogic.getCourse(session.getCourseId());
          String submitUrl = Config.getAppUrl(Const.ActionURIs.INSTRUCTOR_FEEDBACK_SUBMISSION_EDIT_PAGE)
                  .withCourseId(course.getId())
                  .withSessionName(session.getFeedbackSessionName())
                  .toAbsoluteString();
  
          return generateSubmissionConfirmationEmail(course, session, submitUrl, instructor.name, instructor.email, timestamp);
      }
  
      private List<EmailWrapper> generateFeedbackSessionEmailBasesForInstructorReminders(
              CourseAttributes course, FeedbackSessionAttributes session, List<InstructorAttributes> instructors,
              String template, String subject) {
  
          List<EmailWrapper> emails = new ArrayList<>();
          String coOwnersLine = generateCoOwnersEmailsLine(course.getId());
          for (InstructorAttributes instructor : instructors) {
              emails.add(generateFeedbackSessionEmailBaseForInstructorReminders(course, session, instructor,
                      template, subject, coOwnersLine));
          }
          return emails;
      }
  
      private EmailWrapper generateSubmissionConfirmationEmail(
              CourseAttributes course, FeedbackSessionAttributes session, String submitUrl,
              String userName, String userEmail, Calendar timestamp) {
          Calendar time = TimeHelper.convertToUserTimeZone(timestamp, session.getTimeZone());
          String template = EmailTemplates.USER_FEEDBACK_SUBMISSION_CONFIRMATION;
          String subject = EmailType.FEEDBACK_SUBMISSION_CONFIRMATION.getSubject();
  
          String emailBody = Templates.populateTemplate(template,
                  "${userName}", SanitizationHelper.sanitizeForHtml(userName),
                  "${courseName}", SanitizationHelper.sanitizeForHtml(course.getName()),
                  "${courseId}", SanitizationHelper.sanitizeForHtml(course.getId()),
                  "${feedbackSessionName}", SanitizationHelper.sanitizeForHtml(session.getFeedbackSessionName()),
                  "${deadline}", SanitizationHelper.sanitizeForHtml(TimeHelper.formatTime12H(session.getEndTime())),
                  "${submitUrl}", submitUrl,
                  "${timeStamp}", SanitizationHelper.sanitizeForHtml(TimeHelper.formatTime12H(time.getTime())),
                  "${coOwnersEmails}", generateCoOwnersEmailsLine(course.getId()),
                  "${supportEmail}", Config.SUPPORT_EMAIL);
  
          EmailWrapper email = getEmptyEmailAddressedToEmail(userEmail);
          email.setSubject(String.format(subject, course.getName(), session.getFeedbackSessionName()));
          email.setContent(emailBody);
          return email;
  
      }
  
      private EmailWrapper generateFeedbackSessionEmailBaseForInstructorReminders(
              CourseAttributes course, FeedbackSessionAttributes session, InstructorAttributes instructor,
              String template, String subject, String coOwnersLine) {
  
          String submitUrl = Config.getAppUrl(Const.ActionURIs.INSTRUCTOR_FEEDBACK_SUBMISSION_EDIT_PAGE)
                                   .withCourseId(course.getId())
                                   .withSessionName(session.getFeedbackSessionName())
                                   .toAbsoluteString();
  
          String reportUrl = Config.getAppUrl(Const.ActionURIs.INSTRUCTOR_FEEDBACK_RESULTS_PAGE)
                                   .withCourseId(course.getId())
                                   .withSessionName(session.getFeedbackSessionName())
                                   .toAbsoluteString();
  
          String emailBody = Templates.populateTemplate(template,
                  "${userName}", SanitizationHelper.sanitizeForHtml(instructor.name),
                  "${courseName}", SanitizationHelper.sanitizeForHtml(course.getName()),
                  "${courseId}", SanitizationHelper.sanitizeForHtml(course.getId()),
                  "${feedbackSessionName}", SanitizationHelper.sanitizeForHtml(session.getFeedbackSessionName()),
                  "${deadline}", SanitizationHelper.sanitizeForHtml(TimeHelper.formatTime12H(session.getEndTime())),
                  "${instructorFragment}", "",
                  "${sessionInstructions}", session.getInstructionsString(),
                  "${submitUrl}", submitUrl,
                  "${reportUrl}", reportUrl,
                  "${coOwnersEmails}", coOwnersLine,
                  "${supportEmail}", Config.SUPPORT_EMAIL,
                  "${additionalInstructions}", "",
                  "${feedbackAction}", FEEDBACK_ACTION_SUBMIT);
  
          EmailWrapper email = getEmptyEmailAddressedToEmail(instructor.email);
          email.setSubject(String.format(subject, course.getName(), session.getFeedbackSessionName()));
          email.setContent(emailBody);
          return email;
      }
  
      /**
       * Generates the feedback session closing emails for the given {@code session}.
       */
      public List<EmailWrapper> generateFeedbackSessionClosingEmails(FeedbackSessionAttributes session) {
  
          List<StudentAttributes> students = new ArrayList<>();
          boolean isEmailNeeded = fsLogic.isFeedbackSessionForStudentsToAnswer(session);
  
          if (isEmailNeeded) {
              List<StudentAttributes> studentsForCourse = studentsLogic.getStudentsForCourse(session.getCourseId());
  
              for (StudentAttributes student : studentsForCourse) {
                  try {
                      if (!fsLogic.isFeedbackSessionFullyCompletedByStudent(session.getFeedbackSessionName(),
                              session.getCourseId(), student.email)) {
                          students.add(student);
                      }
                  } catch (EntityDoesNotExistException e) {
                      log.severe("Course " + session.getCourseId() + " does not exist or "
                                 + "session " + session.getFeedbackSessionName() + " does not exist");
                      // Course or session cannot be found for one student => it will be the case for all students
                      // Do not waste time looping through all students
                      break;
                  }
              }
          }
  
          String template = EmailTemplates.USER_FEEDBACK_SESSION_CLOSING
                  .replace("${status}", FEEDBACK_STATUS_SESSION_CLOSING);
          CourseAttributes course = coursesLogic.getCourse(session.getCourseId());
  