content: /*
   * Symphony - A modern community (forum/BBS/SNS/blog) platform written in Java.
   * Copyright (C) 2012-present, b3log.org
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
   */
  package org.b3log.symphony.processor;
  
  import org.apache.commons.lang3.StringUtils;
  import org.b3log.latke.Keys;
  import org.b3log.latke.Latkes;
  import org.b3log.latke.http.Dispatcher;
  import org.b3log.latke.http.Request;
  import org.b3log.latke.http.RequestContext;
  import org.b3log.latke.http.renderer.AbstractFreeMarkerRenderer;
  import org.b3log.latke.ioc.BeanManager;
  import org.b3log.latke.ioc.Inject;
  import org.b3log.latke.ioc.Singleton;
  import org.b3log.latke.model.Pagination;
  import org.b3log.latke.model.User;
  import org.b3log.latke.service.LangPropsService;
  import org.b3log.latke.util.Paginator;
  import org.b3log.symphony.model.*;
  import org.b3log.symphony.processor.middleware.AnonymousViewCheckMidware;
  import org.b3log.symphony.processor.middleware.CSRFMidware;
  import org.b3log.symphony.processor.middleware.UserCheckMidware;
  import org.b3log.symphony.service.*;
  import org.b3log.symphony.util.*;
  import org.json.JSONObject;
  
  import java.util.*;
  
  /**
   * User processor.
   * <ul>
   * <li>User articles (/member/{userName}), GET</li>
   * <li>User anonymous articles (/member/{userName}/anonymous</li>
   * <li>User comments (/member/{userName}/comments), GET</li>
   * <li>User anonymous comments (/member/{userName}/comments/anonymous</li>
   * <li>User following users (/member/{userName}/following/users), GET</li>
   * <li>User following tags (/member/{userName}/following/tags), GET</li>
   * <li>User following articles (/member/{userName}/following/articles), GET</li>
   * <li>User followers (/member/{userName}/followers), GET</li>
   * <li>User points (/member/{userName}/points), GET</li>
   * <li>User breezemoons (/member/{userName}/breezemoons), GET</li>
   * <li>List usernames (/users/names), GET</li>
   * <li>List frequent emotions (/users/emotions), GET</li>
   * </ul>
   *
   * @author <a href="http://88250.b3log.org">Liang Ding</a>
   * @author <a href="https://ld246.com/member/ZephyrJung">Zephyr</a>
   * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
   * @version 2.0.0.0, Feb 11, 2020
   * @since 0.2.0
   */
  @Singleton
  public class UserProcessor {
  
      /**
       * User management service.
       */
      @Inject
      private UserMgmtService userMgmtService;
  
      /**
       * Article management service.
       */
      @Inject
      private ArticleQueryService articleQueryService;
  
      /**
       * User query service.
       */
      @Inject
      private UserQueryService userQueryService;
  
      /**
       * Comment query service.
       */
      @Inject
      private CommentQueryService commentQueryService;
  
      /**
       * Language service.
       */
      @Inject
      private LangPropsService langPropsService;
  
      /**
       * Follow query service.
       */
      @Inject
      private FollowQueryService followQueryService;
  
      /**
       * Emotion query service.
       */
      @Inject
      private EmotionQueryService emotionQueryService;
  
      /**
       * Emotion management service.
       */
      @Inject
      private EmotionMgmtService emotionMgmtService;
  
      /**
       * Data model service.
       */
      @Inject
      private DataModelService dataModelService;
  
      /**
       * Avatar query service.
       */
      @Inject
      private AvatarQueryService avatarQueryService;
  
      /**
       * Pointtransfer query service.
       */
      @Inject
      private PointtransferQueryService pointtransferQueryService;
  
      /**
       * Pointtransfer management service.
       */
      @Inject
      private PointtransferMgmtService pointtransferMgmtService;
  
      /**
       * Notification management service.
       */
      @Inject
      private NotificationMgmtService notificationMgmtService;
  
      /**
       * Option query service.
       */
      @Inject
      private OptionQueryService optionQueryService;
  
      /**
       * Role query service.
       */
      @Inject
      private RoleQueryService roleQueryService;
  
      /**
       * Breezemoon query service.
       */
      @Inject
      private BreezemoonQueryService breezemoonQueryService;
  
      /**
       * Register request handlers.
       */
      public static void register() {
          final BeanManager beanManager = BeanManager.getInstance();
          final AnonymousViewCheckMidware anonymousViewCheckMidware = beanManager.getReference(AnonymousViewCheckMidware.class);
          final CSRFMidware csrfMidware = beanManager.getReference(CSRFMidware.class);
          final UserCheckMidware userCheckMidware = beanManager.getReference(UserCheckMidware.class);
  
          final UserProcessor userProcessor = beanManager.getReference(UserProcessor.class);
          Dispatcher.get("/member/{userName}", userProcessor::showHome, anonymousViewCheckMidware::handle, userCheckMidware::handle);
          Dispatcher.group().middlewares(anonymousViewCheckMidware::handle, userCheckMidware::handle, csrfMidware::fill).router().get().uris(new String[]{"/member/{userName}/breezemoons", "/member/{userName}/breezemoons/{breezemoonId}"}).handler(userProcessor::showHomeBreezemoons);
          Dispatcher.get("/member/{userName}/comments/anonymous", userProcessor::showHomeAnonymousComments, userCheckMidware::handle);
          Dispatcher.get("/member/{userName}/articles/anonymous", userProcessor::showAnonymousArticles, userCheckMidware::handle);
          Dispatcher.get("/member/{userName}/comments", userProcessor::showHomeComments, anonymousViewCheckMidware::handle, userCheckMidware::handle);
          Dispatcher.get("/member/{userName}/following/users", userProcessor::showHomeFollowingUsers, anonymousViewCheckMidware::handle, userCheckMidware::handle);
          Dispatcher.get("/member/{userName}/following/tags", userProcessor::showHomeFollowingTags, anonymousViewCheckMidware::handle, userCheckMidware::handle);
          Dispatcher.get("/member/{userName}/following/articles", userProcessor::showHomeFollowingArticles, anonymousViewCheckMidware::handle, userCheckMidware::handle);
          Dispatcher.get("/member/{userName}/watching/articles", userProcessor::showHomeWatchingArticles, anonymousViewCheckMidware::handle, userCheckMidware::handle);
          Dispatcher.get("/member/{userName}/followers", userProcessor::showHomeFollowers, anonymousViewCheckMidware::handle, userCheckMidware::handle);
          Dispatcher.get("/member/{userName}/points", userProcessor::showHomePoints, anonymousViewCheckMidware::handle, userCheckMidware::handle);
          Dispatcher.post("/users/names", userProcessor::listNames);
          Dispatcher.get("/users/emotions", userProcessor::getFrequentEmotions);
      }
  
      /**
       * Shows user home breezemoons page.
       *
       * @param context the specified context
       */
      public void showHomeBreezemoons(final RequestContext context) {
          final String breezemoonId = context.pathVar("breezemoonId");
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/breezemoons.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          JSONObject currentUser;
          String currentUserId = null;
          if (isLoggedIn) {
              currentUser = Sessions.getUser();
              currentUserId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(currentUserId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
          }
  
          final JSONObject result = breezemoonQueryService.getBreezemoons(currentUserId, followingId, pageNum, pageSize, windowSize);
          List<JSONObject> bms = (List<JSONObject>) result.opt(Breezemoon.BREEZEMOONS);
          dataModel.put(Common.USER_HOME_BREEZEMOONS, bms);
  
          final JSONObject pagination = result.optJSONObject(Pagination.PAGINATION);
          final int recordCount = pagination.optInt(Pagination.PAGINATION_RECORD_COUNT);
          final int pageCount = (int) Math.ceil(recordCount / (double) pageSize);
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, recordCount);
  
          dataModel.put(Common.TYPE, Breezemoon.BREEZEMOONS);
  
          if (StringUtils.isNotBlank(breezemoonId)) {
              dataModel.put(Common.IS_SINGLE_BREEZEMOON_URL, true);
              final JSONObject breezemoon = breezemoonQueryService.getBreezemoon(breezemoonId);
              if (null == breezemoon) {
                  context.sendError(404);
                  return;
              }
  
              bms = Collections.singletonList(breezemoon);
              breezemoonQueryService.organizeBreezemoons("admin", bms);
              dataModel.put(Common.USER_HOME_BREEZEMOONS, bms);
          } else {
              dataModel.put(Common.IS_SINGLE_BREEZEMOON_URL, false);
          }
      }
  
      /**
       * Shows user home anonymous comments page.
       *
       * @param context the specified context
       */
      public void showHomeAnonymousComments(final RequestContext context) {
          final Request request = context.getRequest();
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/comments.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          JSONObject currentUser = null;
          if (isLoggedIn) {
              currentUser = Sessions.getUser();
          }
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          if (null == currentUser || (!currentUser.optString(Keys.OBJECT_ID).equals(user.optString(Keys.OBJECT_ID)))
                  && !Role.ROLE_ID_C_ADMIN.equals(currentUser.optString(User.USER_ROLE))) {
              context.sendError(404);
              return;
          }
  
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          if (isLoggedIn) {
              currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
          }
  
          final List<JSONObject> userComments = commentQueryService.getUserComments(user.optString(Keys.OBJECT_ID), Comment.COMMENT_ANONYMOUS_C_ANONYMOUS, pageNum, pageSize, currentUser);
          dataModel.put(Common.USER_HOME_COMMENTS, userComments);
  
          int recordCount = 0;
          int pageCount = 0;
          if (!userComments.isEmpty()) {
              final JSONObject first = userComments.get(0);
              pageCount = first.optInt(Pagination.PAGINATION_PAGE_COUNT);
              recordCount = first.optInt(Pagination.PAGINATION_RECORD_COUNT);
          }
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, recordCount);
  
          dataModel.put(Common.TYPE, "commentsAnonymous");
      }
  
      /**
       * Shows user home anonymous articles page.
       *
       * @param context the specified context
       */
      public void showAnonymousArticles(final RequestContext context) {
          final String userName = context.pathVar("userName");
          final Request request = context.getRequest();
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/home.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          JSONObject currentUser = null;
          if (isLoggedIn) {
              currentUser = Sessions.getUser();
          }
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          if (null == currentUser || (!currentUser.optString(Keys.OBJECT_ID).equals(user.optString(Keys.OBJECT_ID)))
                  && !Role.ROLE_ID_C_ADMIN.equals(currentUser.optString(User.USER_ROLE))) {
              context.sendError(404);
              return;
          }
  
          final int pageNum = Paginator.getPage(request);
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          if (isLoggedIn) {
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
          }
  
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          final List<JSONObject> userArticles = articleQueryService.getUserArticles(user.optString(Keys.OBJECT_ID), Article.ARTICLE_ANONYMOUS_C_ANONYMOUS, pageNum, pageSize);
          dataModel.put(Common.USER_HOME_ARTICLES, userArticles);
  
          int recordCount = 0;
          int pageCount = 0;
          if (!userArticles.isEmpty()) {
              final JSONObject first = userArticles.get(0);
              pageCount = first.optInt(Pagination.PAGINATION_PAGE_COUNT);
              recordCount = first.optInt(Pagination.PAGINATION_RECORD_COUNT);
          }
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, recordCount);
  
          dataModel.put(Common.IS_MY_ARTICLE, userName.equals(currentUser.optString(User.USER_NAME)));
  
          dataModel.put(Common.TYPE, "articlesAnonymous");
      }
  
      /**
       * Shows user home page.
       *
       * @param context the specified context
       */
      public void showHome(final RequestContext context) {
          final String userName = context.pathVar("userName");
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
          final int pageNum = Paginator.getPage(request);
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/home.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          if (isLoggedIn) {
              final JSONObject currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
          }
  
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          final List<JSONObject> userArticles = articleQueryService.getUserArticles(user.optString(Keys.OBJECT_ID), Article.ARTICLE_ANONYMOUS_C_PUBLIC, pageNum, pageSize);
          dataModel.put(Common.USER_HOME_ARTICLES, userArticles);
  
          int recordCount = 0;
          int pageCount = 0;
          if (!userArticles.isEmpty()) {
              final JSONObject first = userArticles.get(0);
              pageCount = first.optInt(Pagination.PAGINATION_PAGE_COUNT);
              recordCount = first.optInt(Pagination.PAGINATION_RECORD_COUNT);
          }
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, recordCount);
  
          final JSONObject currentUser = Sessions.getUser();
          if (null == currentUser) {
              dataModel.put(Common.IS_MY_ARTICLE, false);
          } else {
              dataModel.put(Common.IS_MY_ARTICLE, userName.equals(currentUser.optString(User.USER_NAME)));
          }
  
          dataModel.put(Common.TYPE, "home");
      }
  
      /**
       * Shows user home comments page.
       *
       * @param context the specified context
       */
      public void showHomeComments(final RequestContext context) {
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/comments.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          JSONObject currentUser = null;
          if (isLoggedIn) {
              currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
          }
  
          final List<JSONObject> userComments = commentQueryService.getUserComments(user.optString(Keys.OBJECT_ID), Comment.COMMENT_ANONYMOUS_C_PUBLIC, pageNum, pageSize, currentUser);
          dataModel.put(Common.USER_HOME_COMMENTS, userComments);
  
          int recordCount = 0;
          int pageCount = 0;
          if (!userComments.isEmpty()) {
              final JSONObject first = userComments.get(0);
              pageCount = first.optInt(Pagination.PAGINATION_PAGE_COUNT);
              recordCount = first.optInt(Pagination.PAGINATION_RECORD_COUNT);
          }
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, recordCount);
  
          dataModel.put(Common.TYPE, "comments");
      }
  
      /**
       * Shows user home following users page.
       *
       * @param context the specified context
       */
      public void showHomeFollowingUsers(final RequestContext context) {
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/following-users.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final JSONObject followingUsersResult = followQueryService.getFollowingUsers(followingId, pageNum, pageSize);
          final List<JSONObject> followingUsers = (List<JSONObject>) followingUsersResult.opt(Keys.RESULTS);
          dataModel.put(Common.USER_HOME_FOLLOWING_USERS, followingUsers);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          if (isLoggedIn) {
              final JSONObject currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
  
              for (final JSONObject followingUser : followingUsers) {
                  final String homeUserFollowingUserId = followingUser.optString(Keys.OBJECT_ID);
  
                  followingUser.put(Common.IS_FOLLOWING, followQueryService.isFollowing(followerId, homeUserFollowingUserId, Follow.FOLLOWING_TYPE_C_USER));
              }
          }
  
          final int followingUserCnt = followingUsersResult.optInt(Pagination.PAGINATION_RECORD_COUNT);
          final int pageCount = (int) Math.ceil((double) followingUserCnt / (double) pageSize);
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, followingUserCnt);
  
          dataModel.put(Common.TYPE, "followingUsers");
      }
  
      /**
       * Shows user home following tags page.
       *
       * @param context the specified context
       */
      public void showHomeFollowingTags(final RequestContext context) {
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/following-tags.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final JSONObject followingTagsResult = followQueryService.getFollowingTags(followingId, pageNum, pageSize);
          final List<JSONObject> followingTags = (List<JSONObject>) followingTagsResult.opt(Keys.RESULTS);
          dataModel.put(Common.USER_HOME_FOLLOWING_TAGS, followingTags);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          if (isLoggedIn) {
              final JSONObject currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
  
              for (final JSONObject followingTag : followingTags) {
                  final String homeUserFollowingTagId = followingTag.optString(Keys.OBJECT_ID);
  
                  followingTag.put(Common.IS_FOLLOWING, followQueryService.isFollowing(followerId, homeUserFollowingTagId, Follow.FOLLOWING_TYPE_C_TAG));
              }
          }
  
          final int followingTagCnt = followingTagsResult.optInt(Pagination.PAGINATION_RECORD_COUNT);
          final int pageCount = (int) Math.ceil(followingTagCnt / (double) pageSize);
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, followingTagCnt);
  
          dataModel.put(Common.TYPE, "followingTags");
      }
  
      /**
       * Shows user home following articles page.
       *
       * @param context the specified context
       */
      public void showHomeFollowingArticles(final RequestContext context) {
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/following-articles.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final JSONObject followingArticlesResult = followQueryService.getFollowingArticles(followingId, pageNum, pageSize);
          final List<JSONObject> followingArticles = (List<JSONObject>) followingArticlesResult.opt(Keys.RESULTS);
          dataModel.put(Common.USER_HOME_FOLLOWING_ARTICLES, followingArticles);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          if (isLoggedIn) {
              final JSONObject currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
  
              for (final JSONObject followingArticle : followingArticles) {
                  final String homeUserFollowingArticleId = followingArticle.optString(Keys.OBJECT_ID);
  
                  followingArticle.put(Common.IS_FOLLOWING, followQueryService.isFollowing(followerId, homeUserFollowingArticleId, Follow.FOLLOWING_TYPE_C_ARTICLE));
              }
          }
  
          final int followingArticleCnt = followingArticlesResult.optInt(Pagination.PAGINATION_RECORD_COUNT);
          final int pageCount = (int) Math.ceil(followingArticleCnt / (double) pageSize);
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, followingArticleCnt);
  
          dataModel.put(Common.TYPE, "followingArticles");
      }
  
      /**
       * Shows user home watching articles page.
       *
       * @param context the specified context
       */
      public void showHomeWatchingArticles(final RequestContext context) {
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/watching-articles.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final JSONObject followingArticlesResult = followQueryService.getWatchingArticles(followingId, pageNum, pageSize);
          final List<JSONObject> followingArticles = (List<JSONObject>) followingArticlesResult.opt(Keys.RESULTS);
          dataModel.put(Common.USER_HOME_FOLLOWING_ARTICLES, followingArticles);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          if (isLoggedIn) {
              final JSONObject currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
  
              for (final JSONObject followingArticle : followingArticles) {
                  final String homeUserFollowingArticleId = followingArticle.optString(Keys.OBJECT_ID);
  
                  followingArticle.put(Common.IS_FOLLOWING, followQueryService.isFollowing(followerId, homeUserFollowingArticleId, Follow.FOLLOWING_TYPE_C_ARTICLE_WATCH));
              }
          }
  
          final int followingArticleCnt = followingArticlesResult.optInt(Pagination.PAGINATION_RECORD_COUNT);
          final int pageCount = (int) Math.ceil(followingArticleCnt / (double) pageSize);
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, followingArticleCnt);
  
          dataModel.put(Common.TYPE, "watchingArticles");
      }
  
      /**
       * Shows user home follower users page.
       *
       * @param context the specified context
       */
      public void showHomeFollowers(final RequestContext context) {
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/followers.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          final JSONObject followerUsersResult = followQueryService.getFollowerUsers(followingId, pageNum, pageSize);
          final List<JSONObject> followerUsers = (List) followerUsersResult.opt(Keys.RESULTS);
          dataModel.put(Common.USER_HOME_FOLLOWER_USERS, followerUsers);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          if (isLoggedIn) {
              final JSONObject currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, followingId, Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
  
              for (final JSONObject followerUser : followerUsers) {
                  final String homeUserFollowerUserId = followerUser.optString(Keys.OBJECT_ID);
  
                  followerUser.put(Common.IS_FOLLOWING, followQueryService.isFollowing(followerId, homeUserFollowerUserId, Follow.FOLLOWING_TYPE_C_USER));
              }
  
              if (followerId.equals(followingId)) {
                  notificationMgmtService.makeRead(followingId, Notification.DATA_TYPE_C_NEW_FOLLOWER);
              }
          }
  
          final int followerUserCnt = followerUsersResult.optInt(Pagination.PAGINATION_RECORD_COUNT);
          final int pageCount = (int) Math.ceil((double) followerUserCnt / (double) pageSize);
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
          dataModel.put(Pagination.PAGINATION_RECORD_COUNT, followerUserCnt);
  
          dataModel.put(Common.TYPE, "followers");
  
          notificationMgmtService.makeRead(followingId, Notification.DATA_TYPE_C_NEW_FOLLOWER);
      }
  
      /**
       * Shows user home points page.
       *
       * @param context the specified context
       */
      public void showHomePoints(final RequestContext context) {
          final Request request = context.getRequest();
  
          final JSONObject user = (JSONObject) context.attr(User.USER);
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "home/points.ftl");
          final Map<String, Object> dataModel = renderer.getDataModel();
          dataModelService.fillHeaderAndFooter(context, dataModel);
          final int pageNum = Paginator.getPage(request);
          final int pageSize = Symphonys.USER_HOME_LIST_CNT;
          final int windowSize = Symphonys.USER_HOME_LIST_WIN_SIZE;
  
          fillHomeUser(dataModel, user, roleQueryService);
  
          avatarQueryService.fillUserAvatarURL(user);
  
          final String followingId = user.optString(Keys.OBJECT_ID);
          dataModel.put(Follow.FOLLOWING_ID, followingId);
  
          final JSONObject userPointsResult = pointtransferQueryService.getUserPoints(user.optString(Keys.OBJECT_ID), pageNum, pageSize);
          final List<JSONObject> userPoints = (List<JSONObject>) userPointsResult.opt(Keys.RESULTS);
          dataModel.put(Common.USER_HOME_POINTS, userPoints);
  
          final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);
          if (isLoggedIn) {
              final JSONObject currentUser = Sessions.getUser();
              final String followerId = currentUser.optString(Keys.OBJECT_ID);
  
              final boolean isFollowing = followQueryService.isFollowing(followerId, user.optString(Keys.OBJECT_ID), Follow.FOLLOWING_TYPE_C_USER);
              dataModel.put(Common.IS_FOLLOWING, isFollowing);
          }
  
          final int pointsCnt = userPointsResult.optInt(Pagination.PAGINATION_RECORD_COUNT);
          final int pageCount = (int) Math.ceil((double) pointsCnt / (double) pageSize);
  
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
          if (!pageNums.isEmpty()) {
              dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
              dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
          }
  
          dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
          dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
          dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
  
          dataModel.put(Common.TYPE, "points");
      }
  
      /**
       * List usernames.
       *
       * @param context the specified context
       */
      public void listNames(final RequestContext context) {
          final JSONObject result = Results.newSucc();
          context.renderJSON(result);
  
          final JSONObject requestJSON = context.requestJSON();
          final String namePrefix = requestJSON.optString("name");
          if (StringUtils.isBlank(namePrefix)) {
              final List<JSONObject> admins = userQueryService.getAdmins();
              final List<JSONObject> userNames = new ArrayList<>();
              for (final JSONObject admin : admins) {
                  final JSONObject userName = new JSONObject();
                  userName.put(User.USER_NAME, admin.optString(User.USER_NAME));
                  final String avatar = avatarQueryService.getAvatarURLByUser(admin, "20");
                  userName.put(UserExt.USER_AVATAR_URL, avatar);
  
                  userNames.add(userName);
              }
  
              result.put(Common.DATA, userNames);
              return;
          }
  
          final List<JSONObject> userNames = userQueryService.getUserNamesByPrefix(namePrefix);
          result.put(Common.DATA, userNames);
      }
  
      /**
       * List frequent emotions.
       *
       * @param context the specified context
       */
      public void getFrequentEmotions(final RequestContext context) {
          final JSONObject result = Results.newSucc();
          context.renderJSON(result);
  
          final List<JSONObject> data = new ArrayList<>();
          final JSONObject currentUser = Sessions.getUser();
          if (null == currentUser) {
              result.put(Common.DATA, data);
              return;
          }
  
          final String userId = currentUser.optString(Keys.OBJECT_ID);
          String emotions = emotionQueryService.getEmojis(userId);
          final String[] emojis = emotions.split(",");
          for (final String emoji : emojis) {
              String emojiChar = Emotions.toUnicode(":" + emoji + ":");
              if (StringUtils.contains(emojiChar, ":")) {
                  final String suffix = "huaji".equals(emoji) ? ".gif" : ".png";
                  emojiChar = Latkes.getStaticServePath() + "/emoji/graphics/" + emoji + suffix;
              }
  
              data.add(new JSONObject().put(emoji, emojiChar));
          }
  
          result.put(Common.DATA, data);
      }
  
      /**
       * Fills home user.
       *
       * @param dataModel the specified data model
       * @param user      the specified user
       */
      static void fillHomeUser(final Map<String, Object> dataModel, final JSONObject user, final RoleQueryService roleQueryService) {
          Escapes.escapeHTML(user);
          dataModel.put(User.USER, user);
  
          final String roleId = user.optString(User.USER_ROLE);
          final JSONObject role = roleQueryService.getRole(roleId);
          user.put(Role.ROLE_NAME, role.optString(Role.ROLE_NAME));
          user.put(UserExt.USER_T_CREATE_TIME, new Date(user.optLong(Keys.OBJECT_ID)));
      }
  }
  