using System; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Reactive; using System.Reactive.Linq; using System.Text.RegularExpressions; using Avalonia.Controls.Platform; using DynamicData; using DynamicData.Binding; using ReactiveUI; using Splat; using StarsAssistant.Model; using StarsAssistant.ViewModels; namespace StarsAssistant.Services; public class FleetManager : IEnableLogger, IDisposable { protected Services.Game Game = Locator.Current.GetService()!; /// /// SourceList for fleets read from the game files /// private SourceList _fleets = new(); /// /// Fleet data summarized by destination, will be chaned to _fleets /// private SourceCache _fleetSummariesByDestination = new(fs => fs.Destination); /// /// Extract a readonly Cache for all fleet summaries tracked by the /// fleet manager. /// public IObservableCache FleetSummariesByDestination => _fleetSummariesByDestination.AsObservableCache(); /// /// Public accessor to the continously updated fleet summaries. /// // public IObservableCache FleetSummariesByDestination => _fleetSummaries.AsObservableCache(); /// /// Disposal tracking /// // private IDisposable _fleetSummariesSubscription; public FleetManager() { // CreateFleetSummariesLink(); } // [MemberNotNull(nameof(_fleetSummariesSubscription))] // protected void CreateFleetSummariesLink() // { // /* // _fleetSummariesSubscription = _fleets.Connect() // .Filter(fleet => fleet.TrueDestination != "-- ") // .Log(this, $"{DateTime.Now.ToLongTimeString()} fleetWatcher filter", fleet => $"{fleet}") // // .AutoRefreshOnObservable(fleet => fleet.WhenAnyValue( // // f => f.Ironium, f => f.Boranium, f => f.Germanium, f => f.Colonists)) // .Log(this, $"{DateTime.Now.ToLongTimeString()} fleetWatcher refresh", fleet => $"{fleet}") // .GroupOn(fleet => fleet.TrueDestination) // .Log(this, $"{DateTime.Now.ToLongTimeString()} fleetWatcher group", grp => $"{grp.TotalChanges} detected") // .Transform(group => new FleetSummaryByDestination // { // Destination = group.GroupKey, // TotalIronium = group.List.Items.Sum(f => f.Ironium), // TotalBoranium = group.List.Items.Sum(f => f.Boranium), // TotalGermanium = group.List.Items.Sum(f => f.Germanium), // TotalColonists = group.List.Items.Sum(f => f.Colonists) // }) // .AddKey(fs => fs.Destination) // .Log(this, "FleetManager _fleetSummaries update", changes => // $"{changes.Adds} adds, {changes.Updates} updates, {changes.Removes} removes" // ) // .PopulateInto(_fleetSummaries) // ; // */ // /* // // Demo only // var sourceCache = new SourceCache(fs => fs.Destination); // var tmp = fleetSummaries // .AddKey(fs => fs.Destination) // .PopulateInto(sourceCache); // _fleetSummaries // .ObserveOn(RxApp.MainThreadScheduler) // .Bind(out summaries) // .DisposeMany() // .Subscribe(); // d1 = summaries // .Subscribe(Observer.Create(f => // { // this.Log().Debug($"FleetSummaryByDestination observed: {f}"); // } // )); // d2 = _fleetSummaries // .ObserveOn(RxApp.MainThreadScheduler) // .Subscribe(x => // { // var lst = x.ToList(); // foreach (var f in lst) // { // this.Log().Debug($"Reason {f.Reason}, Type {f.Type}: {f.Item.Current}"); // } // } // ); // */ // } /// /// Load the fleet records from the database and push them into our source cache. /// If the data has been freshly imported, call PostProcessImportedData first. /// public void InitFromDatabase() { using var db = Locator.Current.GetService()!; // Load the full list var allFleets = db.Fleet.ToList(); _fleets.Edit(innerCache => { innerCache.Clear(); innerCache.Add(allFleets); } ); var summaries = from f in allFleets group f by f.TrueDestination into grp select new FleetSummaryByDestination { Destination = grp.Key, TotalIronium = grp.Sum(f => f.Ironium), TotalBoranium = grp.Sum(f => f.Boranium), TotalGermanium = grp.Sum(f => f.Germanium), TotalColonists = grp.Sum(f => f.Colonists) }; var cacheKeys = _fleetSummariesByDestination.Keys.ToList(); var summariesToDelete = cacheKeys.Except(summaries.Select(sum => sum.Destination)); _fleetSummariesByDestination.Edit(innerCache => { innerCache.RemoveKeys(summariesToDelete); innerCache.AddOrUpdate(summaries); } ); } /// /// Helper to fill up missing data from the original import. Tries to deduce /// missing properties using heuristics. /// public static void PostProcessImportedData() { using var db = Locator.Current.GetService()!; // Check for all cases where we're targeting another fleet instead of // a planet. Update the DB accordingly. var playerFleets = from flt in db.Fleet where flt.OwnerFileId == Game.Player.PlayerFileId select flt; Regex shipPattern = new ($@"^{Regex.Escape(Game.Player.Name)} (?.+) #\d+$"); foreach (Fleet flt in playerFleets) { if (flt.Destination != "-- ") { var trueDest = db.Fleet .FirstOrDefault(f => f.FleetName == $"{Game.Player.Name} {flt.Destination}" && f.Planet != String.Empty); flt.TrueDestination = trueDest?.Planet ?? flt.Destination; } else { flt.TrueDestination = flt.Planet; } Match m = shipPattern.Match(flt.FleetName); if (m.Success) flt.ShipTypeGuess = m.Groups[1].Value; flt.Colonists *= 100; db.Update(flt); } db.SaveChanges(); } /// /// Handle disposal of all subscriptions and dependencies. /// / protected virtual void Dispose(bool disposing) { if (disposing) { // _fleetSummariesSubscription.Dispose(); _fleetSummariesByDestination.Dispose(); _fleets.Dispose(); } } /// /// Boilerplate disposal /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }