/**
   * Checks that the requesting user can view survey responses for some collection of users. There
   * may not actually be any responses to read or the responses may need to be made public first.
   * This only guarantees that, if the other users have any public responses that the requesting
   * user is allowed to view them. Therefore, this will pass as long as any of the following are
   * true: <br>
   * <br>
   * - If the user is a supervisor or an author.<br>
   * - If the user is an analyst and the campaign is shared.<br>
   * - If the user is the same as all of the requesting users.<br>
   * <br>
   * If you want to check if a user can read survey responses from every user in a campaign, don't
   * pass in any user usernames.
   *
   * @param campaignId The unique identifier for the campaign.
   * @param requesterUsername The requesting user's username.
   * @param userUsernames The array of usernames of specific users to check if the requesting user
   *     has permission to read their information.
   * @throws ServiceException Thrown if none of the rules are true or there is an error.
   */
  public void requesterCanViewUsersSurveyResponses(
      final String campaignId, final String requesterUsername, final String... userUsernames)
      throws ServiceException {
    try {
      // If the requester is asking about other users.
      if (userUsernames.length != 0) {
        // If the requester is the same as all of the users in question.
        boolean otherUsers = false;
        for (String username : userUsernames) {
          if (!requesterUsername.equals(username)) {
            otherUsers = true;
          }
        }
        if (!otherUsers) {
          return;
        }
      }

      List<Campaign.Role> requesterRoles =
          userCampaignQueries.getUserCampaignRoles(requesterUsername, campaignId);

      // If the requester's role list contains supervisor, return.
      if (requesterRoles.contains(Campaign.Role.SUPERVISOR)) {
        return;
      }

      // If the requester's role list contains author, return.
      if (requesterRoles.contains(Campaign.Role.AUTHOR)) {
        return;
      }

      // If the requester's role list contains analyst,
      if (requesterRoles.contains(Campaign.Role.ANALYST)) {
        Campaign.PrivacyState privacyState = campaignQueries.getCampaignPrivacyState(campaignId);

        if ((privacyState != null) && (Campaign.PrivacyState.SHARED.equals(privacyState))) {
          return;
        }
      }

      throw new ServiceException(
          ErrorCode.CAMPAIGN_INSUFFICIENT_PERMISSIONS,
          "The user does not have sufficient permissions to read information about other users.");
    } catch (DataAccessException e) {
      throw new ServiceException(e);
    }
  }
  /**
   * Verifies that a user is allowed to update a campaign's XML.
   *
   * @param username The username of the user.
   * @param campaignId The campaign's unique identifier.
   * @param id The ID from the new XML.
   * @param name The name from the new XML.
   * @throws ServiceException Thrown if the user isn't allowed to modify the campaign, if the user
   *     is allowed to modify the campaign but responses exist, or if there is an error.
   */
  public void verifyUserCanUpdateCampaignXml(
      final String username, final String campaignId, final String id, final String name)
      throws ServiceException {

    try {
      // Get the user's roles for this campaign.
      List<Campaign.Role> roles = userCampaignQueries.getUserCampaignRoles(username, campaignId);

      // If the user isn't a supervisor or an author, then they aren't
      // allowed to update it.
      if (!(roles.contains(Campaign.Role.SUPERVISOR) || roles.contains(Campaign.Role.AUTHOR))) {

        throw new ServiceException(
            ErrorCode.CAMPAIGN_INSUFFICIENT_PERMISSIONS,
            "The user is not allowed to modify the campaign's XML.");
      }

      // If the campaign already has survey responses, then it cannot be
      // updated by anyone.
      if (campaignSurveyResponseQueries.getNumberOfSurveyResponsesForCampaign(campaignId) != 0) {

        throw new ServiceException(
            ErrorCode.CAMPAIGN_INSUFFICIENT_PERMISSIONS,
            "Survey responses exist; therefore the XML can no longer be modified.");
      }

      // Check to ensure that the ID of the campaign hasn't changed.
      if (!campaignId.equals(id)) {
        throw new ServiceException(
            ErrorCode.CAMPAIGN_XML_HEADER_CHANGED,
            "The campaign's ID in the new XML must be the same as the original XML.");
      }

      // Check to ensure that the name of the campaign hasn't changed.
      if (!campaignQueries.getName(campaignId).equals(name)) {
        throw new ServiceException(
            ErrorCode.CAMPAIGN_XML_HEADER_CHANGED,
            "The campaign's name in the new XML must be the same as the original XML.");
      }
    } catch (DataAccessException e) {
      throw new ServiceException(e);
    }
  }
  /**
   * Retrieves the information about a campaign.
   *
   * @param campaignId The campaign's unqiue identifier.
   * @param withClasses Whether or not to populate the campaign with its list of classes.
   * @param withUsers Whether or not to populate the campaign with its list of users and their
   *     respective roles.
   * @return The campaign object with the specified information.
   * @throws ServiceException Thrown if there is an error.
   */
  public Campaign getCampaignInformation(
      final String campaignId, final boolean withClasses, final boolean withUsers)
      throws ServiceException {

    try {
      Campaign result = campaignQueries.findCampaignConfiguration(campaignId);

      if (withClasses) {
        try {
          result.addClasses(campaignClassQueries.getClassesAssociatedWithCampaign(campaignId));
        } catch (DomainException e) {
          throw new ServiceException("There was a problem adding a class.", e);
        }
      }

      if (withUsers) {
        List<String> campaignUsernames = userCampaignQueries.getUsersInCampaign(campaignId);

        for (String campaignUsername : campaignUsernames) {
          List<Campaign.Role> userRoles =
              userCampaignQueries.getUserCampaignRoles(campaignUsername, campaignId);

          for (Campaign.Role userRole : userRoles) {
            try {
              result.addUser(campaignUsername, userRole);
            } catch (DomainException e) {
              throw new ServiceException("There was a problem adding a user.", e);
            }
          }
        }
      }

      return result;
    } catch (DataAccessException e) {
      throw new ServiceException(e);
    }
  }
  /**
   * Gathers the requested information about a campaign. This will be at least its name, description
   * (possibly null), running state, privacy state, creation timestamp, and all of the requesting
   * user's roles in the campaign.<br>
   * <br>
   * The extras include the campaign's XML, all of the users associated with the campaign and their
   * roles, and all of the classes associated with the campaign.
   *
   * @param username The username of the user whose roles in the campaign are desired.
   * @param campaignIds The IDs for the campaigns whose information is desired.
   * @param withExtras A flag to indicate if the extra information should be included in each
   *     Campaign object.
   * @return A map of campaigns and their information to the list of roles for this user in the
   *     campaign.
   * @throws ServiceException Thrown if there is an error.
   */
  public Map<Campaign, List<Campaign.Role>> getCampaignAndUserRolesForCampaigns(
      final String username, final Collection<String> campaignIds, final boolean withExtras)
      throws ServiceException {

    try {
      Map<Campaign, List<Campaign.Role>> result = new HashMap<Campaign, List<Campaign.Role>>();

      for (String campaignId : campaignIds) {
        // Create the Campaign object with the campaign's ID.
        Campaign campaign = campaignQueries.getCampaignInformation(campaignId);

        // Get the user's roles.
        List<Campaign.Role> roles = userCampaignQueries.getUserCampaignRoles(username, campaignId);

        // If we are supposed to get the extra information as well.
        if (withExtras) {

          // Add the classes that are associated with the campaign.
          try {
            campaign.addClasses(campaignClassQueries.getClassesAssociatedWithCampaign(campaignId));
          } catch (DomainException e) {
            throw new ServiceException("There was a problem adding a class.", e);
          }

          // Add the users and their roles to the campaign.
          campaign.addUsers(userCampaignQueries.getUsersAndRolesForCampaign(campaignId));
        }

        // Add the user's roles.
        result.put(campaign, roles);
      }

      return result;
    } catch (DataAccessException e) {
      throw new ServiceException(e);
    }
  }
  /**
   * Gathers the information about the classes that match the criteria based on the user's
   * permissions. If the requesting user is an admin, they will see all campaigns; otherwise, they
   * will only see the campaigns to which they belong.
   *
   * @param username The requesting user's username. This parameter is required.
   * @param campaignIds A list of campaign unique identifiers. This is optional and may be null. It
   *     limits the results to only those campaigns to which the user belongs.
   * @param classIds A list of class unique identifiers. This is optional and may be null. It limits
   *     the results to only those campaigns that are associated with any class in this list.
   * @param nameTokens A collection of token strings which limit the results to only those campaigns
   *     whose name contains at least one of the tokens.
   * @param descriptionTokens A collection of token strings which limit the results to only those
   *     campaigns that have a description and whose description contains at least one of the
   *     tokens.
   * @param startDate A date that limits the results to only those campaigns that were created on or
   *     after this date.
   * @param endDate A date that limits the results to only those campaigns that were created on or
   *     before this date.
   * @param privacyState A campaign privacy state the limits the results to only those campaigns
   *     that have this privacy state.
   * @param runningState A campaign running state that limits the results to only those campaigns
   *     that have this running state.
   * @param role A campaign role which limits the results to only those campaigns where the
   *     requesting user has this role in the campaign.
   * @param withClasses Whether or not to aggregate all of the classes associated with this
   *     campaign.
   * @param withUsers Whether or not to aggregate all of the users and their respective roles for
   *     this campaign.
   * @return A map of Campaign objects to the requesting user's respective roles.
   * @throws ServiceException There was an error.
   */
  public Map<Campaign, Collection<Campaign.Role>> getCampaignInformation(
      final String username,
      final Collection<String> campaignIds,
      final Collection<String> classIds,
      final Collection<String> nameTokens,
      final Collection<String> descriptionTokens,
      final DateTime startDate,
      final DateTime endDate,
      final Campaign.PrivacyState privacyState,
      final Campaign.RunningState runningState,
      final Campaign.Role role,
      final boolean withClasses,
      final boolean withUsers)
      throws ServiceException {

    try {
      QueryResultsList<Campaign> queryResult =
          campaignQueries.getCampaignInformation(
              username,
              campaignIds,
              classIds,
              nameTokens,
              descriptionTokens,
              startDate,
              endDate,
              privacyState,
              runningState,
              role);
      List<Campaign> campaignResults = queryResult.getResults();

      Map<Campaign, Collection<Campaign.Role>> result =
          new HashMap<Campaign, Collection<Campaign.Role>>(campaignResults.size());

      for (Campaign campaign : campaignResults) {
        result.put(campaign, userCampaignQueries.getUserCampaignRoles(username, campaign.getId()));

        if (withClasses) {
          try {
            campaign.addClasses(
                campaignClassQueries.getClassesAssociatedWithCampaign(campaign.getId()));
          } catch (DomainException e) {
            throw new ServiceException("There was a problem adding the classes.", e);
          }
        }

        if (withUsers) {
          // Add the users and their roles to the campaign.
          campaign.addUsers(userCampaignQueries.getUsersAndRolesForCampaign(campaign.getId()));
        }

        // Get and apply the masks for this campaign.
        campaign.addMasks(
            userCampaignQueries.getCampaignMasks(
                null, null, null, null, username, campaign.getId()));
      }

      return result;
    } catch (DataAccessException e) {
      throw new ServiceException(e);
    }
  }
  /**
   * Generates a List of unique identifiers for campaigns based on the parameters. The List is based
   * on the 'campaignIds' parameter unless it is null in which case the List is based on all
   * campaigns visible to the user. All parameters except 'request' and 'username' are optional and
   * each will filter the resulting List of campaign identifiers.<br>
   * <br>
   * <br>
   * For example, if 'campaignIds' was null as were 'endDate' and 'privacyState', then what would be
   * returned would be the intersection of the following lists:<br>
   * - All of the campaigns to which the user was associated (because 'campaignIds' was null).<br>
   * - All of the campaigns that are associated with any of the classes whose unique identifier was
   * in the 'classIds' list.<br>
   * - All of the campaigns whose creation timestamp was equal to or after 'startDate'<br>
   * - All of the campaigns whose running state equaled 'runningState'.<br>
   * - All of the campaigns to which the user had the campaign role 'role'. <br>
   * <br>
   * Therefore, if a campaign was associated with a user only through a single class, but that class
   * wasn't in the 'classIds' list, then that campaign ID would not be returned even if all of the
   * other parameters matched.
   *
   * @param username The username of the user.
   * @param campaignIds An optional Collection of campaign identifiers from which to base the List.
   *     If this is empty, the resulting List will be empty. If this is null, the base List will be
   *     all campaigns to which the user is associated.
   * @param classIds A Collection of unique identifiers for classes where the resulting list will
   *     only contain campaign identifiers for campaigns that are associated with any of these
   *     classes.
   * @param startDate A Calendar where only campaigns whose creation timestamp is equal to or after
   *     this date.
   * @param endDate A Calendar where only campaigns whose creation timestamp is equal to or before
   *     this date.
   * @param privacyState A campaign privacy state that trims the resulting list of campaigns to only
   *     those that have this privacy state.
   * @param runningState A campaign running state that trims the resulting list of campaigns to only
   *     those that have this running state.
   * @param role A campaign role that trims the resulting list of campaigns to only those where the
   *     user has that role in the campaign.
   * @return A Set of campaign unique identifiers based on the 'campaignIds' parameter and trimmed
   *     by the rest of the parameters.
   * @throws ServiceException Thrown if there is an error.
   */
  public Set<String> getCampaignsForUser(
      final String username,
      final Collection<String> campaignIds,
      final Collection<String> classIds,
      final DateTime startDate,
      final DateTime endDate,
      final Campaign.PrivacyState privacyState,
      final Campaign.RunningState runningState,
      final Campaign.Role role)
      throws ServiceException {

    try {
      Set<String> desiredCampaignIds = new HashSet<String>();

      if (campaignIds == null) {
        // Initializes the list with all of the campaign IDs for the
        // requesting user.
        desiredCampaignIds.addAll(
            userCampaignQueries.getCampaignIdsAndNameForUser(username).keySet());
      } else {
        // Initializes the list with the campaign IDs in the query.
        desiredCampaignIds.addAll(campaignIds);
      }

      if (desiredCampaignIds.size() == 0) {
        return Collections.emptySet();
      }

      if (classIds != null) {
        // Get all of the campaigns associated with all of the classes in
        // the list.
        for (String classId : classIds) {
          desiredCampaignIds.retainAll(
              campaignClassQueries.getCampaignsAssociatedWithClass(classId));
        }

        if (desiredCampaignIds.size() == 0) {
          return Collections.emptySet();
        }
      }

      if (startDate != null) {
        // Get all of the campaigns whose creation timestamp is greater
        // than or equal to the start date.
        desiredCampaignIds.retainAll(campaignQueries.getCampaignsOnOrAfterDate(startDate));

        if (desiredCampaignIds.size() == 0) {
          return Collections.emptySet();
        }
      }

      if (endDate != null) {
        // Get all of the campaigns whose creation timestamp is less than
        // or equal to the end date.
        desiredCampaignIds.retainAll(campaignQueries.getCampaignsOnOrBeforeDate(endDate));

        if (desiredCampaignIds.size() == 0) {
          return Collections.emptySet();
        }
      }

      if (privacyState != null) {
        // Get all of the campaigns with a privacy state of 'privacyState'.
        desiredCampaignIds.retainAll(campaignQueries.getCampaignsWithPrivacyState(privacyState));

        if (desiredCampaignIds.size() == 0) {
          return Collections.emptySet();
        }
      }

      if (runningState != null) {
        // Get all of the campaigns with a running state of 'runningState'.
        desiredCampaignIds.retainAll(campaignQueries.getCampaignsWithRunningState(runningState));

        if (desiredCampaignIds.size() == 0) {
          return Collections.emptySet();
        }
      }

      if (role != null) {
        // Get all of the campaigns where the user's role is 'role'.
        desiredCampaignIds.retainAll(
            userCampaignQueries.getCampaignIdsForUserWithRole(username, role));

        if (desiredCampaignIds.size() == 0) {
          return Collections.emptySet();
        }
      }

      return desiredCampaignIds;
    } catch (DataAccessException e) {
      throw new ServiceException(e);
    }
  }