using System;
using System.Globalization;
using System.IO;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
    /// 
    /// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
    /// 
    public class NumericUpDown : TemplatedControl
    {
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty AllowSpinProperty =
            ButtonSpinner.AllowSpinProperty.AddOwner();
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty ButtonSpinnerLocationProperty =
            ButtonSpinner.ButtonSpinnerLocationProperty.AddOwner();
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty ShowButtonSpinnerProperty =
            ButtonSpinner.ShowButtonSpinnerProperty.AddOwner();
        /// 
        /// Defines the  property.
        /// 
        public static readonly DirectProperty ClipValueToMinMaxProperty =
            AvaloniaProperty.RegisterDirect(nameof(ClipValueToMinMax),
                updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
        /// 
        /// Defines the  property.
        /// 
        public static readonly DirectProperty CultureInfoProperty =
            AvaloniaProperty.RegisterDirect(nameof(CultureInfo), o => o.CultureInfo,
                (o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty FormatStringProperty =
            AvaloniaProperty.Register(nameof(FormatString), string.Empty);
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty IncrementProperty =
            AvaloniaProperty.Register(nameof(Increment), 1.0d, validate: OnCoerceIncrement);
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty IsReadOnlyProperty =
            AvaloniaProperty.Register(nameof(IsReadOnly));
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty MaximumProperty =
            AvaloniaProperty.Register(nameof(Maximum), double.MaxValue, validate: OnCoerceMaximum);
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty MinimumProperty =
            AvaloniaProperty.Register(nameof(Minimum), double.MinValue, validate: OnCoerceMinimum);
        /// 
        /// Defines the  property.
        /// 
        public static readonly DirectProperty ParsingNumberStyleProperty =
            AvaloniaProperty.RegisterDirect(nameof(ParsingNumberStyle),
                updown => updown.ParsingNumberStyle, (updown, style) => updown.ParsingNumberStyle = style);
        /// 
        /// Defines the  property.
        /// 
        public static readonly DirectProperty TextProperty =
            AvaloniaProperty.RegisterDirect(nameof(Text), o => o.Text, (o, v) => o.Text = v,
                defaultBindingMode: BindingMode.TwoWay);
        /// 
        /// Defines the  property.
        /// 
        public static readonly DirectProperty ValueProperty =
            AvaloniaProperty.RegisterDirect(nameof(Value), updown => updown.Value,
                (updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay);
        /// 
        /// Defines the  property.
        /// 
        public static readonly StyledProperty WatermarkProperty =
            AvaloniaProperty.Register(nameof(Watermark));
        private IDisposable _textBoxTextChangedSubscription;
        private double _value;
        private string _text;
        private bool _internalValueSet;
        private bool _clipValueToMinMax;
        private bool _isSyncingTextAndValueProperties;
        private bool _isTextChangedFromUI;
        private CultureInfo _cultureInfo;
        private NumberStyles _parsingNumberStyle = NumberStyles.Any;
        
        /// 
        /// Gets the Spinner template part.
        /// 
        private Spinner Spinner { get; set; }
        /// 
        /// Gets the TextBox template part.
        /// 
        private TextBox TextBox { get; set; }
        /// 
        /// Gets or sets the ability to perform increment/decrement operations via the keyboard, button spinners, or mouse wheel.
        /// 
        public bool AllowSpin
        {
            get { return GetValue(AllowSpinProperty); }
            set { SetValue(AllowSpinProperty, value); }
        }
        /// 
        /// Gets or sets current location of the .
        /// 
        public Location ButtonSpinnerLocation
        {
            get { return GetValue(ButtonSpinnerLocationProperty); }
            set { SetValue(ButtonSpinnerLocationProperty, value); }
        }
        /// 
        /// Gets or sets a value indicating whether the spin buttons should be shown.
        /// 
        public bool ShowButtonSpinner
        {
            get { return GetValue(ShowButtonSpinnerProperty); }
            set { SetValue(ShowButtonSpinnerProperty, value); }
        }
        /// 
        /// Gets or sets if the value should be clipped when minimum/maximum is reached.
        /// 
        public bool ClipValueToMinMax
        {
            get { return _clipValueToMinMax; }
            set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); }
        }
        /// 
        /// Gets or sets the current CultureInfo.
        /// 
        public CultureInfo CultureInfo
        {
            get { return _cultureInfo; }
            set { SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); }
        }
        /// 
        /// Gets or sets the display format of the .
        /// 
        public string FormatString
        {
            get { return GetValue(FormatStringProperty); }
            set { SetValue(FormatStringProperty, value); }
        }
        /// 
        /// Gets or sets the amount in which to increment the .
        /// 
        public double Increment
        {
            get { return GetValue(IncrementProperty); }
            set { SetValue(IncrementProperty, value); }
        }
        /// 
        /// Gets or sets if the control is read only.
        /// 
        public bool IsReadOnly
        {
            get { return GetValue(IsReadOnlyProperty); }
            set { SetValue(IsReadOnlyProperty, value); }
        }
        /// 
        /// Gets or sets the maximum allowed value.
        /// 
        public double Maximum
        {
            get { return GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }
        /// 
        /// Gets or sets the minimum allowed value.
        /// 
        public double Minimum
        {
            get { return GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }
        /// 
        /// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
        /// 
        public NumberStyles ParsingNumberStyle
        {
            get { return _parsingNumberStyle; }
            set { SetAndRaise(ParsingNumberStyleProperty, ref _parsingNumberStyle, value); }
        }
        /// 
        /// Gets or sets the formatted string representation of the value.
        /// 
        public string Text
        {
            get { return _text; }
            set { SetAndRaise(TextProperty, ref _text, value); }
        }
        /// 
        /// Gets or sets the value.
        /// 
        public double Value
        {
            get { return _value; }
            set
            {
                value = OnCoerceValue(value);
                SetAndRaise(ValueProperty, ref _value, value);
            }
        }
        /// 
        /// Gets or sets the object to use as a watermark if the  is null.
        /// 
        public string Watermark
        {
            get { return GetValue(WatermarkProperty); }
            set { SetValue(WatermarkProperty, value); }
        }
        /// 
        /// Initializes new instance of  class.
        /// 
        public NumericUpDown()
        {
            Initialized += (sender, e) =>
            {
                if (!_internalValueSet && IsInitialized)
                {
                    SyncTextAndValueProperties(false, null, true);
                }
                SetValidSpinDirection();
            };
        }
        /// 
        /// Initializes static members of the  class.
        /// 
        static NumericUpDown()
        {
            CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
            FormatStringProperty.Changed.Subscribe(FormatStringChanged);
            IncrementProperty.Changed.Subscribe(IncrementChanged);
            IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged);
            MaximumProperty.Changed.Subscribe(OnMaximumChanged);
            MinimumProperty.Changed.Subscribe(OnMinimumChanged);
            TextProperty.Changed.Subscribe(OnTextChanged);
            ValueProperty.Changed.Subscribe(OnValueChanged);
        }
        /// 
        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
        {
            if (TextBox != null)
            {
                TextBox.PointerPressed -= TextBoxOnPointerPressed;
                _textBoxTextChangedSubscription?.Dispose();
            }
            TextBox = e.NameScope.Find("PART_TextBox");
            if (TextBox != null)
            {
                TextBox.Text = Text;
                TextBox.PointerPressed += TextBoxOnPointerPressed;
                _textBoxTextChangedSubscription = TextBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBoxOnTextChanged());
            }
            if (Spinner != null)
            {
                Spinner.Spin -= OnSpinnerSpin;
            }
            Spinner = e.NameScope.Find("PART_Spinner");
            if (Spinner != null)
            {
                Spinner.Spin += OnSpinnerSpin;
            }
            SetValidSpinDirection();
        }
        /// 
        protected override void OnKeyDown(KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Enter:
                    var commitSuccess = CommitInput();
                    e.Handled = !commitSuccess;
                    break;
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void OnCultureInfoChanged(CultureInfo oldValue, CultureInfo newValue)
        {
            if (IsInitialized)
            {
                SyncTextAndValueProperties(false, null);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void OnFormatStringChanged(string oldValue, string newValue)
        {
            if (IsInitialized)
            {
                SyncTextAndValueProperties(false, null);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void OnIncrementChanged(double oldValue, double newValue)
        {
            if (IsInitialized)
            {
                SetValidSpinDirection();
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void OnIsReadOnlyChanged(bool oldValue, bool newValue)
        {
            SetValidSpinDirection();
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void OnMaximumChanged(double oldValue, double newValue)
        {
            if (IsInitialized)
            {
                SetValidSpinDirection();
            }
            if (ClipValueToMinMax)
            {
                Value = MathUtilities.Clamp(Value, Minimum, Maximum);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void OnMinimumChanged(double oldValue, double newValue)
        {
            if (IsInitialized)
            {
                SetValidSpinDirection();
            }
            if (ClipValueToMinMax)
            {
                Value = MathUtilities.Clamp(Value, Minimum, Maximum);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void OnTextChanged(string oldValue, string newValue)
        {
            if (IsInitialized)
            {
                SyncTextAndValueProperties(true, Text);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void OnValueChanged(double oldValue, double newValue)
        {
            if (!_internalValueSet && IsInitialized)
            {
                SyncTextAndValueProperties(false, null, true);
            }
            SetValidSpinDirection();
            RaiseValueChangedEvent(oldValue, newValue);
        }
        /// 
        /// Called when the  property has to be coerced.
        /// 
        /// The value.
        protected virtual double OnCoerceIncrement(double baseValue)
        {
            return baseValue;
        }
        /// 
        /// Called when the  property has to be coerced.
        /// 
        /// The value.
        protected virtual double OnCoerceMaximum(double baseValue)
        {
            return Math.Max(baseValue, Minimum);
        }
        /// 
        /// Called when the  property has to be coerced.
        /// 
        /// The value.
        protected virtual double OnCoerceMinimum(double baseValue)
        {
            return Math.Min(baseValue, Maximum);
        }
        /// 
        /// Called when the  property has to be coerced.
        /// 
        /// The value.
        protected virtual double OnCoerceValue(double baseValue)
        {
            return baseValue;
        }
        /// 
        /// Raises the OnSpin event when spinning is initiated by the end-user.
        /// 
        /// The event args.
        protected virtual void OnSpin(SpinEventArgs e)
        {
            if (e == null)
            {
                throw new ArgumentNullException(nameof(e));
            }
            var handler = Spinned;
            handler?.Invoke(this, e);
            if (e.Direction == SpinDirection.Increase)
            {
                DoIncrement();
            }
            else
            {
                DoDecrement();
            }
        }
        /// 
        /// Raises the  event.
        /// 
        /// The old value.
        /// The new value.
        protected virtual void RaiseValueChangedEvent(double oldValue, double newValue)
        {
            var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue);
            RaiseEvent(e);
        }
        /// 
        /// Converts the formatted text to a value.
        /// 
        private double ConvertTextToValue(string text)
        {
            double result = 0;
            if (string.IsNullOrEmpty(text))
            {
                return result;
            }
            // Since the conversion from Value to text using a FormatString may not be parsable,
            // we verify that the already existing text is not the exact same value.
            var currentValueText = ConvertValueToText();
            if (Equals(currentValueText, text))
            {
                return Value;
            }
            result = ConvertTextToValueCore(currentValueText, text);
            if (ClipValueToMinMax)
            {
                return MathUtilities.Clamp(result, Minimum, Maximum);
            }
            ValidateMinMax(result);
            return result;
        }
        /// 
        /// Converts the value to formatted text.
        /// 
        /// 
        private string ConvertValueToText()
        {
            //Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
            if (FormatString.Contains("{0"))
            {
                return string.Format(CultureInfo, FormatString, Value);
            }
            return Value.ToString(FormatString, CultureInfo);
        }
        /// 
        /// Called by OnSpin when the spin direction is SpinDirection.Increase.
        /// 
        private void OnIncrement()
        {
            var result = Value + Increment;
            Value = MathUtilities.Clamp(result, Minimum, Maximum);
        }
        /// 
        /// Called by OnSpin when the spin direction is SpinDirection.Decrease.
        /// 
        private void OnDecrement()
        {
            var result = Value - Increment;
            Value = MathUtilities.Clamp(result, Minimum, Maximum);
        }
        /// 
        /// Sets the valid spin directions.
        /// 
        private void SetValidSpinDirection()
        {
            var validDirections = ValidSpinDirections.None;
            // Zero increment always prevents spin.
            if (Increment != 0 && !IsReadOnly)
            {
                if (Value < Maximum)
                {
                    validDirections = validDirections | ValidSpinDirections.Increase;
                }
                if (Value > Minimum)
                {
                    validDirections = validDirections | ValidSpinDirections.Decrease;
                }
            }
            if (Spinner != null)
            {
                Spinner.ValidSpinDirection = validDirections;
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The event args.
        private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (e.Sender is NumericUpDown upDown)
            {
                var oldValue = (CultureInfo)e.OldValue;
                var newValue = (CultureInfo)e.NewValue;
                upDown.OnCultureInfoChanged(oldValue, newValue);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The event args.
        private static void IncrementChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (e.Sender is NumericUpDown upDown)
            {
                var oldValue = (double)e.OldValue;
                var newValue = (double)e.NewValue;
                upDown.OnIncrementChanged(oldValue, newValue);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The event args.
        private static void FormatStringChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (e.Sender is NumericUpDown upDown)
            {
                var oldValue = (string)e.OldValue;
                var newValue = (string)e.NewValue;
                upDown.OnFormatStringChanged(oldValue, newValue);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The event args.
        private static void OnIsReadOnlyChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (e.Sender is NumericUpDown upDown)
            {
                var oldValue = (bool)e.OldValue;
                var newValue = (bool)e.NewValue;
                upDown.OnIsReadOnlyChanged(oldValue, newValue);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The event args.
        private static void OnMaximumChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (e.Sender is NumericUpDown upDown)
            {
                var oldValue = (double)e.OldValue;
                var newValue = (double)e.NewValue;
                upDown.OnMaximumChanged(oldValue, newValue);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The event args.
        private static void OnMinimumChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (e.Sender is NumericUpDown upDown)
            {
                var oldValue = (double)e.OldValue;
                var newValue = (double)e.NewValue;
                upDown.OnMinimumChanged(oldValue, newValue);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The event args.
        private static void OnTextChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (e.Sender is NumericUpDown upDown)
            {
                var oldValue = (string)e.OldValue;
                var newValue = (string)e.NewValue;
                upDown.OnTextChanged(oldValue, newValue);
            }
        }
        /// 
        /// Called when the  property value changed.
        /// 
        /// The event args.
        private static void OnValueChanged(AvaloniaPropertyChangedEventArgs e)
        {
            if (e.Sender is NumericUpDown upDown)
            {
                var oldValue = (double)e.OldValue;
                var newValue = (double)e.NewValue;
                upDown.OnValueChanged(oldValue, newValue);
            }
        }
        private void SetValueInternal(double value)
        {
            _internalValueSet = true;
            try
            {
                Value = value;
            }
            finally
            {
                _internalValueSet = false;
            }
        }
        private static double OnCoerceMaximum(NumericUpDown upDown, double value)
        {
            return upDown.OnCoerceMaximum(value);
        }
        private static double OnCoerceMinimum(NumericUpDown upDown, double value)
        {
            return upDown.OnCoerceMinimum(value);
        }
        private static double OnCoerceIncrement(NumericUpDown upDown, double value)
        {
            return upDown.OnCoerceIncrement(value);
        }
        private void TextBoxOnTextChanged()
        {
            try
            {
                _isTextChangedFromUI = true;
                if (TextBox != null)
                {
                    Text = TextBox.Text;
                }
            }
            finally
            {
                _isTextChangedFromUI = false;
            }
        }
        private void OnSpinnerSpin(object sender, SpinEventArgs e)
        {
            if (AllowSpin && !IsReadOnly)
            {
                var spin = !e.UsingMouseWheel;
                spin |= ((TextBox != null) && TextBox.IsFocused);
                if (spin)
                {
                    e.Handled = true;
                    OnSpin(e);
                }
            }
        }
        private void DoDecrement()
        {
            if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease)
            {
                OnDecrement();
            }
        }
        private void DoIncrement()
        {
            if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase)
            {
                OnIncrement();
            }
        }
        public event EventHandler Spinned;
        private void TextBoxOnPointerPressed(object sender, PointerPressedEventArgs e)
        {
            if (e.Device.Captured != Spinner)
            {
                Dispatcher.UIThread.InvokeAsync(() => { e.Device.Capture(Spinner); }, DispatcherPriority.Input);
            }
        }
        /// 
        /// Defines the  event.
        /// 
        public static readonly RoutedEvent ValueChangedEvent =
            RoutedEvent.Register(nameof(ValueChanged), RoutingStrategies.Bubble);
        /// 
        /// Raised when the  changes.
        /// 
        public event EventHandler ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }
        private bool CommitInput()
        {
            return SyncTextAndValueProperties(true, Text);
        }
        /// 
        /// Synchronize  and  properties.
        /// 
        /// If value should be updated from text.
        /// The text.
        private bool SyncTextAndValueProperties(bool updateValueFromText, string text)
        {
            return SyncTextAndValueProperties(updateValueFromText, text, false);
        }
        /// 
        /// Synchronize  and  properties.
        /// 
        /// If value should be updated from text.
        /// The text.
        /// Force text update.
        private bool SyncTextAndValueProperties(bool updateValueFromText, string text, bool forceTextUpdate)
        {
            if (_isSyncingTextAndValueProperties)
                return true;
            _isSyncingTextAndValueProperties = true;
            var parsedTextIsValid = true;
            try
            {
                if (updateValueFromText)
                {
                    if (!string.IsNullOrEmpty(text))
                    {
                        try
                        {
                            var newValue = ConvertTextToValue(text);
                            if (!Equals(newValue, Value))
                            {
                                SetValueInternal(newValue);
                            }
                        }
                        catch
                        {
                            parsedTextIsValid = false;
                        }
                    }
                }
                // Do not touch the ongoing text input from user.
                if (!_isTextChangedFromUI)
                {
                    var keepEmpty = !forceTextUpdate && string.IsNullOrEmpty(Text);
                    if (!keepEmpty)
                    {
                        var newText = ConvertValueToText();
                        if (!Equals(Text, newText))
                        {
                            Text = newText;
                        }
                    }
                    // Sync Text and textBox
                    if (TextBox != null)
                    {
                        TextBox.Text = Text;
                    }
                }
                if (_isTextChangedFromUI && !parsedTextIsValid)
                {
                    // Text input was made from the user and the text
                    // represents an invalid value. Disable the spinner in this case.
                    if (Spinner != null)
                    {
                        Spinner.ValidSpinDirection = ValidSpinDirections.None;
                    }
                }
                else
                {
                    SetValidSpinDirection();
                }
            }
            finally
            {
                _isSyncingTextAndValueProperties = false;
            }
            return parsedTextIsValid;
        }
        private double ConvertTextToValueCore(string currentValueText, string text)
        {
            double result;
            if (IsPercent(FormatString))
            {
                result = decimal.ToDouble(ParsePercent(text, CultureInfo));
            }
            else
            {
                // Problem while converting new text
                if (!double.TryParse(text, ParsingNumberStyle, CultureInfo, out var outputValue))
                {
                    var shouldThrow = true;
                    // Check if CurrentValueText is also failing => it also contains special characters. ex : 90°
                    if (!double.TryParse(currentValueText, ParsingNumberStyle, CultureInfo, out var _))
                    {
                        // extract non-digit characters
                        var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c));
                        var textSpecialCharacters = text.Where(c => !char.IsDigit(c));
                        // same non-digit characters on currentValueText and new text => remove them on new Text to parse it again.
                        if (currentValueTextSpecialCharacters.Except(textSpecialCharacters).ToList().Count == 0)
                        {
                            foreach (var character in textSpecialCharacters)
                            {
                                text = text.Replace(character.ToString(), string.Empty);
                            }
                            // if without the special characters, parsing is good, do not throw
                            if (double.TryParse(text, ParsingNumberStyle, CultureInfo, out outputValue))
                            {
                                shouldThrow = false;
                            }
                        }
                    }
                    if (shouldThrow)
                    {
                        throw new InvalidDataException("Input string was not in a correct format.");
                    }
                }
                result = outputValue;
            }
            return result;
        }
        private void ValidateMinMax(double value)
        {
            if (value < Minimum)
            {
                throw new ArgumentOutOfRangeException(nameof(Minimum), string.Format("Value must be greater than Minimum value of {0}", Minimum));
            }
            else if (value > Maximum)
            {
                throw new ArgumentOutOfRangeException(nameof(Maximum), string.Format("Value must be less than Maximum value of {0}", Maximum));
            }
        }
        /// 
        /// Parse percent format text
        /// 
        /// Text to parse.
        /// The culture info.
        private static decimal ParsePercent(string text, IFormatProvider cultureInfo)
        {
            var info = NumberFormatInfo.GetInstance(cultureInfo);
            text = text.Replace(info.PercentSymbol, null);
            var result = decimal.Parse(text, NumberStyles.Any, info);
            result = result / 100;
            return result;
        }
        private bool IsPercent(string stringToTest)
        {
            var PIndex = stringToTest.IndexOf("P", StringComparison.Ordinal);
            if (PIndex >= 0)
            {
                //stringToTest contains a "P" between 2 "'", it's considered as text, not percent
                var isText = stringToTest.Substring(0, PIndex).Contains("'")
                             && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'");
                return !isText;
            }
            return false;
        }
    }
}