using Elwig.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Elwig.Windows {
    public abstract class AdministrationWindow : ContextWindow {

        protected Control[] ExemptInputs { private get; set; }
        protected Control[] RequiredInputs { private get; set; }
        private TextBox[] TextBoxInputs;
        private ComboBox[] ComboBoxInputs;
        private CheckBox[] CheckBoxInputs;
        private RadioButton[] RadioButtonInputs;
        private readonly Dictionary<Control, bool> Valid;
        private readonly Dictionary<Control, object?> OriginalValues;

        public AdministrationWindow() : base() {
            ExemptInputs = Array.Empty<Control>();
            RequiredInputs = Array.Empty<Control>();
            TextBoxInputs = Array.Empty<TextBox>();
            ComboBoxInputs = Array.Empty<ComboBox>();
            CheckBoxInputs = Array.Empty<CheckBox>();
            RadioButtonInputs = Array.Empty<RadioButton>();
            Valid = new();
            OriginalValues = new();
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs evt) {
            TextBoxInputs = Utils.FindAllChildren<TextBox>(this, ExemptInputs).ToArray();
            ComboBoxInputs = Utils.FindAllChildren<ComboBox>(this, ExemptInputs).ToArray();
            CheckBoxInputs = Utils.FindAllChildren<CheckBox>(this, ExemptInputs).ToArray();
            RadioButtonInputs = Utils.FindAllChildren<RadioButton>(this, ExemptInputs).ToArray();
            foreach (var tb in TextBoxInputs)
                Valid[tb] = true;
            ValidateRequiredInputs();
        }

        abstract protected void UpdateButtons();

        protected void ValidateInput(Control input, bool valid) {
            Valid[input] = valid;
        }

        protected bool SenderIsRequired(object sender) {
            return (sender is Control c) && RequiredInputs.Contains(c);
        }

        protected void ClearInputStates() {
            foreach (var tb in TextBoxInputs)
                Utils.ClearInputState(tb);
            foreach (var cb in ComboBoxInputs)
                Utils.ClearInputState(cb);
            foreach (var cb in CheckBoxInputs)
                Utils.ClearInputState(cb);
            foreach (var rb in RadioButtonInputs)
                Utils.ClearInputState(rb);
        }

        protected void ValidateRequiredInputs() {
            foreach (var input in RequiredInputs) {
                if (input is TextBox tb && tb.Text.Length == 0) {
                    Utils.SetInputInvalid(input);
                    Valid[input] = false;
                } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null) {
                    Utils.SetInputInvalid(input);
                }
            }
        }

        protected void LockInputs() {
            foreach (var tb in TextBoxInputs)
                tb.IsReadOnly = true;
            foreach (var cb in ComboBoxInputs)
                cb.IsEnabled = false;
            foreach (var cb in CheckBoxInputs)
                cb.IsEnabled = false;
            foreach (var rb in RadioButtonInputs)
                rb.IsEnabled = false;
        }

        protected void UnlockInputs() {
            foreach (var tb in TextBoxInputs)
                tb.IsReadOnly = false;
            foreach (var cb in ComboBoxInputs)
                cb.IsEnabled = true;
            foreach (var cb in CheckBoxInputs)
                cb.IsEnabled = true;
            foreach (var rb in RadioButtonInputs)
                rb.IsEnabled = true;
        }

        protected void ClearOriginalValues() {
            OriginalValues.Clear();
        }

        protected void FillOriginalValues() {
            foreach (var tb in TextBoxInputs)
                OriginalValues[tb] = tb.Text;
            foreach (var cb in ComboBoxInputs)
                OriginalValues[cb] = cb.SelectedItem;
            foreach (var cb in CheckBoxInputs)
                OriginalValues[cb] = (cb.IsChecked ?? false) ? bool.TrueString : null;
            foreach (var rb in RadioButtonInputs)
                OriginalValues[rb] = (rb.IsChecked ?? false) ? bool.TrueString : null;
        }

        protected void ClearInputs() {
            foreach (var tb in TextBoxInputs)
                tb.Text = "";
            foreach (var cb in ComboBoxInputs)
                cb.SelectedItem = null;
            foreach (var cb in CheckBoxInputs)
                cb.IsChecked = false;
            foreach (var rb in RadioButtonInputs)
                rb.IsChecked = false;
            ValidateRequiredInputs();
        }

        protected bool IsValid => Valid.All(kv => kv.Value);

        protected bool InputHasChanged(Control input) {
            if (!OriginalValues.ContainsKey(input)) {
                return false;
            } else if (input is TextBox tb) {
                return OriginalValues[tb]?.ToString() != tb.Text;
            } else if (input is ComboBox sb) {
                return OriginalValues[sb] != sb.SelectedItem;
            } else if (input is CheckBox cb) {
                return (OriginalValues[cb] != null) != (cb.IsChecked ?? false);
            } else if (input is RadioButton rb) {
                return (OriginalValues[rb] != null) != (rb.IsChecked ?? false);
            } else {
                return false;
            }
        }

        protected bool HasChanged =>
            !IsValid ||
            TextBoxInputs.Any(InputHasChanged) ||
            ComboBoxInputs.Any(InputHasChanged) ||
            CheckBoxInputs.Any(InputHasChanged) ||
            RadioButtonInputs.Any(InputHasChanged);

        protected void UpdatePlz(TextBox plzInput, bool plzInputValid, ComboBox ortInput) {
            var item = ortInput.SelectedItem;
            var list = plzInputValid && plzInput.Text.Length == 4 ? Context.Postleitzahlen.Find(int.Parse(plzInput.Text))?.Orte.ToList() : null;
            ortInput.ItemsSource = list;
            ortInput.SelectedItem = (list != null && item != null && list.Contains(item)) ? item : null;
            if (list != null && ortInput.SelectedItem == null && list.Count == 1)
                ortInput.SelectedItem = list[0];
            ComboBox_SelectionChanged(ortInput, new());
        }

        protected bool InputTextChanged(TextBox input, Func<TextBox, bool, ValidationResult> checker) {
            return InputTextChanged(input, (tb, required, ctx) => checker(tb, required));
        }

        protected bool InputTextChanged(TextBox input, Func<TextBox, bool, AppDbContext, ValidationResult> checker) {
            return InputTextChanged(input, checker(input, SenderIsRequired(input), Context));
        }

        protected bool InputTextChanged(TextBox input, ValidationResult res) {
            ValidateInput(input, res.IsValid);
            if (res.IsValid) {
                if (InputHasChanged(input)) {
                    Utils.SetInputChanged(input);
                } else {
                    Utils.ClearInputState(input);
                }
            } else {
                Utils.SetInputInvalid(input);
            }
            UpdateButtons();
            return res.IsValid;
        }

        protected bool InputLostFocus(TextBox input, Func<TextBox, bool, ValidationResult> checker, string? msg = null) {
            return InputLostFocus(input, (tb, requiered, ctx) => checker(tb, requiered), msg);
        }

        protected bool InputLostFocus(TextBox input, Func<TextBox, bool, AppDbContext, ValidationResult> checker, string? msg = null) {
            return InputLostFocus(input, checker(input, SenderIsRequired(input), Context), msg);
        }

        protected bool InputLostFocus(TextBox input, ValidationResult res, string? msg = null) {
            if (!res.IsValid)
                MessageBox.Show(res.ErrorContent.ToString(), msg ?? res.ErrorContent.ToString(), MessageBoxButton.OK, MessageBoxImage.Warning);
            return res.IsValid;
        }

        protected void CheckBox_Changed(object sender, RoutedEventArgs evt) {
            var input = (CheckBox)sender;
            if (SenderIsRequired(input) && input.IsChecked != true) {
                Utils.SetInputInvalid(input);
            } else if (InputHasChanged(input)) {
                Utils.SetInputChanged(input);
            } else {
                Utils.ClearInputState(input);
            }
            UpdateButtons();
        }

        protected void RadioButton_Changed(object sender, RoutedEventArgs evt) {
            var input = (RadioButton)sender;
            if (SenderIsRequired(input) && input.IsChecked != true) {
                Utils.SetInputInvalid(input);
            } else if (InputHasChanged(input)) {
                Utils.SetInputChanged(input);
            } else {
                Utils.ClearInputState(input);
            }
            UpdateButtons();
        }

        protected void TextBox_TextChanged(object sender, RoutedEventArgs evt) {
            var input = (TextBox)sender;
            if (SenderIsRequired(input) && input.Text.Length == 0) {
                ValidateInput(input, false);
                Utils.SetInputInvalid(input);
            } else {
                ValidateInput(input, true);
                if (InputHasChanged(input)) {
                    Utils.SetInputChanged(input);
                } else {
                    Utils.ClearInputState(input);
                }
            }
            UpdateButtons();
        }

        protected void ComboBox_SelectionChanged(object sender, RoutedEventArgs evt) {
            var input = (ComboBox)sender;
            if (input.ItemsSource != null && input.SelectedItem == null && RequiredInputs.Contains(input)) {
                ValidateInput(input, false);
                Utils.SetInputInvalid(input);
            } else {
                ValidateInput(input, true);
                if (InputHasChanged(input)) {
                    Utils.SetInputChanged(input);
                } else {
                    Utils.ClearInputState(input);
                }
            }
            UpdateButtons();
        }

        protected void IntegerInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckInteger);
        }

        protected void PartialDateInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckPartialDate);
        }

        protected void PartialDateInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckPartialDate);
        }

        protected void DateInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckDate);
        }

        protected void DateInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckDate);
        }

        protected void PlzInput_TextChanged(object sender, RoutedEventArgs evt) {
            var plz = (TextBox)sender;
            var valid = InputTextChanged(plz, Validator.CheckPlz);
            UpdatePlz(plz, valid, Utils.FindNextSibling<ComboBox>(plz));
        }

        protected void PlzInput_LostFocus(object sender, RoutedEventArgs evt) {
            var plz = (TextBox)sender;
            var valid = InputLostFocus(plz, Validator.CheckPlz);
            UpdatePlz(plz, valid, Utils.FindNextSibling<ComboBox>(plz));
        }

        protected void EmailInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckEmailAddress);
        }

        protected void EmailInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckEmailAddress);
        }

        protected void PhoneNrInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckPhoneNumber);
        }

        protected void PhoneNrInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckPhoneNumber);
        }

        protected void IbanInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckIban);
        }

        protected void IbanInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckIban);
        }

        protected void BicInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckBic);
        }

        protected void BicInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckBic);
        }

        protected void UstIdInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckUstId);
        }

        protected void UstIdInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckUstId);
        }

        protected void LfbisNrInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckLfbisNr);
        }

        protected void LfbisNrInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckLfbisNr);
        }
    }
}