// 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.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Diagnostics;
using OwlCore.Events;
using OwlCore.Extensions;
using StrixMusic.Sdk.AppModels;
using StrixMusic.Sdk.BaseModels;
using StrixMusic.Sdk.CoreModels;
using StrixMusic.Sdk.Extensions;
using StrixMusic.Sdk.MediaPlayback;
namespace StrixMusic.Sdk.AdapterModels
{
///
/// A concrete class that merged multiple s.
///
public class MergedTrack : ITrack, IMergedMutable
{
private readonly MergedCollectionConfig _config;
private readonly ICoreTrack _preferredSource;
private readonly List _sources;
private readonly MergedCollectionMap _artistMap;
private readonly MergedCollectionMap _imageCollectionMap;
private readonly MergedCollectionMap _genreCollectionMap;
private readonly MergedCollectionMap _urlCollectionMap;
///
/// Initializes a new instance of the class.
///
public MergedTrack(IEnumerable tracks, MergedCollectionConfig config)
{
_config = config;
_sources = tracks.ToList();
// TODO: Use top Preferred core.
_preferredSource = _sources.First();
_artistMap = new MergedCollectionMap(this, config);
_imageCollectionMap = new MergedCollectionMap(this, config);
_genreCollectionMap = new MergedCollectionMap(this, config);
_urlCollectionMap = new MergedCollectionMap(this, config);
Name = _preferredSource.Name;
foreach (var item in _sources)
{
TotalArtistItemsCount += item.TotalArtistItemsCount;
TotalImageCount += item.TotalImageCount;
TotalGenreCount += item.TotalGenreCount;
TotalUrlCount += item.TotalUrlCount;
if (item.IsExplicit)
IsExplicit = true;
}
AttachEvents(_preferredSource);
}
private void AttachEvents(ICoreTrack source)
{
AttachPlayableEvents(source);
source.IsPlayArtistCollectionAsyncAvailableChanged += IsPlayArtistCollectionAsyncAvailableChanged;
source.IsPauseArtistCollectionAsyncAvailableChanged += IsPauseArtistCollectionAsyncAvailableChanged;
source.LanguageChanged += LanguageChanged;
source.LyricsChanged += Source_LyricsChanged;
source.IsExplicitChanged += IsExplicitChanged;
source.AlbumChanged += Source_AlbumChanged;
source.TrackNumberChanged += TrackNumberChanged;
_imageCollectionMap.ItemsChanged += ImageCollectionMap_ItemsChanged;
_imageCollectionMap.ItemsCountChanged += ImageCollectionMap_ItemsCountChanged;
_artistMap.ItemsChanged += ArtistMap_ItemsChanged;
_artistMap.ItemsCountChanged += ArtistMap_ItemsCountChanged;
_genreCollectionMap.ItemsChanged += GenreCollectionMap_ItemsChanged;
_genreCollectionMap.ItemsCountChanged += GenreCollectionMap_ItemsCountChanged;
_urlCollectionMap.ItemsChanged += UrlCollectionMap_ItemsChanged;
_urlCollectionMap.ItemsCountChanged += UrlCollectionMap_ItemsCountChanged;
}
private void DetachEvents(ICoreTrack source)
{
DetachPlayableEvents(source);
source.IsPlayArtistCollectionAsyncAvailableChanged -= IsPlayArtistCollectionAsyncAvailableChanged;
source.IsPauseArtistCollectionAsyncAvailableChanged -= IsPauseArtistCollectionAsyncAvailableChanged;
source.LanguageChanged -= LanguageChanged;
source.LyricsChanged -= Source_LyricsChanged;
source.IsExplicitChanged -= IsExplicitChanged;
source.AlbumChanged -= Source_AlbumChanged;
source.TrackNumberChanged -= TrackNumberChanged;
_imageCollectionMap.ItemsChanged -= ImageCollectionMap_ItemsChanged;
_imageCollectionMap.ItemsCountChanged -= ImageCollectionMap_ItemsCountChanged;
_artistMap.ItemsChanged -= ArtistMap_ItemsChanged;
_artistMap.ItemsCountChanged -= ArtistMap_ItemsCountChanged;
_genreCollectionMap.ItemsChanged -= GenreCollectionMap_ItemsChanged;
_genreCollectionMap.ItemsCountChanged -= GenreCollectionMap_ItemsCountChanged;
_urlCollectionMap.ItemsChanged -= UrlCollectionMap_ItemsChanged;
_urlCollectionMap.ItemsCountChanged -= UrlCollectionMap_ItemsCountChanged;
}
private void AttachPlayableEvents(IPlayableBase source)
{
source.PlaybackStateChanged += PlaybackStateChanged;
source.NameChanged += NameChanged;
source.DescriptionChanged += DescriptionChanged;
source.DurationChanged += DurationChanged;
source.LastPlayedChanged += LastPlayedChanged;
source.IsChangeNameAsyncAvailableChanged += IsChangeNameAsyncAvailableChanged;
source.IsChangeDurationAsyncAvailableChanged += IsChangeDurationAsyncAvailableChanged;
source.IsChangeDescriptionAsyncAvailableChanged += IsChangeDescriptionAsyncAvailableChanged;
}
private void DetachPlayableEvents(IPlayableBase source)
{
source.PlaybackStateChanged -= PlaybackStateChanged;
source.NameChanged -= NameChanged;
source.DescriptionChanged -= DescriptionChanged;
source.DurationChanged -= DurationChanged;
source.LastPlayedChanged -= LastPlayedChanged;
source.IsChangeNameAsyncAvailableChanged -= IsChangeNameAsyncAvailableChanged;
source.IsChangeDurationAsyncAvailableChanged -= IsChangeDurationAsyncAvailableChanged;
source.IsChangeDescriptionAsyncAvailableChanged -= IsChangeDescriptionAsyncAvailableChanged;
}
private void ArtistMap_ItemsCountChanged(object sender, int e)
{
TotalArtistItemsCount = e;
ArtistItemsCountChanged?.Invoke(this, e);
}
private void ImageCollectionMap_ItemsCountChanged(object sender, int e)
{
TotalImageCount = e;
ImagesCountChanged?.Invoke(this, e);
}
private void GenreCollectionMap_ItemsCountChanged(object sender, int e)
{
TotalGenreCount = e;
GenresCountChanged?.Invoke(this, e);
}
private void UrlCollectionMap_ItemsCountChanged(object sender, int e)
{
TotalUrlCount = e;
UrlsCountChanged?.Invoke(this, e);
}
private void ArtistMap_ItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems)
{
ArtistItemsChanged?.Invoke(this, addedItems, removedItems);
}
private void GenreCollectionMap_ItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems)
{
GenresChanged?.Invoke(this, addedItems, removedItems);
}
private void ImageCollectionMap_ItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems)
{
ImagesChanged?.Invoke(this, addedItems, removedItems);
}
private void UrlCollectionMap_ItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems)
{
UrlsChanged?.Invoke(this, addedItems, removedItems);
}
private void Source_LyricsChanged(object sender, ICoreLyrics? e)
{
if (e is null)
return;
var merged = new MergedLyrics(e, _config);
LyricsChanged?.Invoke(this, merged);
}
private void Source_AlbumChanged(object sender, ICoreAlbum? e)
{
if (e is null)
return;
var merged = new MergedAlbum(e.IntoList(), _config);
AlbumChanged?.Invoke(this, merged);
}
///
public event EventHandler? AlbumChanged;
///
public event EventHandler? TrackNumberChanged;
///
public event EventHandler? PlaybackStateChanged;
///
public event EventHandler? LanguageChanged;
///
public event EventHandler? LyricsChanged;
///
public event EventHandler? IsExplicitChanged;
///
public event EventHandler? NameChanged;
///
public event EventHandler? DescriptionChanged;
///
public event EventHandler? DurationChanged;
///
public event EventHandler? LastPlayedChanged;
///
public event EventHandler? IsPlayArtistCollectionAsyncAvailableChanged;
///
public event EventHandler? IsPauseArtistCollectionAsyncAvailableChanged;
///
public event EventHandler? IsChangeNameAsyncAvailableChanged;
///
public event EventHandler? IsChangeDescriptionAsyncAvailableChanged;
///
public event EventHandler? IsChangeDurationAsyncAvailableChanged;
///
public event EventHandler? ArtistItemsCountChanged;
///
public event EventHandler? GenresCountChanged;
///
public event EventHandler? ImagesCountChanged;
///
public event EventHandler? UrlsCountChanged;
///
public event CollectionChangedEventHandler? ArtistItemsChanged;
///
public event CollectionChangedEventHandler? GenresChanged;
///
public event CollectionChangedEventHandler? ImagesChanged;
///
public event CollectionChangedEventHandler? UrlsChanged;
///
public event EventHandler? DownloadInfoChanged;
///
public event EventHandler? SourcesChanged;
///
IReadOnlyList IMerged.Sources => Sources;
///
IReadOnlyList IMerged.Sources => Sources;
///
IReadOnlyList IMerged.Sources => Sources;
///
IReadOnlyList IMerged.Sources => Sources;
///
IReadOnlyList IMerged.Sources => Sources;
///
IReadOnlyList IMerged.Sources => Sources;
///
/// The original sources for this merged item.
///
public IReadOnlyList Sources => _sources;
///
public string Id => _preferredSource.Id;
///
public string Name { get; internal set; }
///
public TrackType Type => _preferredSource.Type;
///
public int TotalArtistItemsCount { get; private set; }
///
public int TotalImageCount { get; private set; }
///
public int TotalGenreCount { get; private set; }
///
public int TotalUrlCount { get; private set; }
///
public IPlayableCollectionGroup? RelatedItems { get; }
///
public IAlbum? Album { get; }
///
public int? TrackNumber => _preferredSource.TrackNumber;
///
public int? DiscNumber => _preferredSource.DiscNumber;
///
public CultureInfo? Language => _preferredSource.Language;
///
public ILyrics? Lyrics { get; }
///
public bool IsExplicit { get; }
///
public string? Description => _preferredSource.Description;
///
public PlaybackState PlaybackState => _preferredSource.PlaybackState;
///
public DownloadInfo DownloadInfo => default;
///
public TimeSpan Duration => _preferredSource.Duration;
///
public DateTime? LastPlayed => _preferredSource.LastPlayed;
///
public DateTime? AddedAt => _preferredSource.AddedAt;
///
public bool IsChangeAlbumAsyncAvailable => _preferredSource.IsChangeAlbumAsyncAvailable;
///
public bool IsChangeTrackNumberAsyncAvailable => _preferredSource.IsChangeTrackNumberAsyncAvailable;
///
public bool IsChangeLanguageAsyncAvailable => _preferredSource.IsChangeLanguageAsyncAvailable;
///
public bool IsChangeLyricsAsyncAvailable => _preferredSource.IsChangeLyricsAsyncAvailable;
///
public bool IsChangeIsExplicitAsyncAvailable => _preferredSource.IsChangeIsExplicitAsyncAvailable;
///
public bool IsPlayArtistCollectionAsyncAvailable => _preferredSource.IsPlayArtistCollectionAsyncAvailable;
///
public bool IsPauseArtistCollectionAsyncAvailable => _preferredSource.IsPauseArtistCollectionAsyncAvailable;
///
public bool IsChangeNameAsyncAvailable => _preferredSource.IsChangeNameAsyncAvailable;
///
public bool IsChangeDescriptionAsyncAvailable => _preferredSource.IsChangeDescriptionAsyncAvailable;
///
public bool IsChangeDurationAsyncAvailable => _preferredSource.IsChangeDurationAsyncAvailable;
///
public Task PauseArtistCollectionAsync(CancellationToken cancellationToken = default) => _preferredSource.PauseArtistCollectionAsync(cancellationToken);
///
public Task PlayArtistCollectionAsync(CancellationToken cancellationToken = default) => _preferredSource.PlayArtistCollectionAsync(cancellationToken);
///
public Task PlayArtistCollectionAsync(IArtistCollectionItem artistItem, CancellationToken cancellationToken = default)
{
var targetCore = _preferredSource.SourceCore;
ICoreArtistCollectionItem? source = null;
if (artistItem is IArtist artist)
source = artist.GetSources().FirstOrDefault(x => x.SourceCore.InstanceId == targetCore.InstanceId);
if (artistItem is IArtistCollection collection)
source = collection.GetSources().FirstOrDefault(x => x.SourceCore.InstanceId == targetCore.InstanceId);
Guard.IsNotNull(source, nameof(source));
return _preferredSource.PlayArtistCollectionAsync(source, cancellationToken);
}
///
public Task ChangeAlbumAsync(IAlbum? album, CancellationToken cancellationToken = default) => _sources.InParallel(x =>
{
if (!x.IsChangeAlbumAsyncAvailable)
return Task.CompletedTask;
var sourceAlbum = album?.GetSources().First(y => y.SourceCore == x.SourceCore);
return x.ChangeAlbumAsync(sourceAlbum, cancellationToken);
});
///
public Task ChangeTrackNumberAsync(int? trackNumber, CancellationToken cancellationToken = default)
{
return _sources.InParallel(x => x.ChangeTrackNumberAsync(trackNumber, cancellationToken));
}
///
public Task ChangeLanguageAsync(CultureInfo language, CancellationToken cancellationToken = default)
{
return _preferredSource.ChangeLanguageAsync(language, cancellationToken);
}
///
public Task ChangeLyricsAsync(ILyrics? lyrics, CancellationToken cancellationToken = default)
{
var sourceToChange = lyrics?.GetSources().First(x => x.SourceCore == _preferredSource.SourceCore);
Guard.IsNotNull(sourceToChange, nameof(sourceToChange));
return _preferredSource.ChangeLyricsAsync(sourceToChange, cancellationToken);
}
///
public Task ChangeIsExplicitAsync(bool isExplicit, CancellationToken cancellationToken = default) => _preferredSource.ChangeIsExplicitAsync(isExplicit, cancellationToken);
///
public Task ChangeNameAsync(string name, CancellationToken cancellationToken = default) => _preferredSource.ChangeNameAsync(name, cancellationToken);
///
public Task ChangeDescriptionAsync(string? description, CancellationToken cancellationToken = default) => _preferredSource.ChangeDescriptionAsync(description, cancellationToken);
///
public Task ChangeDurationAsync(TimeSpan duration, CancellationToken cancellationToken = default) => _preferredSource.ChangeDurationAsync(duration, cancellationToken);
///
public Task IsAddArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _preferredSource.IsAddArtistItemAvailableAsync(index, cancellationToken);
///
public Task IsAddImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _preferredSource.IsAddImageAvailableAsync(index, cancellationToken);
///
public Task IsAddGenreAvailableAsync(int index, CancellationToken cancellationToken = default) => _genreCollectionMap.IsAddItemAvailableAsync(index, cancellationToken);
///
public Task IsAddUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _urlCollectionMap.IsAddItemAvailableAsync(index, cancellationToken);
///
public Task IsRemoveArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _artistMap.IsRemoveItemAvailableAsync(index, cancellationToken);
///
public Task IsRemoveImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _imageCollectionMap.IsRemoveItemAvailableAsync(index, cancellationToken);
///
public Task IsRemoveGenreAvailableAsync(int index, CancellationToken cancellationToken = default) => _genreCollectionMap.IsRemoveItemAvailableAsync(index, cancellationToken);
///
public Task IsRemoveUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _urlCollectionMap.IsRemoveItemAvailableAsync(index, cancellationToken);
///
public IAsyncEnumerable GetArtistItemsAsync(int limit, int offset, CancellationToken cancellationToken = default) => _artistMap.GetItemsAsync(limit, offset, cancellationToken);
///
public IAsyncEnumerable GetImagesAsync(int limit, int offset, CancellationToken cancellationToken = default) => _imageCollectionMap.GetItemsAsync(limit, offset, cancellationToken);
///
public IAsyncEnumerable GetGenresAsync(int limit, int offset, CancellationToken cancellationToken = default) => _genreCollectionMap.GetItemsAsync(limit, offset, cancellationToken);
///
public IAsyncEnumerable GetUrlsAsync(int limit, int offset, CancellationToken cancellationToken = default) => _urlCollectionMap.GetItemsAsync(limit, offset, cancellationToken);
///
public Task AddArtistItemAsync(IArtistCollectionItem artistItem, int index, CancellationToken cancellationToken = default) => _artistMap.InsertItemAsync(artistItem, index, cancellationToken);
///
public Task AddImageAsync(IImage image, int index, CancellationToken cancellationToken = default) => _imageCollectionMap.InsertItemAsync(image, index, cancellationToken);
///
public Task AddGenreAsync(IGenre genre, int index, CancellationToken cancellationToken = default) => _genreCollectionMap.InsertItemAsync(genre, index, cancellationToken);
///
public Task AddUrlAsync(IUrl genre, int index, CancellationToken cancellationToken = default) => _urlCollectionMap.InsertItemAsync(genre, index, cancellationToken);
///
public Task RemoveArtistItemAsync(int index, CancellationToken cancellationToken = default) => _artistMap.RemoveAtAsync(index, cancellationToken);
///
public Task RemoveImageAsync(int index, CancellationToken cancellationToken = default) => _imageCollectionMap.RemoveAtAsync(index, cancellationToken);
///
public Task RemoveGenreAsync(int index, CancellationToken cancellationToken = default) => _genreCollectionMap.RemoveAtAsync(index, cancellationToken);
///
public Task RemoveUrlAsync(int index, CancellationToken cancellationToken = default) => _urlCollectionMap.RemoveAtAsync(index, cancellationToken);
///
public Task StartDownloadOperationAsync(DownloadOperation operation, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
///
public bool Equals(ICoreArtistCollectionItem other) => Equals(other as ICoreTrack);
///
public bool Equals(ICoreImageCollection other) => Equals(other as ICoreTrack);
///
public bool Equals(ICoreUrlCollection other) => Equals(other as ICoreTrack);
///
public bool Equals(ICoreArtistCollection other) => Equals(other as ICoreTrack);
///
public bool Equals(ICoreGenreCollection other) => Equals(other as ICoreTrack);
///
public override bool Equals(object? obj) => Equals(obj as ICoreTrack);
///
public override int GetHashCode() => _preferredSource.Id.GetHashCode();
///
public void AddSource(ICoreTrack itemToMerge)
{
Guard.IsNotNull(itemToMerge, nameof(itemToMerge));
_artistMap.AddSource(itemToMerge);
_imageCollectionMap.AddSource(itemToMerge);
_urlCollectionMap.AddSource(itemToMerge);
_sources.Add(itemToMerge);
SourcesChanged?.Invoke(this, EventArgs.Empty);
}
///
public void RemoveSource(ICoreTrack itemToRemove)
{
Guard.IsNotNull(itemToRemove, nameof(itemToRemove));
_artistMap.RemoveSource(itemToRemove);
_imageCollectionMap.RemoveSource(itemToRemove);
_urlCollectionMap.RemoveSource(itemToRemove);
_sources.Remove(itemToRemove);
SourcesChanged?.Invoke(this, EventArgs.Empty);
}
///
public bool Equals(ICoreTrack? other)
{
return other != null &&
other.Name == Name &&
other.TrackNumber == TrackNumber &&
other.Type == Type &&
other.DiscNumber == DiscNumber &&
other.Duration == Duration &&
Album is MergedAlbum album &&
!(other.Album is null) &&
album.Equals(other.Album);
}
///
public async ValueTask DisposeAsync()
{
DetachEvents(_preferredSource);
await _sources.InParallel(x => x.DisposeAsync().AsTask());
await _imageCollectionMap.DisposeAsync();
await _artistMap.DisposeAsync();
await _genreCollectionMap.DisposeAsync();
await _urlCollectionMap.DisposeAsync();
}
}
}