using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Reactive.Linq; using Avalonia; using Avalonia.Input.Platform; using DesertPaintCodex.Models; using DesertPaintCodex.Services; using DynamicData; using DynamicData.Binding; using ReactiveUI; namespace DesertPaintCodex.ViewModels { public class SimulatorViewModel : ViewModelBase { private RecipeLibraryViewModel? _recipeLibraryVM; private ViewModelBase? _recipeLibraryActivity; public ViewModelBase? RecipeLibraryActivity { get => _recipeLibraryActivity; private set => this.RaiseAndSetIfChanged(ref _recipeLibraryActivity, value); } private PaintColor? _paintColor; public PaintColor? PaintColor { get => _paintColor; set => this.RaiseAndSetIfChanged(ref _paintColor, value); } // Stealing the recipe view from the paint generator private PaintRecipe? _existingRecipe; public PaintRecipe? ExistingRecipe { get => _existingRecipe; private set => this.RaiseAndSetIfChanged(ref _existingRecipe, value); } private bool _hasMissingReactions; public bool HasMissingReactions { get => _hasMissingReactions; set => this.RaiseAndSetIfChanged(ref _hasMissingReactions, value); } private bool _isGoodRecipe; public bool IsGoodRecipe { get => _isGoodRecipe; set => this.RaiseAndSetIfChanged(ref _isGoodRecipe, value); } private bool _isValidConcentration; public bool IsValidConcentration { get => _isValidConcentration; set => this.RaiseAndSetIfChanged(ref _isValidConcentration, value); } private string _missingReactionList = string.Empty; public string MissingReactionList { get => _missingReactionList; set => this.RaiseAndSetIfChanged(ref _missingReactionList, value); } public ObservableCollection Reagents { get; } = new(); public ObservableCollection ActiveReagents { get; } = new(); public ObservableCollection RecipeItems { get; } = new(); private readonly PaintRecipe _currentRecipe = new(); private PaintColor _tempColor = new PaintColor(0, 0,0); public SimulatorViewModel() { List reagentNames = ReagentService.Names; for (int i = 0; i < reagentNames.Count; i++) { Reagents.Add(ReagentService.GetReagent(reagentNames[i])); } ActiveReagents.CollectionChanged += OnActiveReagentsChanged; RecipeItems .ToObservableChangeSet() .AutoRefresh(item => item.Quantity) .Subscribe(_ => Refresh()); } public async void CopyToClipboard() { IClipboard clipboard = Application.Current.Clipboard; await clipboard.SetTextAsync(_currentRecipe.ToString()); } public void MoveItemUp(RecipeItem item) { int pos = RecipeItems.IndexOf(item); if (pos <= 0) return; RecipeItems.RemoveAt(pos); RecipeItems.Insert(pos - 1, item); Refresh(); } public void MoveItemDown(RecipeItem item) { int pos = RecipeItems.IndexOf(item); if ((pos < 0) || (pos >= RecipeItems.Count - 1)) return; RecipeItems.RemoveAt(pos); RecipeItems.Insert(pos + 1, item); Refresh(); } private void ShowRecipeLibrary() { _recipeLibraryVM = new RecipeLibraryViewModel(); Observable.Merge(_recipeLibraryVM.Ok, _recipeLibraryVM.Cancel).Subscribe(_ => LoadRecipe()); RecipeLibraryActivity = _recipeLibraryVM; } // Show the list of recipes and allow the user to pick one to load public void LoadRecipe() { // TODO: show the list of recipes } public void SelectRecipeToLoad(PaintRecipe recipe) { // 1: Clear the current recipe _currentRecipe.Clear(); RecipeItems.Clear(); // 2: Set the recipe items from the loaded recipe foreach (PaintRecipe.ReagentQuantity reagentQuantity in recipe.Reagents) { Reagent reagent = ReagentService.GetReagent(reagentQuantity.Name); if (reagent != null) { RecipeItems.Add(new RecipeItem(reagent, reagentQuantity.Quantity)); } } UpdateRecipe(); } // Replace the recipe for a color with the current recipe public void ReplaceRecipe() { if (IsGoodRecipe && IsValidConcentration) { if (ProfileManager.CurrentProfile == null) return; string colorName = PaletteService.FindNearest(_currentRecipe.ReactedColor); _tempColor.Set(colorName, _currentRecipe.ReactedColor); ProfileManager.CurrentProfile.Recipes[colorName].CopyFrom(_currentRecipe); UpdateExistingRecipe(); } } private void UpdateExistingRecipe() { if (IsGoodRecipe) { string colorName = PaletteService.FindNearest(_currentRecipe.ReactedColor); if ((ProfileManager.CurrentProfile != null) && (ProfileManager.CurrentProfile.Recipes.TryGetValue(colorName, out PaintRecipe? paintRecipe))) { System.Diagnostics.Debug.WriteLine($"Setting existing recipe for {colorName}."); PaintRecipe? existingRecipe = ExistingRecipe; ExistingRecipe = null; ExistingRecipe = _currentRecipe; } else { ExistingRecipe = null; } } else { ExistingRecipe = null; } } private void UpdateRecipe() { _currentRecipe.Clear(); foreach (RecipeItem entry in RecipeItems) { if (!entry.Unused) { _currentRecipe.AddReagent(entry.Reagent.Name, entry.Quantity); } } PaintColor = null; // TODO: Find a better way to kick the paint swatch when reassigning color from the same ref. PaintColor = _currentRecipe.ReactedColor; HasMissingReactions = _currentRecipe.HasMissingReactions(); if (HasMissingReactions) { List<(string, string)> missingReactions = _currentRecipe.MissingReactionPairs(); List missingReactionsStrings = new List(missingReactions.Count); foreach ((string reagent1, string reagent2) in missingReactions) { missingReactionsStrings.Add($"{reagent1}+{reagent2}"); } MissingReactionList = string.Join(", ", missingReactionsStrings); } IsGoodRecipe = !HasMissingReactions && _currentRecipe.IsValidForConcentration(10); IsValidConcentration = _currentRecipe.IsValidForConcentration(10); UpdateExistingRecipe(); } private void OnActiveReagentsChanged(object? sender, NotifyCollectionChangedEventArgs e) { HashSet unmatchedReagents = new(ActiveReagents); for (int i = RecipeItems.Count - 1; i >= 0; i--) { bool found = false; foreach (Reagent reagent in ActiveReagents) { if (reagent == RecipeItems[i].Reagent) { unmatchedReagents.Remove(reagent); found = true; break; } } if (!found) RecipeItems.RemoveAt(i); } foreach (Reagent reagent in unmatchedReagents) { RecipeItems.Add(new RecipeItem(reagent, 1)); } Refresh(); } private void Refresh() { UpdateFlags(); UpdateRecipe(); } private void UpdateFlags() { int activeIngredients = 0; for (int i = 0; i < RecipeItems.Count; i++) { RecipeItem item = RecipeItems[i]; item.First = i == 0; item.Last = i == RecipeItems.Count - 1; if (item.Quantity > 0) { activeIngredients++; } item.Unused = ((activeIngredients > 5) && item.Reagent.IsCatalyst) || (item.Quantity == 0); } } } }