using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace AppConv.General;

abstract class DecimalUnitConverterBase<T> : IUnitType where T : struct {
	internal sealed class DecimalFuncMap : Dictionary<T, Func<decimal, decimal>> {
		public DecimalFuncMap() {}
		public DecimalFuncMap(int capacity) : base(capacity) {}
	}

	internal sealed class NameMap : Dictionary<string, T> {
		public NameMap() {}
		public NameMap(int capacity) : base(capacity) {}
	}

	protected abstract NameMap Names { get; }
	protected abstract DecimalFuncMap ConvertTo { get; }
	protected abstract DecimalFuncMap ConvertFrom { get; }

	protected virtual int Precision => 0;

	protected virtual bool CaseCheck => false;

	protected virtual NumberStyles NumberStyle => NumberStyles.Float & ~NumberStyles.AllowLeadingSign;

	protected virtual string ProcessSrc(string src) {
		return src;
	}

	protected virtual string ProcessDst(string dst) {
		return dst;
	}

	protected abstract bool IsValueInvalid(T value);

	protected virtual decimal Convert(decimal value, T from, T to) {
		return ConvertFrom[to](ConvertTo[from](value));
	}

	protected virtual string Format(decimal value) {
		if (Precision > 0) {
			decimal truncated = decimal.Truncate(value);

			if (value == truncated) {
				return truncated.ToString(CultureInfo.InvariantCulture);
			}
		}

		int decimalPlaces = Precision;

		if (Math.Abs(value) < 1M) {
			double fractionalPart = (double) Math.Abs(value % 1M);
			int fractionalZeroCount = -(int) Math.Ceiling(Math.Log(fractionalPart, 10D));

			decimalPlaces = Math.Min(28, fractionalZeroCount + Precision);
		}

		string result = decimal.Round(value, decimalPlaces, MidpointRounding.ToEven).ToString(CultureInfo.InvariantCulture);

		if (decimalPlaces > 0) {
			result = result.TrimEnd('0').TrimEnd('.');
		}

		return result;
	}

	public bool TryProcess(string src, string dst, out string result) {
		src = ProcessSrc(src);
		dst = ProcessDst(dst);

		var pairs = new KeyValuePair<string, T>[2];

		for (int index = 0; index < 2; index++) {
			string str = index == 0 ? src : dst;

			if (CaseCheck) {
				List<KeyValuePair<string, T>> list = Names.Where(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCultureIgnoreCase) && (str.Length == kvp.Key.Length || !char.IsLetter(str[str.Length - kvp.Key.Length - 1]))).ToList();

				if (list.Count == 1) {
					pairs[index] = list[0];
				}
				else {
					pairs[index] = list.FirstOrDefault(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCulture));
				}
			}
			else {
				pairs[index] = Names.FirstOrDefault(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCultureIgnoreCase) && (str.Length == kvp.Key.Length || !char.IsLetter(str[str.Length - kvp.Key.Length - 1])));
			}

			if (IsValueInvalid(pairs[index].Value)) {
				result = string.Empty;
				return false;
			}

			if (index == 0) {
				src = src[..^pairs[index].Key.Length].TrimEnd();
			}
		}

		if (decimal.TryParse(src, NumberStyle, CultureInfo.InvariantCulture, out decimal value)) {
			result = Format(Convert(value, pairs[0].Value, pairs[1].Value));
			return true;
		}
		else {
			result = string.Empty;
			return false;
		}
	}
}