using System;
using System.Globalization;
using System.Windows.Data;

namespace NavisworksTransport.UI.WPF.Converters
{
    /// <summary>
    /// Keeps numeric TextBox editing humane: incomplete text is allowed while editing,
    /// and only complete numeric values are written back to the bound source.
    /// </summary>
    public class EditableNumberConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
            {
                return string.Empty;
            }

            string format = parameter as string;
            if (string.IsNullOrWhiteSpace(format))
            {
                return System.Convert.ToString(value, culture);
            }

            if (value is IFormattable formattable)
            {
                return formattable.ToString(format, culture);
            }

            return System.Convert.ToString(value, culture);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string text = value as string;
            if (IsIntermediateText(text))
            {
                return Binding.DoNothing;
            }

            text = text.Trim();
            Type actualTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;

            if (TryParseNumber(text, culture, actualTargetType, out object parsedValue))
            {
                return parsedValue;
            }

            return Binding.DoNothing;
        }

        public static bool IsIntermediateText(string text)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                return true;
            }

            text = text.Trim();

            return text == "-" ||
                   text == "+" ||
                   text == "." ||
                   text == "-." ||
                   text == "+.";
        }

        public static bool TryParseNumber(string text, CultureInfo culture, Type targetType, out object parsedValue)
        {
            NumberStyles styles = NumberStyles.Float | NumberStyles.AllowThousands;

            if (targetType == typeof(double))
            {
                if (TryParseDouble(text, styles, culture, out double doubleValue))
                {
                    parsedValue = doubleValue;
                    return true;
                }
            }
            else if (targetType == typeof(float))
            {
                if (TryParseDouble(text, styles, culture, out double doubleValue))
                {
                    parsedValue = (float)doubleValue;
                    return true;
                }
            }
            else if (targetType == typeof(decimal))
            {
                if (decimal.TryParse(text, styles, culture, out decimal decimalValue) ||
                    decimal.TryParse(text, styles, CultureInfo.InvariantCulture, out decimalValue))
                {
                    parsedValue = decimalValue;
                    return true;
                }
            }
            else if (targetType == typeof(int))
            {
                if (int.TryParse(text, NumberStyles.Integer, culture, out int intValue) ||
                    int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out intValue))
                {
                    parsedValue = intValue;
                    return true;
                }
            }
            else if (targetType == typeof(long))
            {
                if (long.TryParse(text, NumberStyles.Integer, culture, out long longValue) ||
                    long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))
                {
                    parsedValue = longValue;
                    return true;
                }
            }

            parsedValue = null;
            return false;
        }

        private static bool TryParseDouble(string text, NumberStyles styles, CultureInfo culture, out double value)
        {
            return double.TryParse(text, styles, culture, out value) ||
                   double.TryParse(text, styles, CultureInfo.InvariantCulture, out value);
        }
    }
}
