/**
   * Gets the latest comments with the specified fetch size.
   *
   * <p>The returned comments content is plain text.
   *
   * @param fetchSize the specified fetch size
   * @return the latest comments, returns an empty list if not found
   * @throws ServiceException service exception
   */
  public List<JSONObject> getLatestComments(final int fetchSize) throws ServiceException {
    final Query query =
        new Query()
            .addSort(Comment.COMMENT_CREATE_TIME, SortDirection.DESCENDING)
            .setCurrentPageNum(1)
            .setPageSize(fetchSize)
            .setPageCount(1);
    try {
      final JSONObject result = commentRepository.get(query);
      final List<JSONObject> ret =
          CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));

      for (final JSONObject comment : ret) {
        comment.put(Comment.COMMENT_CREATE_TIME, comment.optLong(Comment.COMMENT_CREATE_TIME));
        final String articleId = comment.optString(Comment.COMMENT_ON_ARTICLE_ID);
        final JSONObject article = articleRepository.get(articleId);
        comment.put(
            Comment.COMMENT_T_ARTICLE_TITLE,
            Emotions.clear(article.optString(Article.ARTICLE_TITLE)));
        comment.put(
            Comment.COMMENT_T_ARTICLE_PERMALINK, article.optString(Article.ARTICLE_PERMALINK));

        final String commenterId = comment.optString(Comment.COMMENT_AUTHOR_ID);
        final JSONObject commenter = userRepository.get(commenterId);

        if (UserExt.USER_STATUS_C_INVALID == commenter.optInt(UserExt.USER_STATUS)
            || Comment.COMMENT_STATUS_C_INVALID == comment.optInt(Comment.COMMENT_STATUS)) {
          comment.put(Comment.COMMENT_CONTENT, langPropsService.get("commentContentBlockLabel"));
        }

        if (Article.ARTICLE_TYPE_C_DISCUSSION == article.optInt(Article.ARTICLE_TYPE)) {
          comment.put(Comment.COMMENT_CONTENT, "....");
        }

        String content = comment.optString(Comment.COMMENT_CONTENT);
        content = Emotions.clear(content);
        content = Jsoup.clean(content, Whitelist.none());
        if (StringUtils.isBlank(content)) {
          comment.put(Comment.COMMENT_CONTENT, "....");
        } else {
          comment.put(Comment.COMMENT_CONTENT, content);
        }

        final String commenterEmail = comment.optString(Comment.COMMENT_AUTHOR_EMAIL);
        final String avatarURL = avatarQueryService.getAvatarURL(commenterEmail);
        commenter.put(UserExt.USER_AVATAR_URL, avatarURL);

        comment.put(Comment.COMMENT_T_COMMENTER, commenter);
      }

      return ret;
    } catch (final RepositoryException e) {
      LOGGER.log(Level.ERROR, "Gets user comments failed", e);
      throw new ServiceException(e);
    }
  }
  /**
   * Gets follower users of the specified following user.
   *
   * @param followingUserId the specified following user id
   * @param currentPageNum the specified page number
   * @param pageSize the specified page size
   * @return result json object, for example,
   *     <pre>
   * {
   *     "paginationRecordCount": int,
   *     "rslts": java.util.List[{
   *         User
   *     }, ....]
   * }
   * </pre>
   *
   * @throws ServiceException service exception
   */
  public JSONObject getFollowerUsers(
      final String followingUserId, final int currentPageNum, final int pageSize)
      throws ServiceException {
    final JSONObject ret = new JSONObject();
    final List<JSONObject> records = new ArrayList<JSONObject>();

    ret.put(Keys.RESULTS, (Object) records);
    ret.put(Pagination.PAGINATION_RECORD_COUNT, 0);

    try {
      final JSONObject result =
          getFollowers(followingUserId, Follow.FOLLOWING_TYPE_C_USER, currentPageNum, pageSize);

      @SuppressWarnings("unchecked")
      final List<JSONObject> followers = (List<JSONObject>) result.opt(Keys.RESULTS);

      for (final JSONObject follow : followers) {
        final String followerId = follow.optString(Follow.FOLLOWER_ID);
        final JSONObject user = userRepository.get(followerId);

        if (null == user) {
          LOGGER.log(Level.WARN, "Not found user[id=" + followerId + ']');

          continue;
        }

        avatarQueryService.fillUserAvatarURL(user);

        records.add(user);
      }

      ret.put(
          Pagination.PAGINATION_RECORD_COUNT, result.optInt(Pagination.PAGINATION_RECORD_COUNT));
    } catch (final RepositoryException e) {
      LOGGER.log(
          Level.ERROR,
          "Gets follower users of following user[id=" + followingUserId + "] failed",
          e);
    }

    return ret;
  }
  /**
   * Organizes the specified comment.
   *
   * <ul>
   *   <li>converts comment create time (long) to date type
   *   <li>generates comment author thumbnail URL
   *   <li>generates comment author URL
   *   <li>generates comment author name
   *   <li>generates comment author real name
   *   <li>generates &#64;username home URL
   *   <li>markdowns comment content
   *   <li>block comment if need
   *   <li>generates emotion images
   *   <li>generates time ago text
   * </ul>
   *
   * @param comment the specified comment
   * @throws RepositoryException repository exception
   */
  private void organizeComment(final JSONObject comment) throws RepositoryException {
    comment.put(Common.TIME_AGO, Times.getTimeAgo(comment.optLong(Comment.COMMENT_CREATE_TIME)));
    comment.put(
        Comment.COMMENT_CREATE_TIME, new Date(comment.optLong(Comment.COMMENT_CREATE_TIME)));

    final String authorId = comment.optString(Comment.COMMENT_AUTHOR_ID);
    JSONObject author = userCache.getUser(authorId);
    if (null == author) {
      author = userRepository.get(authorId);
    }

    final String userEmail = author.optString(User.USER_EMAIL);
    final String thumbnailURL = avatarQueryService.getAvatarURL(userEmail);
    comment.put(Comment.COMMENT_T_AUTHOR_THUMBNAIL_URL, thumbnailURL);

    comment.put(Comment.COMMENT_T_COMMENTER, author);
    comment.put(Comment.COMMENT_T_AUTHOR_NAME, author.optString(User.USER_NAME));
    comment.put(Comment.COMMENT_T_AUTHOR_REAL_NAME, author.optString(UserExt.USER_REAL_NAME));
    comment.put(Comment.COMMENT_T_AUTHOR_URL, author.optString(User.USER_URL));

    processCommentContent(comment);
  }
  /**
   * Gets the user comments with the specified user id, page number and page size.
   *
   * @param userId the specified user id
   * @param currentPageNum the specified page number
   * @param pageSize the specified page size
   * @param viewer the specified viewer, may be {@code null}
   * @return user comments, return an empty list if not found
   * @throws ServiceException service exception
   */
  public List<JSONObject> getUserComments(
      final String userId, final int currentPageNum, final int pageSize, final JSONObject viewer)
      throws ServiceException {
    final Query query =
        new Query()
            .addSort(Comment.COMMENT_CREATE_TIME, SortDirection.DESCENDING)
            .setCurrentPageNum(currentPageNum)
            .setPageSize(pageSize)
            .setFilter(new PropertyFilter(Comment.COMMENT_AUTHOR_ID, FilterOperator.EQUAL, userId));
    try {
      final JSONObject result = commentRepository.get(query);
      final List<JSONObject> ret =
          CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));

      for (final JSONObject comment : ret) {
        comment.put(
            Comment.COMMENT_CREATE_TIME, new Date(comment.optLong(Comment.COMMENT_CREATE_TIME)));

        final String articleId = comment.optString(Comment.COMMENT_ON_ARTICLE_ID);
        final JSONObject article = articleRepository.get(articleId);

        comment.put(
            Comment.COMMENT_T_ARTICLE_TITLE,
            Article.ARTICLE_STATUS_C_INVALID == article.optInt(Article.ARTICLE_STATUS)
                ? langPropsService.get("articleTitleBlockLabel")
                : Emotions.convert(article.optString(Article.ARTICLE_TITLE)));
        comment.put(Comment.COMMENT_T_ARTICLE_TYPE, article.optInt(Article.ARTICLE_TYPE));
        comment.put(
            Comment.COMMENT_T_ARTICLE_PERMALINK, article.optString(Article.ARTICLE_PERMALINK));

        final JSONObject commenter = userRepository.get(userId);
        comment.put(Comment.COMMENT_T_COMMENTER, commenter);

        final String articleAuthorId = article.optString(Article.ARTICLE_AUTHOR_ID);
        final JSONObject articleAuthor = userRepository.get(articleAuthorId);
        final String articleAuthorName = articleAuthor.optString(User.USER_NAME);
        final String articleAuthorURL = "/member/" + articleAuthor.optString(User.USER_NAME);
        comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_NAME, articleAuthorName);
        comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_URL, articleAuthorURL);
        final String articleAuthorEmail = articleAuthor.optString(User.USER_EMAIL);
        final String articleAuthorThumbnailURL =
            avatarQueryService.getAvatarURL(articleAuthorEmail);
        comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_THUMBNAIL_URL, articleAuthorThumbnailURL);

        if (Article.ARTICLE_TYPE_C_DISCUSSION == article.optInt(Article.ARTICLE_TYPE)) {
          final String msgContent =
              langPropsService
                  .get("articleDiscussionLabel")
                  .replace(
                      "{user}",
                      "<a href='"
                          + Latkes.getServePath()
                          + "/member/"
                          + articleAuthorName
                          + "'>"
                          + articleAuthorName
                          + "</a>");

          if (null == viewer) {
            comment.put(Comment.COMMENT_CONTENT, msgContent);
          } else {
            final String commenterName = commenter.optString(User.USER_NAME);
            final String viewerUserName = viewer.optString(User.USER_NAME);
            final String viewerRole = viewer.optString(User.USER_ROLE);

            if (!commenterName.equals(viewerUserName) && !Role.ADMIN_ROLE.equals(viewerRole)) {
              final String articleContent = article.optString(Article.ARTICLE_CONTENT);
              final Set<String> userNames = userQueryService.getUserNames(articleContent);

              boolean invited = false;
              for (final String userName : userNames) {
                if (userName.equals(viewerUserName)) {
                  invited = true;

                  break;
                }
              }

              if (!invited) {
                comment.put(Comment.COMMENT_CONTENT, msgContent);
              }
            }
          }
        }

        processCommentContent(comment);
      }

      return ret;
    } catch (final RepositoryException e) {
      LOGGER.log(Level.ERROR, "Gets user comments failed", e);
      throw new ServiceException(e);
    }
  }