using System; using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading.Tasks; using DesertPaintCodex.Services; using JetBrains.Annotations; namespace DesertPaintCodex.Models { public class ReactionTest : INotifyPropertyChanged, IProgress, IComparable { public enum TestState { Unset = -1, Untested, Scanning, LabNotFound, ClippedResult, GoodResult, Saved } public Reagent Reagent1 { get; } public Reagent Reagent2 { get; } private Reagent? _bufferReagent; public Reagent? BufferReagent { get => _bufferReagent; set { if (_bufferReagent == value) return; _bufferReagent = value; _recipe.Clear(); if (_bufferReagent == null) { _recipe.AddReagent(Reagent1.Name); _recipe.AddReagent(Reagent2.Name); } else { _recipe.AddReagent(_bufferReagent.Name); _recipe.AddReagent(Reagent1.Name); _recipe.AddReagent(Reagent2.Name); } NotifyPropertyChanged(nameof(BufferReagent)); NotifyPropertyChanged(nameof(CanScan)); UpdateHypotheticalColor(); } } private ClipType _clipType; public ClipType Clipped { get => _clipType; set { _clipType = value; NotifyPropertyChanged(nameof(Clipped)); } } public bool IsAllCatalysts { get; } private Reaction? _reaction; public Reaction? Reaction { get => _reaction; set { _reaction = value; NotifyPropertyChanged(nameof(Reaction)); } } private Reaction? _badReaction; public Reaction? BadReaction { get => _badReaction; set { _badReaction = value; NotifyPropertyChanged(nameof(BadReaction)); } } private int _scanProgress; public int ScanProgress { get => _scanProgress; set { _scanProgress = value; NotifyPropertyChanged(nameof(ScanProgress)); } } private TestState _state; public TestState State { get => _state; set { _state = value; NotifyPropertyChanged(nameof(State)); NotifyPropertyChanged(nameof(Requires3Way)); NotifyPropertyChanged(nameof(CanScan)); NotifyPropertyChanged(nameof(IsScanning)); NotifyPropertyChanged(nameof(HasResults)); NotifyPropertyChanged(nameof(HasReaction)); NotifyPropertyChanged(nameof(CanClear)); NotifyPropertyChanged(nameof(CanSave)); NotifyPropertyChanged(nameof(NoLab)); NotifyPropertyChanged(nameof(CanPickBuffer)); } } public bool Requires3Way => (State == TestState.ClippedResult) || IsAllCatalysts; public bool CanScan => (State is TestState.Untested or TestState.LabNotFound) || ((State == TestState.ClippedResult) && (BufferReagent != null)); public bool IsScanning => State == TestState.Scanning; public bool HasResults => (ObservedColor != null) && (State is TestState.ClippedResult or TestState.GoodResult or TestState.LabNotFound); public bool HasReaction => State is TestState.ClippedResult or TestState.GoodResult or TestState.Saved; public bool CanClear => State is TestState.ClippedResult or TestState.GoodResult or TestState.Saved; public bool CanSave => State == TestState.GoodResult; public bool NoLab => State == TestState.LabNotFound; public bool CanPickBuffer => (State == TestState.ClippedResult) || IsAllCatalysts; private PaintColor? _hypotheticalColor; public PaintColor? HypotheticalColor { get => _hypotheticalColor; set { _hypotheticalColor = value; NotifyPropertyChanged(nameof(HypotheticalColor)); } } private PaintColor? _observedColor; public PaintColor? ObservedColor { get => _observedColor; set { _observedColor = value; NotifyPropertyChanged(nameof(ObservedColor)); } } private readonly PaintRecipe _recipe = new(); public bool IsStub { get; } public ReactionTest(Reagent reagent1, Reagent reagent2, Reaction? reaction, ClipType clipType, bool isStub = false) { Reagent1 = reagent1; Reagent2 = reagent2; IsAllCatalysts = reagent1.IsCatalyst && reagent2.IsCatalyst; Clipped = clipType; Reaction = reaction; State = (reaction != null) ? TestState.Saved : (clipType == ClipType.None) ? TestState.Untested : TestState.ClippedResult; _recipe.AddReagent(reagent1.Name); _recipe.AddReagent(reagent2.Name); IsStub = isStub; UpdateHypotheticalColor(); } #region Actions public async Task StartScan() { Clipped = ClipType.None; ScanProgress = 0; Reaction = null; BadReaction = null; State = TestState.Scanning; bool foundLab = await ReactionScannerService.Instance.CaptureReactionAsync(this); if (foundLab) { ObservedColor = ReactionScannerService.Instance.RecordedColor; if (_observedColor != null) { Clipped = _observedColor.Red switch { 0 => ClipType.RedLow, 255 => ClipType.RedHigh, _ => ClipType.None } | _observedColor.Green switch { 0 => ClipType.GreenLow, 255 => ClipType.GreenHigh, _ => ClipType.None } | _observedColor.Blue switch { 0 => ClipType.BlueLow, 255 => ClipType.BlueHigh, _ => ClipType.None }; if (Clipped == ClipType.None) { State = TestState.GoodResult; Reaction = CalculateReaction(); } else { State = TestState.ClippedResult; BadReaction = CalculateReaction(); } PlayerProfile? profile = ProfileManager.CurrentProfile; profile?.SetPairClipStatus(Reagent1, Reagent2, Clipped); } } else { Debug.WriteLine("ERROR: Lab UI not found."); State = TestState.LabNotFound; } } public void CancelScan() { ReactionScannerService.Instance.CancelScan(); State = TestState.Untested; } public void MarkInert() { Reaction = new Reaction(0, 0, 0); State = TestState.GoodResult; } public void ClearReaction() { PlayerProfile? profile = ProfileManager.CurrentProfile; if (profile == null) return; profile.Reactions.Remove(Reagent1, Reagent2); profile.SetPairClipStatus(Reagent1, Reagent2, ClipType.None); if (State == TestState.Saved) { profile.Save(); } Reaction = null; BadReaction = null; Clipped = ClipType.None; State = TestState.Untested; } public void SaveReaction() { PlayerProfile? profile = ProfileManager.CurrentProfile; if (profile == null) return; profile.Reactions.Set(Reagent1, Reagent2, Reaction); profile.Save(); State = TestState.Saved; } #endregion #region Internals private Reaction? CalculateReaction() { if (ProfileManager.CurrentProfile == null) return null; if (HypotheticalColor == null) return null; if (ObservedColor == null) return null; if (BufferReagent != null) { return ReactionScannerService.Calculate3WayReaction(ProfileManager.CurrentProfile, HypotheticalColor, ObservedColor, BufferReagent, Reagent1, Reagent2); } return ReactionScannerService.CalculateReaction(HypotheticalColor, ObservedColor); } private void UpdateHypotheticalColor() { HypotheticalColor = null; HypotheticalColor = (IsAllCatalysts && (BufferReagent == null)) ? null : _recipe.BaseColor; } #endregion #region Interface Implementations public event PropertyChangedEventHandler? PropertyChanged; [NotifyPropertyChangedInvocator] private void NotifyPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public void Report(float value) { ScanProgress = (int)Math.Round(value * 100); } #endregion public int CompareTo(ReactionTest? other) { if (other == null) return 1; if (Clipped == ClipType.None) { if (other.Clipped != ClipType.None) return -1; } else if (other.Clipped == ClipType.None) return 1; if (IsAllCatalysts) { if (!other.IsAllCatalysts) return 1; } else if (other.IsAllCatalysts) return -1; return string.CompareOrdinal(Reagent1.Name, other.Reagent1.Name) switch { < 0 => -1, > 0 => 1, _ => string.CompareOrdinal(Reagent2.Name, other.Reagent2.Name) }; } } }