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.http.Request;
  import org.b3log.latke.http.RequestContext;
  import org.b3log.latke.http.renderer.AbstractFreeMarkerRenderer;
  import org.b3log.latke.ioc.Inject;
  import org.b3log.latke.ioc.Singleton;
  import org.b3log.latke.model.Pagination;
  import org.b3log.latke.service.LangPropsService;
  import org.b3log.latke.util.Paginator;
  import org.b3log.symphony.model.Article;
  import org.b3log.symphony.model.Common;
  import org.b3log.symphony.model.UserExt;
  import org.b3log.symphony.service.ArticleQueryService;
  import org.b3log.symphony.service.DataModelService;
  import org.b3log.symphony.service.SearchQueryService;
  import org.b3log.symphony.service.UserQueryService;
  import org.b3log.symphony.util.Escapes;
  import org.b3log.symphony.util.Sessions;
  import org.b3log.symphony.util.Symphonys;
  import org.json.JSONArray;
  import org.json.JSONObject;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Search processor.
   * <ul>
   * <li>Searches keyword (/search), GET</li>
   * </ul>
   *
   * @author <a href="http://88250.b3log.org">Liang Ding</a>
   * @version 2.0.0.0, Feb 11, 2020
   * @since 1.4.0
   */
  @Singleton
  public class SearchProcessor {
  
      /**
       * Search query service.
       */
      @Inject
      private SearchQueryService searchQueryService;
  
      /**
       * Article query service.
       */
      @Inject
      private ArticleQueryService articleQueryService;
  
      /**
       * User query service.
       */
      @Inject
      private UserQueryService userQueryService;
  
      /**
       * Data model service.
       */
      @Inject
      private DataModelService dataModelService;
  
      /**
       * Language service.
       */
      @Inject
      private LangPropsService langPropsService;
  
      /**
       * Searches.
       *
       * @param context the specified context
       */
      public void search(final RequestContext context) {
          final Request request = context.getRequest();
  
          final AbstractFreeMarkerRenderer renderer = new SkinRenderer(context, "search-articles.ftl");
  
          if (!Symphonys.ES_ENABLED && !Symphonys.ALGOLIA_ENABLED) {
              context.sendError(404);
              return;
          }
  
          final Map<String, Object> dataModel = renderer.getDataModel();
          String keyword = context.param("key");
          if (StringUtils.isBlank(keyword)) {
              keyword = "";
          }
          dataModel.put(Common.KEY, Escapes.escapeHTML(keyword));
  
          final int pageNum = Paginator.getPage(request);
          int pageSize = Symphonys.ARTICLE_LIST_CNT;
          final JSONObject user = Sessions.getUser();
          if (null != user) {
              pageSize = user.optInt(UserExt.USER_LIST_PAGE_SIZE);
          }
          final List<JSONObject> articles = new ArrayList<>();
          int total = 0;
  
          if (Symphonys.ES_ENABLED) {
              final JSONObject result = searchQueryService.searchElasticsearch(Article.ARTICLE, keyword, pageNum, pageSize);
              if (null == result || 0 != result.optInt("status")) {
                  context.sendError(404);
                  return;
              }
  
              final JSONObject hitsResult = result.optJSONObject("hits");
              final JSONArray hits = hitsResult.optJSONArray("hits");
  
              for (int i = 0; i < hits.length(); i++) {
                  final JSONObject article = hits.optJSONObject(i).optJSONObject("_source");
                  articles.add(article);
              }
  
              total = result.optInt("total");
          }
  
          if (Symphonys.ALGOLIA_ENABLED) {
              final JSONObject result = searchQueryService.searchAlgolia(keyword, pageNum, pageSize);
              if (null == result) {
                  context.sendError(404);
                  return;
              }
  
              final JSONArray hits = result.optJSONArray("hits");
  
              for (int i = 0; i < hits.length(); i++) {
                  final JSONObject article = hits.optJSONObject(i);
                  articles.add(article);
              }
  
              total = result.optInt("nbHits");
              if (total > 1000) {
                  total = 1000; // Algolia limits the maximum number of search results to 1000
              }
          }
  
          articleQueryService.organizeArticles(articles);
          final Integer participantsCnt = Symphonys.ARTICLE_LIST_PARTICIPANTS_CNT;
          articleQueryService.genParticipants(articles, participantsCnt);
  
          dataModel.put(Article.ARTICLES, articles);
  
          final int pageCount = (int) Math.ceil(total / (double) pageSize);
          final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, Symphonys.ARTICLE_LIST_WIN_SIZE);
          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);
  
          dataModelService.fillHeaderAndFooter(context, dataModel);
          dataModelService.fillRandomArticles(dataModel);
          dataModelService.fillSideHotArticles(dataModel);
          dataModelService.fillSideTags(dataModel);
          dataModelService.fillLatestCmts(dataModel);
  
          String searchEmptyLabel = langPropsService.get("searchEmptyLabel");
          searchEmptyLabel = searchEmptyLabel.replace("${key}", keyword);
          dataModel.put("searchEmptyLabel", searchEmptyLabel);
      }
  }
  