package org.silverpeas.looks.aurora;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.silverpeas.components.almanach.AlmanachSettings;
import org.silverpeas.components.delegatednews.model.DelegatedNews;
import org.silverpeas.components.delegatednews.service.DelegatedNewsService;
import org.silverpeas.components.questionreply.QuestionReplyException;
import org.silverpeas.components.questionreply.model.Question;
import org.silverpeas.components.questionreply.service.QuestionManager;
import org.silverpeas.components.questionreply.service.QuestionManagerProvider;
import org.silverpeas.components.quickinfo.model.News;
import org.silverpeas.components.quickinfo.model.QuickInfoService;
import org.silverpeas.components.quickinfo.service.QuickInfoDateComparatorDesc;
import org.silverpeas.components.rssaggregator.model.RSSItem;
import org.silverpeas.components.rssaggregator.model.SPChannel;
import org.silverpeas.components.rssaggregator.service.RSSService;
import org.silverpeas.components.rssaggregator.service.RSSServiceProvider;
import org.silverpeas.core.admin.space.SpaceInst;
import org.silverpeas.core.subscription.SubscriptionServiceProvider;
import org.silverpeas.core.subscription.service.ComponentSubscription;
import org.silverpeas.core.subscription.util.SubscriptionList;
import org.silverpeas.core.web.util.WebRedirection;
import org.silverpeas.kernel.SilverpeasRuntimeException;
import org.silverpeas.core.admin.component.model.ComponentInst;
import org.silverpeas.core.admin.component.model.ComponentInstLight;
import org.silverpeas.core.admin.component.model.PersonalComponent;
import org.silverpeas.core.admin.component.model.PersonalComponentInstance;
import org.silverpeas.core.admin.component.model.SilverpeasComponentInstance;
import org.silverpeas.core.admin.domain.model.Domain;
import org.silverpeas.core.admin.service.AdminException;
import org.silverpeas.core.admin.service.Administration;
import org.silverpeas.core.admin.service.OrganizationController;
import org.silverpeas.core.admin.service.SpaceWithSubSpacesAndComponents;
import org.silverpeas.core.admin.space.SpaceInstLight;
import org.silverpeas.core.admin.user.model.Group;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.admin.user.model.User;
import org.silverpeas.core.admin.user.model.UserDetail;
import org.silverpeas.core.admin.user.model.UserDetailsSearchCriteria;
import org.silverpeas.core.calendar.Priority;
import org.silverpeas.core.contribution.content.form.Form;
import org.silverpeas.core.contribution.content.wysiwyg.service.WysiwygController;
import org.silverpeas.core.contribution.model.WysiwygContent;
import org.silverpeas.core.contribution.publication.model.PublicationDetail;
import org.silverpeas.core.contribution.template.publication.PublicationTemplate;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateImpl;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateManager;
import org.silverpeas.core.index.search.model.QueryDescription;
import org.silverpeas.core.index.search.model.SearchResult;
import org.silverpeas.core.mylinks.model.LinkDetail;
import org.silverpeas.core.search.SearchService;
import org.silverpeas.core.util.ArrayUtil;
import org.silverpeas.core.util.CollectionUtil;
import org.silverpeas.core.util.DateUtil;
import org.silverpeas.kernel.bundle.LocalizationBundle;
import org.silverpeas.kernel.bundle.ResourceLocator;
import org.silverpeas.kernel.util.StringUtil;
import org.silverpeas.core.util.URLUtil;
import org.silverpeas.kernel.logging.SilverLogger;
import org.silverpeas.core.web.look.DefaultLayoutConfiguration;
import org.silverpeas.core.web.look.LookHelper;
import org.silverpeas.core.web.look.LookSilverpeasV5Helper;
import org.silverpeas.core.web.look.Shortcut;
import org.silverpeas.core.webapi.calendar.CalendarResourceURIs;
import org.silverpeas.core.webapi.calendar.CalendarWebManager;
import org.silverpeas.core.webapi.mylinks.MyLinksWebManager;
import org.silverpeas.looks.aurora.service.almanach.CalendarEventOccurrenceEntity;
import org.silverpeas.looks.aurora.service.weather.City;
import org.silverpeas.looks.aurora.service.weather.WeatherSettings;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.of;
import static org.silverpeas.core.admin.space.SpaceInst.SPACE_KEY_PREFIX;
import static org.silverpeas.core.admin.user.constant.UserState.*;
import static org.silverpeas.kernel.util.StringUtil.EMPTY;
import static org.silverpeas.kernel.util.StringUtil.split;
import static org.silverpeas.looks.aurora.AuroraSpaceHomePage.TEMPLATE_NAME;

@SuppressWarnings("unused")
public class LookAuroraHelper extends LookSilverpeasV5Helper {

  private static final String DEFAULT_VALUE = "toBeDefined";
  private static final Random RANDOM = new Random();
  private static final String BANNER_ALL_SPACES = "*";
  private static final String PROPERTY_NEWS_MAIN = "home.news";
  private static final String PROPERTY_NEWS_SECONDARY = "home.news.secondary";
  private static final String PROPERTY_NEWS_THIRD = "home.news.third";
  private static final String PROPERTY_SPACEHOMEPAGE_CUSTOMTEMPLATE = "space.homepage.management" +
      ".customtemplate.";
  private static final String QUICKINFO = "quickinfo";
  private final DelegatedNewsService delegatedNewsService;
  private final LocalizationBundle messages;
  private LookSettings settings;
  private List<Domain> directoryDomains = null;
  private List<Group> directoryGroups = null;

  // Main search form session objects
  private PublicationTemplate mainSearchTemplate = null;
  private boolean mainSearchTemplateLoaded = false;

  public static LookHelper newLookHelper(HttpSession session) {
    LookHelper lookHelper = new LookAuroraHelper(session);
    session.setAttribute(LookHelper.SESSION_ATT, lookHelper);
    return lookHelper;
  }

  private LookAuroraHelper(HttpSession session) {
    super(session);

    delegatedNewsService = DelegatedNewsService.get();

    String language = getMainSessionController().getFavoriteLanguage();

    messages =
        ResourceLocator.getLocalizationBundle("org.silverpeas.looks.aurora.multilang.lookBundle",
            language);

    final WeatherSettings weatherSettings = WeatherSettings.get();
    weatherSettings.setSettingsFile(getSettings("home.weather.settings",
        WeatherSettings.DEFAULT_SETTINGS));
  }

  @Override
  public void initLayoutConfiguration() {
    super.initLayoutConfiguration();
    DefaultLayoutConfiguration layout = (DefaultLayoutConfiguration) super.getLayoutConfiguration();
    layout.setHeaderURL("/look/jsp/TopBar.jsp");
    layout.setBodyURL("/look/jsp/bodyPartAurora.jsp");
    layout.setBodyNavigationURL("/look/jsp/DomainsBar.jsp");

    settings = new LookSettings(getSettingsBundle());
  }

  public List<BannerMainItem> getBannerMainItems() {
    String param = getSettings("banner.spaces", "*");
    if (StringUtil.isDefined(param)) {
      List<String> definedSpaceIds = new ArrayList<>();
      if (param.contains("*")) {
        definedSpaceIds = getExplicitlyDefinedSpaceIds();
      }
      String[] spaceIds = StringUtils.split(param);
      return getBannerMainItemOfSpaces(spaceIds, definedSpaceIds);
    }
    return new ArrayList<>();
  }

  private List<BannerMainItem> getBannerMainItemOfSpaces(final String[] spaceIds,
      final List<String> definedSpaceIds) {
    List<BannerMainItem> items = new ArrayList<>();
    OrganizationController oc = getOrganisationController();
    for (String spaceId : spaceIds) {
      if (BANNER_ALL_SPACES.equals(spaceId)) {
        List<String> allRootSpaceIds =
            new ArrayList<>(Arrays.asList(oc.getAllRootSpaceIds(getUserId())));
        allRootSpaceIds.removeAll(definedSpaceIds);
        for (String rootSpaceId : allRootSpaceIds) {
          items.add(getBannerMainItem(rootSpaceId));
        }
      } else {
        if (oc.isSpaceAvailable(spaceId, getUserId())) {
          items.add(getBannerMainItem(spaceId));
        }
      }
    }
    return items;
  }

  private BannerMainItem getBannerMainItem(String spaceId) {
    OrganizationController oc = getOrganisationController();
    return new BannerMainItem(oc.getSpaceInstLightById(spaceId));
  }

  private List<String> getExplicitlyDefinedSpaceIds() {
    List<String> spaceIds = new ArrayList<>();
    spaceIds.add(getProjectsSpaceId());
    spaceIds.add(getSettings("applications.spaceId", ""));
    String bannerSpaceIds = getSettings("banner.spaces", "*");
    for (String bannerSpaceId : StringUtils.split(bannerSpaceIds)) {
      if (!BANNER_ALL_SPACES.equals(bannerSpaceId)) {
        spaceIds.add(bannerSpaceId);
      }
    }
    return spaceIds;
  }

  public String getProjectsSpaceId() {
    return getSettings("projects.spaceId", "0");
  }

  public List<Project> getProjects() {
    return getProjects(false);
  }

  public List<Project> getProjects(boolean includeApps) {
    List<Project> projects = new ArrayList<>();
    OrganizationController controller = getOrganisationController();
    String[] spaceIds = controller.getAllowedSubSpaceIds(getUserId(), getProjectsSpaceId());
    for (String spaceId : spaceIds) {
      SpaceInstLight space = controller.getSpaceInstLightById(spaceId);
      if (space != null) {
        Project project = new Project(space);
        if (includeApps) {
          project.setComponents(getComponents(space.getId()));
        }
        projects.add(project);
      }
    }
    return projects;
  }

  private List<SilverpeasComponentInstance> getComponents(String spaceId) {
    List<SilverpeasComponentInstance> components = new ArrayList<>();
    OrganizationController controller = getOrganisationController();
    String[] componentIds = controller.getAvailCompoIdsAtRoot(spaceId, getUserId());
    for (String componentId : componentIds) {
      SilverpeasComponentInstance component = controller.getComponentInstLight(componentId);
      if (component != null && !component.isHidden()) {
        components.add(component);
      }
    }
    return components;
  }

  public String getLoginHomePage() {
    return getSettings("loginHomepage", URLUtil.getApplicationURL() + "/look/jsp/Main.jsp");
  }

  public List<Shortcut> getToolsShortcuts() {
    return getShortcuts("tool");
  }

  public List<Shortcut> getMainShortcuts() {
    return getShortcuts("home");
  }

  public List<Shortcut> getShortcuts(String id) {
    List<Shortcut> shortcuts = new ArrayList<>();
    boolean end = false;
    int i = 1;
    while (!end) {
      String prefix = "Shortcut." + id + "." + i;
      String url = getSettings(prefix + ".Url", null);
      if (StringUtil.isDefined(url)) {
        String target = getSettings(prefix + ".Target", DEFAULT_VALUE);
        String altText = getSettings(prefix + ".AltText", DEFAULT_VALUE);
        String iconUrl = getSettings(prefix + ".IconUrl", null);
        Shortcut shortcut = new Shortcut(iconUrl, target, url, altText);
        shortcuts.add(shortcut);
        i++;
      } else {
        end = true;
      }
    }
    return shortcuts;
  }

  public List<City> getWeatherCities() {
    if (!getSettings("home.weather", false)) {
      return Collections.emptyList();
    }

    return WeatherSettings.get().getCities();
  }

  public List<PublicationDetail> getLatestPublications() {
    String spaceId = getSettings("home.publications.spaceid", "");
    if (!StringUtil.isDefined(spaceId)) {
      spaceId = null;
    }
    return getLatestPublications(spaceId,
        Integer.parseInt(getSettings("home.publications.nb", "3")));
  }

  public List<PublicationDetail> getMoreLatestPublications() {
    return getLatestPublications(null,
            Integer.parseInt(getSettings("home.publications.more.nb", "30")));
  }

  public List<ComponentInst> getApplications() {
    List<ComponentInst> hyperLinks = new ArrayList<>();
    String[] asAvailCompoForCurUser = getOrganisationController().getAvailCompoIds(
        getSettings("applications.spaceId", DEFAULT_VALUE), getMainSessionController().getUserId());
    for (String componentId : asAvailCompoForCurUser) {
      ComponentInst componentInst = getOrganisationController().getComponentInst(componentId);
      if ("hyperlink".equals(componentInst.getName()) && !componentInst.isHidden()) {
        hyperLinks.add(componentInst);
      }
    }

    return hyperLinks;
  }

  @Override
  public List<PublicationDetail> getLatestPublications(String spaceId, int nbPublis) {
    String[] excludedComponentIds =
        StringUtils.split(getSettings("home.publications.components.excluded", ""));
    String[] excludedSpaceIds =
        StringUtils.split(getSettings("home.publications.spaces.excluded", ""));
    for (String excludedSpaceId : excludedSpaceIds) {
      try {
        String[] excludedAppId = Administration.get().getAllComponentIdsRecur(excludedSpaceId);
        excludedComponentIds = ArrayUtils.addAll(excludedComponentIds, excludedAppId);
      } catch (AdminException e) {
        SilverLogger.getLogger(this).error(e);
      }
    }

    try {
      SpaceInst personalSpace = Administration.get().getPersonalSpace(getUserId());
      String[] excludedPersonalAppId = Administration.get().getAllComponentIds(personalSpace.getId());
      excludedComponentIds = ArrayUtils.addAll(excludedComponentIds, excludedPersonalAppId);
    } catch(Exception e) {
      SilverLogger.getLogger(this).error(e);
    }

    List<PublicationDetail> publications =
        super.getLatestPublications(spaceId, Arrays.asList(excludedComponentIds), nbPublis * 2);
    List<PublicationDetail> result = new ArrayList<>();
    for (PublicationDetail publication : publications) {
      String componentId = publication.getPK().getInstanceId();
      boolean excluded = ArrayUtil.contains(excludedComponentIds, componentId);
      if (!isComponentForNews(componentId) && !excluded) {
        result.add(publication);
      }
      if (result.size() == nbPublis) {
        return result;
      }
    }
    return result;
  }

  private boolean isComponentForNews(String componentId) {
    return componentId.startsWith(QUICKINFO);
  }

  public NewsList getNews() {
    return getNewsList(1);
  }

  public NewsList getSecondaryNews() {
    return getNewsList(2);
  }

  public NewsList getThirdNews() {
    return getNewsList(3);
  }

  public List<AuroraNews> getNews(int index, int max) {
    List<AuroraNews> appNews = new ArrayList<>();
    List<String> ids = getComponentIdsForNews(index);
    String key = PROPERTY_NEWS_MAIN;
    if (index == 2) {
      key = PROPERTY_NEWS_SECONDARY;
    } else if (index == 3) {
      key = PROPERTY_NEWS_THIRD;
    }
    boolean importantOnly = getSettings(key + ".importantOnly", false);
    List<News> news = getNewsByComponentIds(ids, importantOnly);
    int i = 1;
    for (News n : news) {
      if (i >= max) break;
      News aNews = QuickInfoService.get().getNewsByForeignId(n.getPublicationId());
      AuroraNews an = new AuroraNews(aNews);
      an.setAppShortcut(getAppShortcut(aNews.getComponentInstanceId()));
      appNews.add(an);
      i++;
    }
    return appNews;
  }

  public List<AuroraNews> getAllDelegatedNews() {
    List<AuroraNews> delegatedNews = new ArrayList<>();
    List<News> news = getDelegatedNews();
    for (News n : news) {
      News aNews = QuickInfoService.get().getNewsByForeignId(n.getPublicationId());
      AuroraNews an = new AuroraNews(aNews);
      an.setAppShortcut(getAppShortcut(aNews.getComponentInstanceId()));
      delegatedNews.add(an);
    }

    return delegatedNews;
  }
  private List<News> getDelegatedNews() {
    List<News> news = new ArrayList<>();
    List<DelegatedNews> delegatedNews = delegatedNewsService.getAllValidDelegatedNews();
    for (DelegatedNews delegated : delegatedNews) {
      if (delegated != null && isComponentAvailable(delegated.getInstanceId())) {
        News aNews = new News(delegated.getPublicationDetail());
        if (delegated.getPublicationDetail() != null) {
          aNews.setPublicationId(delegated.getPublicationDetail().getId());
          aNews.setComponentInstanceId(delegated.getPublicationDetail().getInstanceId());
          news.add(aNews);
        }
      }
    }
    return news;
  }

  private Shortcut getAppShortcut(String appId) {
    Shortcut shortcut = null;
    try {
      ComponentInst app = Administration.get().getComponentInst(appId);
      shortcut = new Shortcut("", "", app.getPermalink(), app.getLabel());
    } catch (AdminException e) {
      SilverLogger.getLogger(this).error(e);
    }
    return shortcut;
  }

  private List<News> getNewsByComponentIds(List<String> allowedComponentIds,
      boolean importantOnly) {
    if (CollectionUtil.isEmpty(allowedComponentIds)) {
      return Collections.emptyList();
    }

    // getting news from allowed components
    List<News> allNews = new ArrayList<>();
    for (String appId : allowedComponentIds) {
      allNews.addAll(getNewsByComponentId(appId, importantOnly));
    }

    // sorting news
    allNews.sort(QuickInfoDateComparatorDesc.comparator);

    return allNews;
  }

  private List<News> getNewsByComponentId(String allowedComponentId, boolean importantOnly) {
    // getting news from allowed component
    List<News> allNews = QuickInfoService.get().getVisibleNews(allowedComponentId);
    if (!importantOnly) {
      return allNews;
    }
    List<News> importantNews = new ArrayList<>();
    for (News news : allNews) {
      if (news.isImportant()) {
        importantNews.add(news);
      }
    }
    return importantNews;
  }

  public NewsList getAllNewsByTaxonomyPosition(String taxonomyPosition) {
    List<News> allNews = getNewsByTaxonomyPosition(getAllowedComponents(QUICKINFO, "*"), false,
        taxonomyPosition);
    final NewsList newsList = new NewsList(allNews, null);
    newsList.setRenderingType(NewsList.RenderingType.LIST);
    return newsList;
  }

  private List<News> getNewsByTaxonomyPosition(List<String> allowedComponentIds,
      boolean importantOnly, String taxonomyPosition) {
    List<News> someNews = new ArrayList<>();
    QueryDescription query = new QueryDescription();
    query.setSearchingUser(getUserId());
    query.setTaxonomyPosition(taxonomyPosition);
    for (String componentId : allowedComponentIds) {
      query.addComponent(componentId);
    }

    try {
      List<SearchResult> results = SearchService.get().search(query);
      for (SearchResult result : results) {
        News news = QuickInfoService.get().getNewsByForeignId(result.getId());
        if (importantOnly && news.isImportant()) {
          someNews.add(news);
        } else if (!importantOnly) {
          someNews.add(news);
        }
      }
    } catch (Exception e) {
      SilverLogger.getLogger(this).error(e);
    }

    // sorting news
    someNews.sort(QuickInfoDateComparatorDesc.comparator);

    return someNews;
  }

  public NextEvents getNextEvents() {
    final String[] appIds = split(getSettings("home.events.appId", ""), ' ');
    final List<String> allowedComponentIds = getAllowedComponents("almanach", appIds);
    if (getSettings("home.events.personal.include", false)) {
      PersonalComponent.getByName("userCalendar").ifPresent(c -> allowedComponentIds
          .add(PersonalComponentInstance.from(User.getCurrentRequester(), c).getId()));
    }
    return getNextEvents(allowedComponentIds);
  }

  private NextEvents getNextEvents(List<String> allowedComponentIds) {
    boolean includeToday = getSettings("home.events.today.include", true);
    int nbDays = getSettings("home.events.maxDays", 2);
    boolean fetchOnlyImportant = getSettings("home.events.importantOnly", true);
    return getNextEvents(allowedComponentIds, includeToday, nbDays, fetchOnlyImportant);
  }

  protected NextEvents getNextEvents(List<String> allowedComponentIds, boolean includeToday,
      int nbDays, boolean onlyImportant) {
    List<NextEventsDate> result = new ArrayList<>();
    if (!allowedComponentIds.isEmpty()) {
      final CalendarResourceURIs uri = CalendarResourceURIs.get();
      List<CalendarEventOccurrenceEntity> events =
          CalendarWebManager.get(allowedComponentIds.get(0))
              .getNextEventOccurrences(allowedComponentIds, emptySet(), emptySet(), emptySet(),
                  AlmanachSettings.getZoneId(),
                  AlmanachSettings.getNbOccurrenceLimitOfShortNextEventView())
              .map(o -> {
                final CalendarEventOccurrenceEntity entity = new CalendarEventOccurrenceEntity(o);
                entity.withOccurrencePermalinkUrl(uri.ofOccurrencePermalink(o));
                return entity;
              })
              .collect(toList());

      result = filterNextEvents(events, nbDays, includeToday, onlyImportant);
    }
    NextEvents nextEvents = new NextEvents(result);
    if (allowedComponentIds.size() == 1) {
      nextEvents.setApp(getAppById(allowedComponentIds.get(0)));
    }
    return nextEvents;
  }

  private List<NextEventsDate> filterNextEvents(final List<CalendarEventOccurrenceEntity> events,
      final int nbDays, final boolean includeToday, final boolean onlyImportant) {
    Date date = null;
    Date today = new Date();
    NextEventsDate nextEventsDate = null;
    List<NextEventsDate> filteredEventDates = new ArrayList<>();
    for (CalendarEventOccurrenceEntity event : events) {
      Date eventDate = event.getStartDateAsDate();
      if ((!onlyImportant || Priority.HIGH == event.getPriority() || event.getInstanceId().startsWith("userCalendar")) &&
          (includeToday || DateUtil.compareTo(today, eventDate, true) != 0)) {
        if (date == null || DateUtil.compareTo(date, eventDate, true) != 0) {
          nextEventsDate = new NextEventsDate(eventDate);
          if (filteredEventDates.size() == nbDays) {
            break;
          }
          filteredEventDates.add(nextEventsDate);
          date = eventDate;
        }
        nextEventsDate.addEvent(event);
      }
    }
    return filteredEventDates;
  }

  private List<String> getAllowedComponents(String componentName, String... componentIds) {
    if (ArrayUtil.contains(componentIds, "*")) {
      return CollectionUtil
          .asList(getOrganisationController().getComponentIdsForUser(getUserId(), componentName));
    }
    List<String> allowedComponentIds = new ArrayList<>();
    for (String componentId : componentIds) {
      if (isComponentAvailable(componentId)) {
        allowedComponentIds.add(componentId);
      }
    }
    return allowedComponentIds;
  }

  public List<PublicationDetail> getLastUpdatedPublicationsSince(String spaceId, int sinceNbDays,
      int nbPublis) {
    try {
      return getPublicationHelper().getUpdatedPublications(spaceId, sinceNbDays, nbPublis);
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
      SilverLogger.getLogger(this).error(e);
    }
    return Collections.emptyList();
  }

  public List<PublicationDetail> getLastUpdatedPublications(String spaceId) {
    return getLastUpdatedPublicationsSince(spaceId, Integer.parseInt(getSettings(
        "publications.page.since.days", "30")), 10000);
  }

  public List<LinkDetail> getBookmarks() {
    return MyLinksWebManager.get()
        .getAllLinksOfCurrentUser()
        .stream()
        .filter(LinkDetail::isVisible)
        .collect(toList());
  }

  public Questions getQuestions() {
    Questions faqs = new Questions();
    QuestionManager qm = QuestionManagerProvider.getQuestionManager();
    String appId = getSettings("home.faq.appId", "");
    if (StringUtil.isDefined(appId) && isComponentAvailable(appId)) {
      faqs.setAppId(appId);
      try {
        String[] profiles = getOrganisationController().getUserProfiles(getUserId(), appId);
        SilverpeasRole role = SilverpeasRole.getHighestFrom(SilverpeasRole.fromStrings(profiles));
        if (role != null) {
          faqs.setCanAskAQuestion(role.isGreaterThanOrEquals(SilverpeasRole.WRITER));
        }
        List<Question> questions = qm.getQuestions(appId);
        if (CollectionUtil.isNotEmpty(questions)) {
          return filterFAQsMatchingQuestions(faqs, questions);
        }
      } catch (QuestionReplyException e) {
        SilverLogger.getLogger(this).error(e);
      }
    }
    return null;
  }

  private Questions filterFAQsMatchingQuestions(final Questions faqs,
      final List<Question> questions) {
    int nb = getSettings("home.faq.nb", 1);
    if (nb > questions.size()) {
      nb = questions.size();
    }
    if ("random".equalsIgnoreCase(getSettings("home.faq.display", "random")) &&
        questions.size() > 1 && questions.size() > nb) {
      Predicate<Question> shouldTryAgain =
          q -> faqs.getList().stream().anyMatch(f -> q.getPK().getId().equals(f.getPK().getId()));
      int j = 0;
      while (j < nb) {
        int i = RANDOM.nextInt(questions.size() - 1);
        Question question = questions.get(i);
        boolean tryAgain = shouldTryAgain.test(question);
        if (!tryAgain) {
          faqs.add(question);
          j++;
        }
      }
    } else {
      for (int i = 0; i < nb; i++) {
        faqs.add(questions.get(i));
      }
    }
    return faqs;
  }

  @Override
  public LocalizationBundle getLocalizedBundle() {
    return messages;
  }

  public LookSettings getLookSettings() {
    return settings;
  }

  private boolean isComponentAvailable(String componentId) {
    return getOrganisationController().isComponentAvailableToUser(componentId, getUserId());
  }

  public BodyPartSettings getBodyPartSettings(HttpServletRequest request) {
    BodyPartSettings bodyPartSettings = new BodyPartSettings();

    HttpSession session = request.getSession();
    String strGoToNew = (String) session.getAttribute(WebRedirection.REDIRECT_URL);
    String spaceId = request.getParameter("SpaceId");
    String subSpaceId = request.getParameter("SubSpaceId");
    String fromTopBar = request.getParameter("FromTopBar");
    String componentId = request.getParameter("ComponentId");
    String login = request.getParameter("Login");
    String fromMySpace = request.getParameter("FromMySpace");

    if (StringUtil.isDefined(strGoToNew) || StringUtil.isDefined(spaceId) ||
        StringUtil.isDefined(subSpaceId) || StringUtil.isDefined(componentId)) {
      // ignore login page when try to access a direct resource
      login = null;
    }

    //Allow to force a page only on login and when user clicks on logo
    boolean displayLoginHomepage = false;
    String loginHomepage = getSettings("loginHomepage", "");
    if (StringUtil.isDefined(loginHomepage) && StringUtil.isDefined(login) &&
        !StringUtil.isDefined(spaceId) && !StringUtil.isDefined(subSpaceId) &&
        !StringUtil.isDefined(componentId) && !StringUtil.isDefined(strGoToNew)) {
      displayLoginHomepage = true;

      if (!isUserCanDisplayMainHomePage()) {
        spaceId = getBannerMainItems().get(0).getSpace().getId();
        displayLoginHomepage = false;
        login = null;
      }
    }

    if (displayLoginHomepage || StringUtil.getBooleanValue(fromMySpace)) {
      setSpaceId(null);
      setSubSpaceId(null);
    }

    StringBuilder paramsForDomainsBar = new StringBuilder().append("{");
    buildDomainsBar(paramsForDomainsBar, spaceId, subSpaceId, fromTopBar, componentId, fromMySpace);

    String frameURL = getFrameURL(bodyPartSettings, strGoToNew, spaceId, componentId,
        displayLoginHomepage, loginHomepage);

    session.removeAttribute("goto");
    session.removeAttribute(WebRedirection.REDIRECT_URL);
    session.removeAttribute(WebRedirection.REDIRECT_TO_COMPONENT);
    session.removeAttribute(WebRedirection.REDIRECT_TO_SPACE);

    boolean hideMenu = "1".equals(fromTopBar) || "1".equals(login);
    if (hideMenu) {
      bodyPartSettings.setHideMenu(true);
    }

    bodyPartSettings.setDomainsBarParams(paramsForDomainsBar.toString());
    bodyPartSettings.setMainPartURL(frameURL);
    return bodyPartSettings;
  }

  private String getFrameURL(final BodyPartSettings bodyPartSettings,
      final String strGoToNew, final String spaceId, final String componentId,
      final boolean displayLoginHomepage, final String loginHomepage) {
    String webContext = URLUtil.getApplicationURL();
    String frameURL;
    if (displayLoginHomepage) {
      frameURL = loginHomepage;
      bodyPartSettings.setHideMenu(true);
    } else if (strGoToNew == null) {
      if (StringUtil.isDefined(componentId)) {
        frameURL = webContext + URLUtil.getURL(null, componentId) + "Main";
      } else {
        String homePage = getSettings("defaultHomepage", "/dt");
        String param = "";
        if (StringUtil.isDefined(spaceId)) {
          param = "?SpaceId=" + spaceId;
        }
        frameURL = webContext + homePage + param;
      }
    } else {
      frameURL = webContext + strGoToNew;
      if (strGoToNew.startsWith(webContext)) {
        frameURL = strGoToNew;
      }
    }
    return frameURL;
  }

  private void buildDomainsBar(final StringBuilder paramsForDomainsBar, final String spaceId,
      final String subSpaceId, final String fromTopBar, final String componentId,
      final String fromMySpace) {
    if ("1".equals(fromTopBar)) {
      if (spaceId != null) {
        paramsForDomainsBar
            .append("privateDomain:'")
            .append(spaceId)
            .append("', privateSubDomain" + ":'")
            .append(subSpaceId)
            .append("', FromTopBar:'1'");
      }
    } else if (componentId != null) {
      paramsForDomainsBar
          .append("privateDomain:'', component_id:'")
          .append(componentId)
          .append("'");
    } else {
      paramsForDomainsBar
          .append("privateDomain:'")
          .append(spaceId)
          .append("'");
    }
    if ("1".equals(fromMySpace)) {
      paramsForDomainsBar.append(",FromMySpace:'1'");
    }
    paramsForDomainsBar.append('}');
  }

  public boolean isUserCanDisplayMainHomePage() {
    if (getSettings("home.displayedWhenNoNews", true) || getUserDetail().isAccessAdmin()) {
      return true;
    }
    NewsList news = getNews();
    return !news.isEmpty();
  }

  public List<Domain> getDirectoryDomains() {
    if (directoryDomains == null) {
      directoryDomains = Arrays.stream(getSettings("directory.domains", "").split(","))
          .filter(StringUtil::isDefined)
          .map(i -> getOrganisationController().getDomain(i.trim()))
          .filter(Objects::nonNull)
          .distinct()
          .collect(toList());
    }
    return directoryDomains;
  }

  public List<Group> getDirectoryGroups() {
    if (directoryGroups == null) {
      directoryGroups = Arrays.stream(getSettings("directory.groups", "").split(","))
          .filter(StringUtil::isDefined)
          .map(groupId -> Group.getById(groupId.trim()))
          .filter(Objects::nonNull)
          .distinct()
          .collect(toList());
    }
    return directoryGroups;
  }

  @Override
  public boolean isDirectoryDisplayEnabled() {
    return getSettings("directory.enabled", true) &&
        ((isAnonymousUser() && settings.isDisplayDirectoryForAnonymous()) ||
            (!isAnonymousUser() && isAccessGuest() && settings.isDisplayDirectoryForGuest()) ||
            super.isDirectoryDisplayEnabled());
  }

  public String getDirectoryURL() {
    if (!isDirectoryDisplayEnabled()) {
      return null;
    }
    String url = "/Rdirectory/jsp/Main";
    url += "?DomainIds=" + getDirectoryDomainIds();
    url += "&GroupIds=" + getDirectoryGroups().stream().map(Group::getId).collect(Collectors.joining(","));
    url += "&Sort=" + getSettings("directory.sort", "ALPHA");
    return url;
  }

  public String getDirectoryDomainIds() {
    return getDirectoryDomains().stream().map(Domain::getId).collect(Collectors.joining(","));
  }

  public boolean isBookmarkDisplayEnabled() {
    return getSettings("banner.bookmark.enable");
  }
  public RSSFeeds getRSSFeeds() {
    String componentId = getSettings("home.rss.appId", "");
    if (StringUtil.isDefined(componentId)) {
      RSSService rssService = RSSServiceProvider.getRSSService();
      try {
        List<RSSItem> allItems = rssService.getApplicationItems(componentId, true);
        List<SPChannel> channels = rssService.getAllChannels(componentId);
        RSSFeeds rssFeeds = new RSSFeeds(channels, allItems);
        rssFeeds.setDisplayer(getSettings("home.rss.displayer", "aggregate"));
        return rssFeeds;
      } catch (Exception e) {
        SilverLogger.getLogger(this).error(e);
      }
    }
    return null;
  }

  public FreeZone getFreeZone() {
    String componentId = getSettings("home.freezone.appId", "");
    boolean useLabel = getSettings("home.freezone.app.useLabel", true);
    String defaultLabel = getString("look.home.freezone");
    return getAFreeZone(componentId, useLabel, defaultLabel);
  }

  public FreeZone getThinFreeZone() {
    String componentId = getSettings("home.freezone.thin.appId", "");
    boolean useLabel = getSettings("home.freezone.thin.app.useLabel", true);
    String defaultLabel = getString("look.home.freezone.thin");
    return getAFreeZone(componentId, useLabel, defaultLabel);
  }

  private FreeZone getAFreeZone(String componentId, boolean useComponentLabel,
      String defaultLabel) {
    if (StringUtil.isDefined(componentId)) {
      WysiwygContent content = WysiwygController.get(componentId, componentId, getLanguage());
      if (content != null) {
        FreeZone freeZone = new FreeZone(content.getData());
        if (useComponentLabel) {
          Optional<SilverpeasComponentInstance> component =
              getOrganisationController().getComponentInstance(componentId);
          component.ifPresent(silverpeasComponentInstance ->
              freeZone.setTitle(silverpeasComponentInstance.getLabel(getLanguage())));
        } else {
          freeZone.setTitle(defaultLabel);
        }
        return freeZone;
      }
    }
    return null;
  }

  public AuroraSpaceHomePage getHomePage(String spaceId) {
    setSpaceIdAndSubSpaceId(spaceId);
    String currentSpaceId = getSubSpaceId();

    // get main information of space
    SpaceInstLight space = getOrganisationController().getSpaceInstLightById(currentSpaceId);
    return new AuroraSpaceHomePage(this, space);
  }

  public ComponentInstLight getConfigurationApp(String spaceId) {
    OrganizationController oc = OrganizationController.get();
    List<ComponentInstLight> apps = getAppsByName(spaceId, "webPages");
    for (ComponentInstLight app : apps) {
      String xmlFormName = oc.getComponentParameterValue(app.getId(), "xmlTemplate");
      if (StringUtil.isDefined(xmlFormName) && xmlFormName.equals(TEMPLATE_NAME)) {
        return app;
      }
    }
    return null;
  }

  @SuppressWarnings("SameParameterValue")
  private List<ComponentInstLight> getAppsByName(String spaceId, String name) {
    List<ComponentInstLight> apps = new ArrayList<>();
    OrganizationController oc = OrganizationController.get();
    String[] appsIds = oc.getAvailCompoIdsAtRoot(spaceId, getUserId());
    for (String appId : appsIds) {
      ComponentInstLight app = oc.getComponentInstLight(appId);
      if (app.getName().equals(name)) {
        apps.add(app);
      }
    }
    return apps;
  }

  public boolean isSpaceAdmin(String spaceId) {
    if (getUserDetail().isAccessAdmin()) {
      return true;
    }
    return getSpaceAdmins(spaceId).contains(getUserDetail());
  }

  public Form getMainSearchForm() {
    PublicationTemplateImpl template = (PublicationTemplateImpl) getMainSearchTemplate();
    if (template != null) {
      try {
        Form form = template.getSearchForm();
        if (form == null) {
          SilverLogger.getLogger(this).warn("The template " + template.getName() +
              " has no form defined for the search! No main search form to render");
        } else {
          form.setData(template.getSearchTemplate().getEmptyRecord());
          return form;
        }
      } catch (Exception e) {
        SilverLogger.getLogger(this).error(e);
      }
    }
    return null;
  }

  private PublicationTemplate getMainSearchTemplate() {
    if (mainSearchTemplate == null && !mainSearchTemplateLoaded) {
      String templateFilename = getSettings("home.search.template", "");
      if (StringUtil.isDefined(templateFilename)) {
        PublicationTemplateManager templateManager = PublicationTemplateManager.getInstance();
        try {
          mainSearchTemplate = templateManager.loadPublicationTemplate(templateFilename);
        } catch (Exception e) {
          SilverLogger.getLogger(this).error(e);
        }
      }
      mainSearchTemplateLoaded = true;
    }
    return mainSearchTemplate;
  }

  private NewsList getNewsList(int index) {
    String key = PROPERTY_NEWS_MAIN;
    if (index == 2) {
      key = PROPERTY_NEWS_SECONDARY;
    } else if (index == 3) {
      key = PROPERTY_NEWS_THIRD;
    }

    List<News> news = new ArrayList<>();
    String newsType = getSettings(key, "");
    if (!StringUtil.isDefined(newsType)) {
      return setupMainHomePageNewsListDisplay(key, new NewsList(news, null));
    }

    boolean importantOnly = getSettings(key + ".importantOnly", false);
    boolean taxonomyType = "taxonomy".equalsIgnoreCase(newsType);
    String uniqueAppId = "";
    if ("delegated".equalsIgnoreCase(newsType)) {
      news = getDelegatedNews();
    } else if (taxonomyType) {
      String position = getSettings(key + ".taxonomy.position", "");
      news = getNewsByTaxonomyPosition(getAllowedComponents(QUICKINFO, "*"), importantOnly,
          position);
    } else {
      List<String> allowedComponentIds = getComponentIdsForNews(index);
      if (allowedComponentIds.size() == 1) {
        uniqueAppId = allowedComponentIds.get(0);
      }
      news = getNewsByComponentIds(allowedComponentIds, importantOnly);
    }

    NewsList newsList = new NewsList(news, getAppById(uniqueAppId));
    int nbNews = getSettings(key + ".size", -1);
    if (nbNews != -1 && news.size() > nbNews) {
      newsList.limitNews(nbNews);
    }
    if (getSettings(key + ".taxonomy.display", false)) {
      newsList.withTaxonomyButtons();
    }
    return setupMainHomePageNewsListDisplay(key, newsList);
  }

  private NewsList setupMainHomePageNewsListDisplay(final String key, final NewsList newsList) {
    switch (key) {
      case PROPERTY_NEWS_MAIN:
        newsList.setZone(AuroraSpaceHomePageZone.MAIN);
        break;
      case PROPERTY_NEWS_THIRD:
        newsList.setZone(AuroraSpaceHomePageZone.THIRD);
        break;
      default:
        newsList.setZone(AuroraSpaceHomePageZone.RIGHT);
    }
    newsList.setRenderingType(ofNullable(getSettings(key + ".displayer", null))
        .filter(StringUtil::isDefined)
        .map(String::toUpperCase)
        .map(NewsList.RenderingType::valueOf)
        .orElse(NewsList.RenderingType.CAROUSEL));
    newsList.setImageSize(
        getSettings(key + ".width", "800") + "x" + getSettings(key + ".height", ""));
    return newsList;
  }

  private List<String> getComponentIdsForNews(int index) {
    String mainNewsProp = getSettings(PROPERTY_NEWS_MAIN, "");
    String secondaryNewsProp = getSettings(PROPERTY_NEWS_SECONDARY, "");
    boolean excludeSecondaryNews = "*".equals(mainNewsProp);
    boolean excludeMainNews = "*".equals(secondaryNewsProp);

    String[] mainNewsComponentIds = split(mainNewsProp, ' ');
    String[] secondaryNewsComponentIds = split(secondaryNewsProp, ' ');
    List<String> allowedMainNewsComponentIds =
        getAllowedComponents(QUICKINFO, mainNewsComponentIds);
    List<String> allowedSecondaryNewsComponentIds =
        getAllowedComponents(QUICKINFO, secondaryNewsComponentIds);
    if (index == 2) {
      if (excludeMainNews) {
        // exclude main news components from secondary ones
        allowedSecondaryNewsComponentIds.removeAll(allowedMainNewsComponentIds);
      }
      return allowedSecondaryNewsComponentIds;
    } else {
      if (excludeSecondaryNews) {
        // exclude secondary news components from main ones
        allowedMainNewsComponentIds.removeAll(allowedSecondaryNewsComponentIds);
      }
      return allowedMainNewsComponentIds;
    }
  }

  public String getSpaceHomePageCustomTemplate(String spaceId) {
    List<SpaceInstLight> spaces = getOrganisationController().getPathToSpace(spaceId);
    // get closest space definition so we start from space itself
    List<SpaceInstLight> pathToRoot = CollectionUtil.reverse(spaces);
    for (SpaceInstLight space : pathToRoot) {
      String customTemplate =
          getSettings(PROPERTY_SPACEHOMEPAGE_CUSTOMTEMPLATE + space.getId(), "");
      if (StringUtil.isDefined(customTemplate)) {
        return customTemplate;
      }
    }
    return null;
  }

  NewsList getNewsOfSpace(final String spaceId, final boolean includeSubSpaces,
      final boolean importantOnly, final int limit) {
    final String localSpaceId = spaceId.replace(SPACE_KEY_PREFIX, EMPTY);
    final List<String> appIds;
    try {
      final SpaceWithSubSpacesAndComponents.ComponentInstanceSelector componentInstanceSelector =
          getOrganisationController()
              .getFullTreeviewOnComponentName(getUserId(), QUICKINFO)
              .componentInstanceSelector();
      appIds = componentInstanceSelector.fromSpaces(Set.of(localSpaceId))
          .select()
          .stream()
          .filter(i -> includeSubSpaces || i.getSpaceId().equals(localSpaceId))
          .map(SilverpeasComponentInstance::getId)
          .collect(toList());
    } catch (AdminException e) {
      throw new SilverpeasRuntimeException(e);
    }
    final List<News> someNews = getNewsByComponentIds(appIds, importantOnly);
    if (someNews.size() > limit) {
      return new NewsList(someNews.subList(0, limit), null);
    }
    return new NewsList(someNews, null);
  }

  public NewUsersList getNewUsersList() {
    int nbUsersDisplayed = getSettings("home.users.nb", 0);
    if (nbUsersDisplayed > 0) {
      final OrganizationController organizationController = OrganizationController.get();
      UserDetailsSearchCriteria criteria = new UserDetailsSearchCriteria().onUserStatesToExclude(
          BLOCKED, DEACTIVATED, REMOVED);
      String groupId = getSettings("home.users.group", "-1");
      if (!"-1".equals(groupId)) {
        criteria.onGroupIds(groupId);
      }
      final List<User> users = organizationController.searchUsers(criteria);
      users.sort(new UserDetail.OnCreationDate().reversed());
      return getNewUsersList(false, users);
    }
    return null;
  }

  private NewUsersList getNewUsersList(boolean spaceScope, List<User> users) {
    String paramPrefix = spaceScope ? "space.homepage." : "home.";
    int nbUsersDisplayed = getSettings(paramPrefix + "users.nb", 0);
    if (users.size() < nbUsersDisplayed) {
      nbUsersDisplayed = users.size() - 1;
    }
    NewUsersList newUsersList = new NewUsersList(users.subList(0, nbUsersDisplayed));
    newUsersList.setAvatar(getSettings(paramPrefix + "users.avatar", true));
    List<String> fields = of(split(getSettings(paramPrefix + "users.fields", ""), " "))
        .collect(toList());
    newUsersList.setFields(fields);
    return newUsersList;
  }

  public NewUsersList getSpaceNewUsersList(String spaceId) {
    int nbUsersDisplayed = getSettings("space.homepage.users.nb", 0);
    if (nbUsersDisplayed > 0) {
      final OrganizationController organizationController = OrganizationController.get();
      final List<User> newUsers = of(organizationController.getAllComponentIdsRecur(spaceId))
          .flatMap(i -> of(organizationController.getAllUsers(i)).map(User.class::cast))
          .distinct()
          .sorted(new UserDetail.OnCreationDate().reversed())
          .collect(toList());
      return getNewUsersList(true, newUsers);
    }
    return null;
  }

  public List<NewsList> getAllNewsBySubscription() {
    List<NewsList> news = new ArrayList<>();
    List<String> newsComponentsIds = getAppNewsIdSubscribed();
    for (String componentId : newsComponentsIds) {
      int maxHistory = getSettings("subscription.news.maxbycompoments", 10);
      List<News> l = QuickInfoService.get().getVisibleNews(componentId);
      if (l.size() > maxHistory) {
        l = l.subList(0,maxHistory);
      }
      NewsList n = new NewsList(l ,getAppById(componentId));
      news.add(n);
    }

    return news;
  }

  private List<String> getAppNewsIdSubscribed() {
    List<String> newsComponentsIds = new ArrayList<>();
    SubscriptionList subscriptions = SubscriptionServiceProvider.getSubscribeService().getByUserSubscriber(getUserId());
    for (Object subscription: subscriptions) {
      if (subscription instanceof ComponentSubscription) {
        ComponentSubscription componentSubscription = (ComponentSubscription) subscription;
        if (componentSubscription.getResource().getInstanceId().startsWith(QUICKINFO)) {
          newsComponentsIds.add(componentSubscription.getResource().getInstanceId());
        }
      }
    }
    return newsComponentsIds;
  }

  public List<AuroraNews> getLastNewsSubscribed() {
    List<String> newsComponentsIds = getAppNewsIdSubscribed();
    List<AuroraNews> news = new ArrayList<>();
    for (String componentId : newsComponentsIds) {
      List<News> l = QuickInfoService.get().getVisibleNews(componentId);
      for (News n : l) {
        AuroraNews auroraNews = new AuroraNews(n);
        news.add(auroraNews);
      }
    }
    news.sort((n1, n2) -> n2.getNews().getPublishDate().compareTo(n1.getNews().getPublishDate()));
    int max = getSettings("home.subscription.news.max",4);
    if (news.size() > max) {
      news = news.subList(0, max);
    }

    return news;
  }

  private ComponentInst getAppById(String appId) {
    ComponentInst app = null;
    if (StringUtil.isDefined(appId)) {
      try {
        app = Administration.get().getComponentInst(appId);
      } catch (AdminException e) {
        SilverLogger.getLogger(this).error(e);
      }
    }
    return app;
  }
}
