Files @ d036e3a47323
Branch filter:

Location: ATITD-Tools/Desert-Paint-Codex/Models/ReactionTest.cs

Malkyne
Merge
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<float>, IComparable<ReactionTest>
    {
        public enum TestState
        {
            Unset = -1,
            Untested,
            Scanning,
            LabNotFound,
            ClippedResult,
            GoodResult,
            Saved
        }

        public Reagent Reagent1 { get; }
        public Reagent Reagent2 { get; }

        private Reagent? _bufferReagentFirst;
        public Reagent? BufferReagentFirst
        {
            get => _bufferReagentFirst;
            set
            {
                if (_bufferReagentFirst == value) return;
                _bufferReagentFirst = value;
                
                UpdateRecipe();
                
                NotifyPropertyChanged(nameof(BufferReagentFirst));
                NotifyPropertyChanged(nameof(CanScan));
            }
        }
        
        private Reagent? _bufferReagentLast;
        public Reagent? BufferReagentLast
        {
            get => _bufferReagentLast;
            set
            {
                if (_bufferReagentLast == value) return;
                _bufferReagentLast = value;

                UpdateRecipe();

                NotifyPropertyChanged(nameof(BufferReagentLast));
                NotifyPropertyChanged(nameof(CanScan));
            }
        }

        private bool _useFirstBuffer;

        public bool UseFirstBuffer
        {
            get => _useFirstBuffer;
            set
            {
                _useFirstBuffer = value;
                UpdateRecipe();
                NotifyPropertyChanged(nameof(UseFirstBuffer));
                NotifyPropertyChanged(nameof(CanScan));
                NotifyPropertyChanged(nameof(ShowFirstBuffer));
                NotifyPropertyChanged(nameof(ShowLastBuffer));
            }
        }

        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));
                NotifyPropertyChanged(nameof(ShowFirstBuffer));
                NotifyPropertyChanged(nameof(ShowLastBuffer));
            }
        }

        public bool Requires3Way =>
            (State == TestState.ClippedResult) || IsAllCatalysts;

        public bool CanScan => (State is TestState.Untested or TestState.LabNotFound) || ((State == TestState.ClippedResult) && BufferIsSelected);
        
        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;

        public bool ShowFirstBuffer => Requires3Way && UseFirstBuffer;

        public bool ShowLastBuffer => Requires3Way && !UseFirstBuffer;


        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;
            UseFirstBuffer = true;
            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)
                {
                    // Handle the normal case.
                    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 (_useFirstBuffer)
            {
                if (BufferReagentFirst != null)
                {
                    return ReactionScannerService.Calculate3WayReaction(ProfileManager.CurrentProfile, HypotheticalColor,
                        ObservedColor, BufferReagentFirst, Reagent1, Reagent2, true);
                }
            }
            else
            {
                if (BufferReagentLast != null)
                {
                    return ReactionScannerService.Calculate3WayReaction(ProfileManager.CurrentProfile, HypotheticalColor,
                        ObservedColor, Reagent1, Reagent2, BufferReagentLast, false);
                }
            }
            
            return ReactionScannerService.CalculateReaction(HypotheticalColor, ObservedColor);
        }


        private void UpdateHypotheticalColor()
        {
            HypotheticalColor = null;
            HypotheticalColor = (IsAllCatalysts && !BufferIsSelected) ? null : _recipe.BaseColor;
        }

        private bool BufferIsSelected => UseFirstBuffer ? (BufferReagentFirst != null) : (BufferReagentLast != null);


        private void UpdateRecipe()
        {
            _recipe.Clear();
            if (_useFirstBuffer)
            {
                if (_bufferReagentFirst == null)
                {
                    _recipe.AddReagent(Reagent1.Name);
                    _recipe.AddReagent(Reagent2.Name);
                }
                else
                {
                    _recipe.AddReagent(_bufferReagentFirst.Name);
                    _recipe.AddReagent(Reagent1.Name);
                    _recipe.AddReagent(Reagent2.Name);
                } 
            }
            else
            {
                if (_bufferReagentLast == null)
                {
                    _recipe.AddReagent(Reagent1.Name);
                    _recipe.AddReagent(Reagent2.Name);
                }
                else
                {
                    _recipe.AddReagent(Reagent1.Name);
                    _recipe.AddReagent(Reagent2.Name);
                    _recipe.AddReagent(_bufferReagentLast.Name);
                }                
            }
            
            UpdateHypotheticalColor();
        }
        
        #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)
            };
        }
    }
}