using System.Globalization;

namespace Connected;

/// <summary>
/// A universal T to string binding converter
/// </summary>
public class DefaultConverter<T> : ToStringConverter<T>
{
	protected override string? ConvertValue(T? value) => ConvertToString(value);

	protected override T? ConvertValueBack(string? value) => ConvertFromString(value);

	public string DefaultTimeSpanFormat { get; set; } = "c";

	protected virtual T? ConvertFromString(string? value)
	{
		try
		{
			/*
			 * This is important, or otherwise all the TryParse down there might fail.
			 */
			if (string.IsNullOrEmpty(value))
			{
				return default;
			}
			/*
			 * String
			 */
			else if (typeof(T) == typeof(string))
			{
				return (T)(object)value;
			}
			/*
			 * Char
			 */
			else if (typeof(T) == typeof(char) || typeof(T) == typeof(char?))
			{
				return (T)(object)value[0];
			}
			/*
			 * Bool
			 */
			else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?))
			{
				var lowerValue = value.ToLowerInvariant();

				if (lowerValue is "true" or "on")
					return (T)(object)true;

				if (lowerValue is "false" or "off")
					return (T)(object)false;

				TriggerError("Not a valid boolean");
			}
			/*
			 * Sbyte
			 */
			else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
			{
				if (sbyte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Byte
			 */
			else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
			{
				if (byte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Short
			 */
			else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
			{
				if (short.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Ushort
			 */
			else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
			{
				if (ushort.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Int
			 */
			else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
			{
				if (int.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Uint
			 */
			else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
			{
				if (uint.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Long
			 */
			else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
			{
				if (long.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Ulong
			 */
			else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
			{
				if (ulong.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Float
			 */
			else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
			{
				if (float.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/* 
			 * Double
			 */
			else if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
			{
				if (double.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Decimal
			 */
			else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
			{
				if (decimal.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid number");
			}
			/*
			 * Guid
			 */
			else if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?))
			{
				if (Guid.TryParse(value, out var parsedValue))
					return (T)(object)parsedValue;

				TriggerError("Not a valid GUID");
			}
			/*
			 * Enum
			 */
			else if (IsNullableEnum(typeof(T)))
			{
				var enum_type = Nullable.GetUnderlyingType(typeof(T));

				if (Enum.TryParse(enum_type, value, out var parsedValue))
					return (T)parsedValue;

				TriggerError("Not a value of " + enum_type.Name);
			}
			else if (typeof(T).IsEnum)
			{
				if (Enum.TryParse(typeof(T), value, out var parsedValue))
					return (T)parsedValue;

				TriggerError("Not a value of " + typeof(T).Name);
			}
			/*
			 * DateTime
			 */
			else if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?))
			{
				try
				{
					return (T)(object)DateTime.ParseExact(value, Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
				}
				catch (FormatException)
				{
					TriggerError("Not a valid date time");
				}
			}
			/*
			 * Timespan
			 */
			else if (typeof(T) == typeof(TimeSpan) || typeof(T) == typeof(TimeSpan?))
			{
				try
				{
					return (T)(object)TimeSpan.ParseExact(value, Format ?? DefaultTimeSpanFormat, Culture);
				}
				catch (FormatException)
				{
					TriggerError("Not a valid time span");
				}
			}
			else
			{
				TriggerError($"Conversion to type {typeof(T)} not implemented");
			}
		}
		catch (Exception e)
		{
			TriggerError("Conversion error: " + e.Message);
		}

		return default;
	}

	protected virtual string? ConvertToString(T? arg)
	{
		/*
		 * This catches all null values. No additional null checks necessary.
		 */
		if (arg is null)
			return null;

		try
		{
			/*
			 * String
			 */
			if (typeof(T) == typeof(string))
			{
				return (string)(object)arg;
			}
			/* 
			 * Char
			 */
			else if (typeof(T) == typeof(char))
			{
				return ((char)(object)arg).ToString(Culture);
			}
			else if (typeof(T) == typeof(char?))
			{
				return ((char?)(object)arg).Value.ToString(Culture);
			}
			/*
			 * Bool
			 */
			else if (typeof(T) == typeof(bool))
			{
				return ((bool)(object)arg).ToString(CultureInfo.InvariantCulture);
			}
			else if (typeof(T) == typeof(bool?))
			{
				return ((bool?)(object)arg).Value.ToString(CultureInfo.InvariantCulture);
			}
			/* 
			 * Sbyte
			 */
			else if (typeof(T) == typeof(sbyte))
			{
				return ((sbyte)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(sbyte?))
			{
				return ((sbyte?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Byte
			 */
			else if (typeof(T) == typeof(byte))
			{
				return ((byte)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(byte?))
			{
				return ((byte?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Short
			 */
			else if (typeof(T) == typeof(short))
			{
				return ((short)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(short?))
			{
				return ((short?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Ushort
			 */
			else if (typeof(T) == typeof(ushort))
			{
				return ((ushort)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(ushort?))
			{
				return ((ushort?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Int
			 */
			else if (typeof(T) == typeof(int))
			{
				return ((int)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(int?))
			{
				return ((int?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Uint
			 */
			else if (typeof(T) == typeof(uint))
			{
				return ((uint)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(uint?))
			{
				return ((uint?)(object)arg).Value.ToString(Format, Culture);
			}
			/*
			 * Long
			 */
			else if (typeof(T) == typeof(long))
			{
				return ((long)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(long?))
			{
				return ((long?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Ulong
			 */
			else if (typeof(T) == typeof(ulong))
			{
				return ((ulong)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(ulong?))
			{
				return ((ulong?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Float
			 */
			else if (typeof(T) == typeof(float))
			{
				return ((float)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(float?))
			{
				return ((float?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Double
			 */
			else if (typeof(T) == typeof(double))
			{
				return ((double)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(double?))
			{
				return ((double?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Decimal
			 */
			else if (typeof(T) == typeof(decimal))
			{
				return ((decimal)(object)arg).ToString(Format, Culture);
			}
			else if (typeof(T) == typeof(decimal?))
			{
				return ((decimal?)(object)arg).Value.ToString(Format, Culture);
			}
			/* 
			 * Guid
			 */
			else if (typeof(T) == typeof(Guid))
			{
				var value = (Guid)(object)arg;
				return value.ToString();
			}
			else if (typeof(T) == typeof(Guid?))
			{
				var value = (Guid?)(object)arg;
				return value.Value.ToString();
			}
			/* 
			 * Enum
			 */
			else if (IsNullableEnum(typeof(T)))
			{
				var value = (Enum)(object)arg;
				return value.ToString();
			}
			else if (typeof(T).IsEnum)
			{
				var value = (Enum)(object)arg;
				return value.ToString();
			}
			/* 
			 * DateTime
			 */
			else if (typeof(T) == typeof(DateTime))
			{
				var value = (DateTime)(object)arg;
				return value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
			}
			else if (typeof(T) == typeof(DateTime?))
			{
				var value = (DateTime?)(object)arg;
				return value.Value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
			}
			/* 
			 * Timespan
			 */
			else if (typeof(T) == typeof(TimeSpan))
			{
				var value = (TimeSpan)(object)arg;
				return value.ToString(Format ?? DefaultTimeSpanFormat, Culture);
			}
			else if (typeof(T) == typeof(TimeSpan?))
			{
				var value = (TimeSpan?)(object)arg;
				return value.Value.ToString(Format ?? DefaultTimeSpanFormat, Culture);
			}

			return arg.ToString();
		}
		catch (FormatException e)
		{
			TriggerError("Conversion error: " + e.Message);
			return null;
		}
	}

	public static bool IsNullableEnum(Type t)
	{
		var u = Nullable.GetUnderlyingType(t);

		return (u is not null) && u.IsEnum;
	}
}
