// Copyright (c) Arlo Godfrey. All Rights Reserved. // Licensed under the GNU Lesser General Public License, Version 3.0 with additional terms. // See the LICENSE, LICENSE.LESSER and LICENSE.ADDITIONAL files in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using OwlCore.Extensions; using StrixMusic.Sdk.AppModels; using StrixMusic.Sdk.CoreModels; namespace StrixMusic.Sdk.AdapterModels { /// /// Aggregates many instances into one. /// public sealed class MergedSearch : ISearch, IMergedMutable { private readonly MergedCollectionConfig _config; private readonly List _sources; /// /// Creates a new instance of . /// public MergedSearch(IEnumerable sources, MergedCollectionConfig config) { _config = config; _sources = sources.ToList(); if (Sources.Any(x => x.SearchHistory != null)) SearchHistory = new MergedSearchHistory(Sources.Select(x => x.SearchHistory).PruneNull(), config); } /// public event EventHandler? SourcesChanged; /// public IReadOnlyList Sources => _sources; /// public async IAsyncEnumerable GetSearchAutoCompleteAsync(string query, [EnumeratorCancellation] CancellationToken cancellationToken = default) { var sources = await Sources.InParallel(async x => await x.GetSearchAutoCompleteAsync(query, cancellationToken).ToListAsync(cancellationToken)); foreach (var result in sources.SelectMany(x => x)) { yield return result; } } /// public async Task GetSearchResultsAsync(string query, CancellationToken cancellationToken = default) { var results = await Sources.InParallel(x => x.GetSearchResultsAsync(query, cancellationToken)); var merged = new MergedSearchResults(results, _config); return merged; } /// public async IAsyncEnumerable GetRecentSearchQueries([EnumeratorCancellation] CancellationToken cancellationToken = default) { var sources = await Sources.InParallel(async x => await x.GetRecentSearchQueries(cancellationToken).ToListAsync(cancellationToken)); var results = sources.SelectMany(x => x); var mergedData = new List(); foreach (var item in results) { // Inserts the new instance into its proper place by date. // Should minimize the amount of iterations and shifting of items in the list for (var i = mergedData.Count - 1; i >= 0; i--) { // If we're at the start of the list, indiscriminately add to the collection. if (i == 0) { mergedData.InsertOrAdd(i, new MergedSearchQuery(item.IntoList())); break; } if (mergedData[i].Equals(item)) { mergedData[i].AddSource(item); break; } if (item.CreatedAt > mergedData[i].CreatedAt) { mergedData.InsertOrAdd(i, new MergedSearchQuery(item.IntoList())); break; } } } foreach (var item in mergedData) { yield return item; } } /// public ISearchHistory? SearchHistory { get; } /// public void AddSource(ICoreSearch itemToMerge) { Guard.IsNotNull(itemToMerge, nameof(itemToMerge)); _sources.Add(itemToMerge); SourcesChanged?.Invoke(this, EventArgs.Empty); } /// public void RemoveSource(ICoreSearch itemToRemove) { Guard.IsNotNull(itemToRemove, nameof(itemToRemove)); _sources.Remove(itemToRemove); SourcesChanged?.Invoke(this, EventArgs.Empty); } /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true. We always merge together search sources. public bool Equals(ICoreSearch other) => true; /// public async ValueTask DisposeAsync() { await _sources.InParallel(x => x.DisposeAsync().AsTask()); } } }