Files @ 6a6817b17a06
Branch filter:

Location: ATITD-Tools/Desert-Paint-Codex/ViewModels/SimulatorViewModel.cs - annotation

Jason Maltzen
Simulator view updates: new warning when the recipe is below minimum concentration. Add the missing reactions to the warning about missing reactions. Show the current saved recipe for a color, and allow replacing/saving the current simulated recipe as the recipe for that color.
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
6a6817b17a06
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
6a6817b17a06
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
6a6817b17a06
40eaee10ae56
6a6817b17a06
6a6817b17a06
6a6817b17a06
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
40eaee10ae56
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 GeneratorRecipe? _existingRecipe;
        public GeneratorRecipe? 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<Reagent> Reagents { get; } = new();
        public ObservableCollection<Reagent> ActiveReagents { get; } = new();
        public ObservableCollection<RecipeItem> RecipeItems { get; } = new();

        
        private readonly PaintRecipe _currentRecipe = new();
        private PaintColor _tempColor = new PaintColor(0, 0,0);

        public SimulatorViewModel()
        {
            List<string> 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}.");

                    GeneratorRecipe? existingRecipe = ExistingRecipe;
                    if (existingRecipe == null)
                    {
                        _tempColor.Set(colorName, _currentRecipe.ReactedColor);
                        existingRecipe = new GeneratorRecipe(_tempColor);
                    }
                    else
                    {
                        existingRecipe.Color.Set(colorName, _currentRecipe.ReactedColor);
                    }
                    existingRecipe.DraftRecipe(paintRecipe);
                    ExistingRecipe = null;
                    ExistingRecipe = existingRecipe;
                }
                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<string> missingReactionsStrings = new List<string>(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<Reagent> 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);
            }
        }
    }
}