using System;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Localization;


namespace Content.Client.Atmos.Monitor.UI.Widgets
{
    [GenerateTypedNameReferences]
    public sealed partial class ThresholdControl : BoxContainer
    {
        private AtmosAlarmThreshold _threshold;
        private AtmosMonitorThresholdType _type;
        private Gas? _gas;

        public event Action<AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? ThresholdDataChanged;

        private CollapsibleHeading _name => CName;
        private CheckBox _ignore => CIgnore;
        private BoxContainer _dangerBounds => CDangerBounds;
        private BoxContainer _warningBounds => CWarningBounds;
        private ThresholdBoundControl _upperBoundControl;
        private ThresholdBoundControl _lowerBoundControl;
        private ThresholdBoundControl _upperWarningBoundControl;
        private ThresholdBoundControl _lowerWarningBoundControl;

        // i have played myself by making threshold values nullable to
        // indicate validity/disabled status, with several layers of side effect
        // dependent on the other three values when you change one :HECK:
        public ThresholdControl(string name, AtmosAlarmThreshold threshold, AtmosMonitorThresholdType type, Gas? gas = null, float modifier = 1)
        {
            RobustXamlLoader.Load(this);

            _threshold = threshold;
            _type = type;
            _gas = gas;

            _name.Title = name;

            // i miss rust macros

            _upperBoundControl = new ThresholdBoundControl("upper-bound", _threshold.UpperBound, modifier);
            _upperBoundControl.OnBoundChanged += value =>
            {
                // a lot of threshold logic is baked into the properties,
                // so setting this just returns if a change occurred or not
                _threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Upper, value);
                return _threshold.UpperBound;
            };
            _upperBoundControl.OnBoundEnabled += () =>
            {
                var value = 0f;

                if (_threshold.LowerWarningBound != null)
                    value = (float) _threshold.LowerWarningBound + 0.1f;
                else if (_threshold.LowerBound != null)
                    value = (float) _threshold.LowerBound + 0.1f;

                return value;
            };
            _upperBoundControl.OnValidBoundChanged += () =>
            {
                ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
            };
            _dangerBounds.AddChild(_upperBoundControl);

            _lowerBoundControl = new ThresholdBoundControl("lower-bound", _threshold.LowerBound, modifier);
            _lowerBoundControl.OnBoundChanged += value =>
            {
                _threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Lower, value);
                return _threshold.LowerBound;
            };
            _lowerBoundControl.OnBoundEnabled += () =>
            {
                var value = 0f;

                if (_threshold.UpperWarningBound != null)
                    value = (float) _threshold.UpperWarningBound - 0.1f;
                else if (_threshold.UpperBound != null)
                    value = (float) _threshold.UpperBound - 0.1f;

                return value;
            };
            _lowerBoundControl.OnValidBoundChanged += () =>
                ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
            _dangerBounds.AddChild(_lowerBoundControl);

            _upperWarningBoundControl = new ThresholdBoundControl("upper-warning-bound", _threshold.UpperWarningBound, modifier);
            _upperWarningBoundControl.OnBoundChanged += value =>
            {
                _threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Upper, value);
                return _threshold.UpperWarningBound;
            };
            _upperWarningBoundControl.OnBoundEnabled += () =>
            {
                var value = 0f;

                if (_threshold.LowerWarningBound != null)
                    value = (float) _threshold.LowerWarningBound + 0.1f;
                else if (_threshold.LowerBound != null)
                    value = (float) _threshold.LowerBound + 0.1f;

                return value;
            };
            _upperWarningBoundControl.OnValidBoundChanged += () =>
                ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
            _warningBounds.AddChild(_upperWarningBoundControl);

            _lowerWarningBoundControl = new ThresholdBoundControl("lower-warning-bound", _threshold.LowerWarningBound, modifier);
            _lowerWarningBoundControl.OnBoundChanged += value =>
            {
                _threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Lower, value);
                return _threshold.LowerWarningBound;
            };
            _lowerWarningBoundControl.OnBoundEnabled += () =>
            {
                var value = 0f;

                if (_threshold.UpperWarningBound != null)
                    value = (float) _threshold.UpperWarningBound - 0.1f;
                else if (_threshold.UpperBound != null)
                    value = (float) _threshold.UpperBound - 0.1f;

                return value;
            };
            _lowerWarningBoundControl.OnValidBoundChanged += () =>
                ThresholdDataChanged!.Invoke(_type, _threshold, _gas);

            _warningBounds.AddChild(_lowerWarningBoundControl);

            _ignore.OnToggled += args =>
            {
                _threshold.Ignore = args.Pressed;
                ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
            };
            _ignore.Pressed = _threshold.Ignore;
        }

        public void UpdateThresholdData(AtmosAlarmThreshold threshold)
        {
            _upperBoundControl.SetValue(threshold.UpperBound);
            _lowerBoundControl.SetValue(threshold.LowerBound);
            _upperWarningBoundControl.SetValue(threshold.UpperWarningBound);
            _lowerWarningBoundControl.SetValue(threshold.LowerWarningBound);
            _ignore.Pressed = threshold.Ignore;
        }


        private sealed class ThresholdBoundControl : BoxContainer
        {
            // raw values to use in thresholds, prefer these
            // over directly setting Modified(Value/LastValue)
            // when working with the FloatSpinBox
            private float? _value;
            private float _lastValue;

            // convenience thing for getting multiplied values
            // and also setting value to a usable value
            private float? ModifiedValue
            {
                get => _value * _modifier;
                set => _value = value / _modifier;
            }

            private float ModifiedLastValue
            {
                get => _lastValue * _modifier;
                set => _lastValue = value / _modifier;
            }

            private float _modifier;

            private FloatSpinBox _bound;
            private CheckBox _boundEnabled;

            public event Action? OnValidBoundChanged;
            public Func<float?, float?>? OnBoundChanged;
            public Func<float>? OnBoundEnabled;

            public void SetValue(float? value)
            {
                _value = value;

                if (_value == null)
                {
                    _boundEnabled.Pressed = false;
                    _bound.Value = 0;
                }
                else
                {
                    _boundEnabled.Pressed = true;
                    _bound.Value = (float) ModifiedValue!;
                }
            }

            // Modifier indicates what factor the value should be multiplied by.
            // Mostly useful to convert tiny decimals to human-readable 'percentages'
            // (yes it's still a float, but floatspinbox unfucks that)
            public ThresholdBoundControl(string name, float? value, float modifier = 1)
            {
                _modifier = modifier > 0 ? modifier : 1;
                _value = value;

                this.HorizontalExpand = true;
                this.Orientation = LayoutOrientation.Vertical;

                this.AddChild(new Label { Text = Loc.GetString($"air-alarm-ui-thresholds-{name}") });
                _bound = new FloatSpinBox(.01f, 2);
                this.AddChild(_bound);

                _boundEnabled = new CheckBox
                {
                    Text = Loc.GetString("Enabled")
                };
                this.AddChild(_boundEnabled);

                _bound.Value = ModifiedValue ?? 0;
                _lastValue = _value ?? 0;
                _boundEnabled.Pressed = _value != null;

                _bound.OnValueChanged += ChangeValue;
                _bound.IsValid += ValidateThreshold;
                _boundEnabled.OnToggled += ToggleBound;
            }

            private void ChangeValue(FloatSpinBox.FloatSpinBoxEventArgs args)
            {
                // ensure that the value in the spinbox is transformed
                ModifiedValue = args.Value;
                // set the value in the scope above
                var value = OnBoundChanged!(_value);
                // is the value not null, or has it changed?
                if (value != null || value != _lastValue)
                {
                    _value = value;
                    _lastValue = (float) value!;
                    OnValidBoundChanged!.Invoke();
                }
                // otherwise, just set it to the last known value
                else
                {
                    _value = _lastValue;
                    _bound.Value = ModifiedLastValue;
                }
            }

            private void ToggleBound(BaseButton.ButtonToggledEventArgs args)
            {
                if (args.Pressed)
                {
                    var value = OnBoundChanged!(_lastValue);

                    if (value != _lastValue)
                    {
                        value = OnBoundChanged!(OnBoundEnabled!());

                        if (value == null || value < 0)
                        {
                            // TODO: Improve UX here, this is ass
                            // basically this implies that the bound
                            // you currently have is too aggressive
                            // for the other set of values, so a
                            // default value (which is +/-0.1) can't
                            // be used
                            _boundEnabled.Pressed = false;
                            return;
                        }
                    }

                    _value = value;

                    _bound.Value = (float) ModifiedValue!;
                    _lastValue = (float) _value;
                }
                else
                {
                    _value = null;
                    _bound.Value = 0f;
                    OnBoundChanged!(_value);
                }

                OnValidBoundChanged!.Invoke();
            }

            private bool ValidateThreshold(float value) => (_value != null) && (value >= 0);

        }
    }
}
