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

namespace eagle {
    public class DynamicTextBinding : IDisposable {
        public event Action<string> TextChanged;

        public string Text { get; private set; }

        private readonly DynamicTextTemplate textTemplate;
        private readonly Dictionary<DynamicTextPlaceholderName, string> placeholderValues = new();
        private readonly List<GeneratedTextListener> textListeners = new();

        public DynamicTextBinding(
                DynamicTextTemplate textTemplate,
                IEnumerable<DynamicPlaceholder> placeholders) {
            this.textTemplate =
                    textTemplate ?? throw new ArgumentNullException(nameof(textTemplate));
            var placeholderList =
                    (placeholders ?? throw new ArgumentNullException(nameof(placeholders)))
                            .ToList();
            if (placeholderList.Any(placeholder => placeholder == null)) {
                throw new ArgumentException(
                        "Dynamic text placeholders cannot contain null entries.",
                        nameof(placeholders));
            }
            ValidatePlaceholders(this.textTemplate, placeholderList);

            int index = 0;
            foreach (var placeholder in placeholderList) {
                placeholderValues[placeholder.Name] = InitialValue(placeholder);

                if (!string.IsNullOrEmpty(placeholder.TextId)) {
                    var listener = new GeneratedTextListener(
                            placeholder.TextId,
                            "",
                            "",
                            index,
                            (listenerIndex, text) => UpdatePlaceholder(placeholder.Name, text));

                    textListeners.Add(listener);
                    ClientTextProvider.Provider.AddListener(listener);
                }

                index++;
            }

            Text = Render();
        }

        private static void ValidatePlaceholders(
                DynamicTextTemplate textTemplate,
                IReadOnlyCollection<DynamicPlaceholder> placeholders) {
            var templateNames = textTemplate.PlaceholderNames();
            var placeholderNames = placeholders.Select(placeholder => placeholder.Name).ToList();

            var duplicates = placeholderNames.GroupBy(name => name)
                                     .Where(group => group.Count() > 1)
                                     .Select(group => group.Key)
                                     .ToList();
            if (duplicates.Any()) {
                throw new ArgumentException(
                        $"Duplicate dynamic text placeholders: {string.Join(", ", duplicates)}");
            }

            var missingPlaceholders = templateNames.Except(placeholderNames).ToList();
            if (missingPlaceholders.Any()) {
                throw new ArgumentException(
                        $"Dynamic text template placeholders missing values: {string.Join(", ", missingPlaceholders)}");
            }

            var unusedPlaceholders =
                    placeholderNames.Where(name => !templateNames.Contains(name)).ToList();
            if (unusedPlaceholders.Any()) {
                throw new ArgumentException(
                        $"Dynamic text placeholders not used by template: {string.Join(", ", unusedPlaceholders)}");
            }
        }

        private static string InitialValue(DynamicPlaceholder placeholder) {
            if (!string.IsNullOrEmpty(placeholder.TextId)) {
                var textEntry = ClientTextProvider.Provider.GetTextEntry(placeholder.TextId);
                if (textEntry != null && !string.IsNullOrEmpty(textEntry.Text)) {
                    return textEntry.Text;
                }
            }

            return placeholder.Fallback;
        }

        private void UpdatePlaceholder(DynamicTextPlaceholderName placeholder, string newText) {
            placeholderValues[placeholder] = newText;
            SetText(Render());
        }

        private string Render() { return textTemplate.Render(placeholderValues); }

        private void SetText(string text) {
            if (Text == text) { return; }
            Text = text;
            TextChanged?.Invoke(text);
        }

        public void Dispose() {
            foreach (var listener in textListeners) {
                ClientTextProvider.Provider.RemoveListener(listener);
            }
            textListeners.Clear();
        }
    }
}
