From 95d19455de435359a8b6d856893811c732ff5657 Mon Sep 17 00:00:00 2001
From: Liza Carvelli <liza@carvel.li>
Date: Fri, 27 Dec 2024 18:25:59 +0100
Subject: [PATCH] Adjust filter logic

---
 Questionable/Data/JournalData.cs              |  38 ++---
 .../QuestJournalComponent.cs                  | 149 +++++++++---------
 .../JournalComponents/QuestJournalUtils.cs    |  20 +--
 3 files changed, 94 insertions(+), 113 deletions(-)

diff --git a/Questionable/Data/JournalData.cs b/Questionable/Data/JournalData.cs
index 52f37e22..c2983fe5 100644
--- a/Questionable/Data/JournalData.cs
+++ b/Questionable/Data/JournalData.cs
@@ -10,44 +10,30 @@ namespace Questionable.Data;
 
 internal sealed class JournalData
 {
-    private readonly IDataManager _dataManager;
-    private readonly QuestData _questData;
     public JournalData(IDataManager dataManager, QuestData questData)
     {
-        _dataManager = dataManager;
-        _questData = questData;
-
-        Reload();
-    }
-
-    public List<Genre> Genres { get; set; }
-    public List<Category> Categories { get; set; }
-    public List<Section> Sections { get; set; }
-
-    public void Reload()
-    {
-        var genres = _dataManager.GetExcelSheet<JournalGenre>()
+        var genres = dataManager.GetExcelSheet<JournalGenre>()
             .Where(x => x.RowId > 0 && x.Icon > 0)
-            .Select(x => new Genre(x, _questData.GetAllByJournalGenre(x.RowId)))
+            .Select(x => new Genre(x, questData.GetAllByJournalGenre(x.RowId)))
         .ToList();
 
-        var limsaStart = _dataManager.GetExcelSheet<QuestRedo>().GetRow(1);
-        var gridaniaStart = _dataManager.GetExcelSheet<QuestRedo>().GetRow(2);
-        var uldahStart = _dataManager.GetExcelSheet<QuestRedo>().GetRow(3);
+        var limsaStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(1);
+        var gridaniaStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(2);
+        var uldahStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(3);
         var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
             new uint[] { 108, 109 }.Concat(limsaStart.QuestRedoParam.Select(x => x.Quest.RowId))
                 .Where(x => x != 0)
-                .Select(x => _questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
+                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
                 .ToList());
         var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
             new uint[] { 85, 123, 124 }.Concat(gridaniaStart.QuestRedoParam.Select(x => x.Quest.RowId))
                 .Where(x => x != 0)
-                .Select(x => _questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
+                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
                 .ToList());
         var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1,
             new uint[] { 568, 569, 570 }.Concat(uldahStart.QuestRedoParam.Select(x => x.Quest.RowId))
                 .Where(x => x != 0)
-                .Select(x => _questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
+                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
                 .ToList());
         genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]);
         genres.Single(x => x.Id == 1)
@@ -56,15 +42,19 @@ internal sealed class JournalData
                 genreLimsa.Quests.Contains(x) || genreGridania.Quests.Contains(x) || genreUldah.Quests.Contains(x));
 
         Genres = genres.ToList();
-        Categories = _dataManager.GetExcelSheet<JournalCategory>()
+        Categories = dataManager.GetExcelSheet<JournalCategory>()
             .Where(x => x.RowId > 0)
             .Select(x => new Category(x, Genres.Where(y => y.CategoryId == x.RowId).ToList()))
         .ToList();
-        Sections = _dataManager.GetExcelSheet<JournalSection>()
+        Sections = dataManager.GetExcelSheet<JournalSection>()
             .Select(x => new Section(x, Categories.Where(y => y.SectionId == x.RowId).ToList()))
             .ToList();
     }
 
+    public List<Genre> Genres { get; }
+    public List<Category> Categories { get; }
+    public List<Section> Sections { get; }
+
     internal sealed class Genre
     {
         public Genre(JournalGenre journalGenre, List<IQuestInfo> quests)
diff --git a/Questionable/Windows/JournalComponents/QuestJournalComponent.cs b/Questionable/Windows/JournalComponents/QuestJournalComponent.cs
index de5a7bb8..437591ff 100644
--- a/Questionable/Windows/JournalComponents/QuestJournalComponent.cs
+++ b/Questionable/Windows/JournalComponents/QuestJournalComponent.cs
@@ -31,14 +31,12 @@ internal sealed class QuestJournalComponent
     private readonly IDalamudPluginInterface _pluginInterface;
     private readonly QuestJournalUtils _questJournalUtils;
     private readonly QuestValidator _questValidator;
-    private readonly IPluginLog _log;
 
     private List<FilteredSection> _filteredSections = [];
-    private string _searchText = string.Empty;
 
     public QuestJournalComponent(JournalData journalData, QuestRegistry questRegistry, QuestFunctions questFunctions,
         UiUtils uiUtils, QuestTooltipComponent questTooltipComponent, IDalamudPluginInterface pluginInterface,
-        QuestJournalUtils questJournalUtils, QuestValidator questValidator, IPluginLog log)
+        QuestJournalUtils questJournalUtils, QuestValidator questValidator)
     {
         _journalData = journalData;
         _questRegistry = questRegistry;
@@ -48,9 +46,10 @@ internal sealed class QuestJournalComponent
         _pluginInterface = pluginInterface;
         _questJournalUtils = questJournalUtils;
         _questValidator = questValidator;
-        _log = log;
     }
 
+    internal FilterConfiguration Filter { get; } = new();
+
     public void DrawQuests()
     {
         using var tab = ImRaii.TabItem("Quests");
@@ -74,7 +73,7 @@ internal sealed class QuestJournalComponent
 
         ImGui.SameLine();
         ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
-        if (ImGui.InputTextWithHint(string.Empty, "Search quests and categories", ref _searchText, 256))
+        if (ImGui.InputTextWithHint(string.Empty, "Search quests and categories", ref Filter.SearchText, 256))
             UpdateFilter();
 
         if (_filteredSections.Count > 0)
@@ -92,7 +91,7 @@ internal sealed class QuestJournalComponent
                 DrawSection(section);
         }
         else
-            ImGui.Text("No quest or category matches your search text.");
+            ImGui.Text("No quest or category matches your search.");
     }
 
     private void DrawSection(FilteredSection filter)
@@ -239,104 +238,63 @@ internal sealed class QuestJournalComponent
 
     public void UpdateFilter()
     {
-        _journalData.Reload();
-        Predicate<string> match = string.IsNullOrWhiteSpace(_searchText) ? x => true : x => x.Contains(_searchText, StringComparison.CurrentCultureIgnoreCase);
-
         _filteredSections = _journalData.Sections
-            .Select(section => FilterSection(section, match))
-            .Where(x => x != null)
-            .Cast<FilteredSection>()
+            .Select(x => FilterSection(x, Filter))
+            .Where(x => x.Categories.Count > 0)
             .ToList();
 
-        for (int i = 0; i < _filteredSections.Count; i++)
-        {
-            var section = _filteredSections[i];
-            for (int s = 0; s < section.Categories.Count; s++)
-            {
-                var category = section.Categories[s];
-                for (int c = 0; c < category.Genres.Count; c++)
-                {
-                    var genre = category.Genres[c];
-                    for (int g = 0; g < genre.Quests.Count; g++)
-                    {
-                        var quest = genre.Quests[g];
-
-                        //All Quest Filter conditions checked here, cause we just love IEnumerable
-                        if (QuestJournalUtils.AvailableOnly && !_questFunctions.IsReadyToAcceptQuest(quest.QuestId) ||
-                            QuestJournalUtils.HideNoPaths && !_questRegistry.TryGetQuest(quest.QuestId, out _))
-                        {
-                            genre.Quests.Remove(quest);
-                            g--;
-                        }
-                    }
-                }
-            }
-        }
-
         RefreshCounts();
     }
 
-    private static FilteredSection? FilterSection(JournalData.Section section, Predicate<string> match)
+    private FilteredSection FilterSection(JournalData.Section section, FilterConfiguration filter)
     {
-        if (match(section.Name))
+        IEnumerable<FilteredCategory> filteredCategories;
+        if (IsCategorySectionGenreMatch(filter, section.Name))
         {
-            return new FilteredSection(section,
-                section.Categories
-                    .Select(x => FilterCategory(x, _ => true))
-                    .Cast<FilteredCategory>()
-                    .ToList());
+            filteredCategories = section.Categories
+                .Select(x => FilterCategory(x, filter.WithoutName()));
         }
         else
         {
-            List<FilteredCategory> filteredCategories = section.Categories
-                .Select(category => FilterCategory(category, match))
-                .Where(x => x != null)
-                .Cast<FilteredCategory>()
-                .ToList();
-            if (filteredCategories.Count > 0)
-                return new FilteredSection(section, filteredCategories);
-
-            return null;
+            filteredCategories = section.Categories
+                .Select(category => FilterCategory(category, filter));
         }
+
+        return new FilteredSection(section, filteredCategories.Where(x => x.Genres.Count > 0).ToList());
     }
 
-    private static FilteredCategory? FilterCategory(JournalData.Category category, Predicate<string> match)
+    private FilteredCategory FilterCategory(JournalData.Category category, FilterConfiguration filter)
     {
-        if (match(category.Name))
+        IEnumerable<FilteredGenre> filteredGenres;
+        if (IsCategorySectionGenreMatch(filter, category.Name))
         {
-            return new FilteredCategory(category,
-                category.Genres
-                    .Select(x => FilterGenre(x, _ => true)!)
-                    .ToList());
+            filteredGenres = category.Genres
+                .Select(x => FilterGenre(x, filter.WithoutName()));
         }
         else
         {
-            List<FilteredGenre> filteredGenres = category.Genres
-                .Select(genre => FilterGenre(genre, match))
-                .Where(x => x != null)
-                .Cast<FilteredGenre>()
-                .ToList();
-            if (filteredGenres.Count > 0)
-                return new FilteredCategory(category, filteredGenres);
-
-            return null;
+            filteredGenres = category.Genres
+                .Select(genre => FilterGenre(genre, filter));
         }
+
+        return new FilteredCategory(category, filteredGenres.Where(x => x.Quests.Count > 0).ToList());
     }
 
-    private static FilteredGenre? FilterGenre(JournalData.Genre genre, Predicate<string> match)
+    private FilteredGenre FilterGenre(JournalData.Genre genre, FilterConfiguration filter)
     {
-        if (match(genre.Name))
-            return new FilteredGenre(genre, genre.Quests);
+        IEnumerable<IQuestInfo> filteredQuests;
+        if (IsCategorySectionGenreMatch(filter, genre.Name))
+        {
+            filteredQuests = genre.Quests
+                .Where(x => IsQuestMatch(filter.WithoutName(), x));
+        }
         else
         {
-            List<IQuestInfo> filteredQuests = genre.Quests
-                .Where(x => match(x.Name))
-                .ToList();
-            if (filteredQuests.Count > 0)
-                return new FilteredGenre(genre, filteredQuests);
+            filteredQuests = genre.Quests
+                .Where(x => IsQuestMatch(filter, x));
         }
 
-        return null;
+        return new FilteredGenre(genre, filteredQuests.ToList());
     }
 
     internal void RefreshCounts()
@@ -396,6 +354,28 @@ internal sealed class QuestJournalComponent
             _sectionCounts[sectionCount.Key] = sectionCount.Value with { Completed = 0 };
     }
 
+    private static bool IsCategorySectionGenreMatch(FilterConfiguration filter, string name)
+    {
+        return string.IsNullOrEmpty(filter.SearchText) ||
+               name.Contains(filter.SearchText, StringComparison.CurrentCultureIgnoreCase);
+    }
+
+    private bool IsQuestMatch(FilterConfiguration filter, IQuestInfo questInfo)
+    {
+        if (!string.IsNullOrEmpty(filter.SearchText) &&
+            !questInfo.Name.Contains(filter.SearchText, StringComparison.CurrentCultureIgnoreCase))
+            return false;
+
+        if (filter.AvailableOnly && !_questFunctions.IsReadyToAcceptQuest(questInfo.QuestId))
+            return false;
+
+        if (filter.HideNoPaths &&
+            (!_questRegistry.TryGetQuest(questInfo.QuestId, out var quest) || quest.Root.Disabled))
+            return false;
+
+        return true;
+    }
+
     private sealed record FilteredSection(JournalData.Section Section, List<FilteredCategory> Categories);
 
     private sealed record FilteredCategory(JournalData.Category Category, List<FilteredGenre> Genres);
@@ -409,4 +389,19 @@ internal sealed class QuestJournalComponent
         {
         }
     }
+
+    internal sealed class FilterConfiguration
+    {
+        public string SearchText = string.Empty;
+        public bool AvailableOnly;
+        public bool HideNoPaths;
+
+        public bool AdvancedFiltersActive => AvailableOnly || HideNoPaths;
+
+        public FilterConfiguration WithoutName() => new()
+        {
+            AvailableOnly = AvailableOnly,
+            HideNoPaths = HideNoPaths
+        };
+    }
 }
diff --git a/Questionable/Windows/JournalComponents/QuestJournalUtils.cs b/Questionable/Windows/JournalComponents/QuestJournalUtils.cs
index 8d43d7f7..906dd9d2 100644
--- a/Questionable/Windows/JournalComponents/QuestJournalUtils.cs
+++ b/Questionable/Windows/JournalComponents/QuestJournalUtils.cs
@@ -7,6 +7,7 @@ using Questionable.Functions;
 using Questionable.Model;
 using Questionable.Model.Questing;
 using System;
+using Dalamud.Interface.Colors;
 
 namespace Questionable.Windows.JournalComponents;
 
@@ -16,9 +17,6 @@ internal sealed class QuestJournalUtils
     private readonly QuestFunctions _questFunctions;
     private readonly ICommandManager _commandManager;
 
-    public static bool AvailableOnly;
-    public static bool HideNoPaths;
-
     public QuestJournalUtils(QuestController questController, QuestFunctions questFunctions,
         ICommandManager commandManager)
     {
@@ -50,19 +48,17 @@ internal sealed class QuestJournalUtils
         }
     }
 
-    internal static void ShowFilterContextMenu(QuestJournalComponent journalUI)
+    internal static void ShowFilterContextMenu(QuestJournalComponent journalUi)
     {
-        if (ImGuiComponents.IconButton(Dalamud.Interface.FontAwesomeIcon.Filter))
-            ImGui.OpenPopup($"##QuestFilters");
+        if (ImGuiComponents.IconButtonWithText(Dalamud.Interface.FontAwesomeIcon.Filter, "Filter"))
+            ImGui.OpenPopup("##QuestFilters");
 
-        using var popup = ImRaii.Popup($"##QuestFilters");
+        using var popup = ImRaii.Popup("##QuestFilters");
         if (!popup)
             return;
 
-        if (ImGui.Checkbox("Show only Available Quests", ref AvailableOnly) ||
-            ImGui.Checkbox("Hide Quests Without Path", ref HideNoPaths))
-            journalUI.UpdateFilter();
-
-
+        if (ImGui.Checkbox("Show only Available Quests", ref journalUi.Filter.AvailableOnly) ||
+            ImGui.Checkbox("Hide Quests Without Path", ref journalUi.Filter.HideNoPaths))
+            journalUi.UpdateFilter();
     }
 }