using Elwig.Helpers;
using Elwig.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using Xceed.Wpf.Toolkit;

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

        protected Control[] ExemptInputs { private get; set; }
        protected Control[] RequiredInputs { private get; set; }

        private bool _isEditing;
        private bool _isCreating;
        protected bool IsEditing {
            get { return _isEditing; }
            set {
                _isEditing = value;
                LockContext = IsEditing || IsCreating;
            }
        }
        protected bool IsCreating {
            get { return _isCreating; }
            set {
                _isCreating = value;
                LockContext = IsEditing || IsCreating;
            }
        }
        protected bool DoShowWarningWindows = true;
        protected bool IsClosing { get; private set; }

        private TextBox[] TextBoxInputs;
        private TextBox[] PlzInputs;
        private ComboBox[] ComboBoxInputs;
        private ComboBox[] PlzOrtInputs;
        private CheckComboBox[] CheckComboBoxInputs;
        private CheckBox[] CheckBoxInputs;
        private RadioButton[] RadioButtonInputs;
        private readonly Dictionary<Control, bool> Valid;
        private readonly Dictionary<Control, object?> OriginalValues;
        private readonly Dictionary<Control, object?> DefaultValues;

        public AdministrationWindow() : base() {
            IsEditing = false;
            IsCreating = false;
            ExemptInputs = Array.Empty<Control>();
            RequiredInputs = Array.Empty<Control>();
            TextBoxInputs = Array.Empty<TextBox>();
            PlzInputs = Array.Empty<TextBox>();
            ComboBoxInputs = Array.Empty<ComboBox>();
            CheckComboBoxInputs = Array.Empty<CheckComboBox>();
            PlzOrtInputs = Array.Empty<ComboBox>();
            CheckBoxInputs = Array.Empty<CheckBox>();
            RadioButtonInputs = Array.Empty<RadioButton>();
            Valid = new();
            OriginalValues = new();
            DefaultValues = new();
            Closing += OnClosing;
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs evt) {
            TextBoxInputs = ControlUtils.FindAllChildren<TextBox>(this, ExemptInputs).ToArray();
            ComboBoxInputs = ControlUtils.FindAllChildren<ComboBox>(this, ExemptInputs).ToArray();
            CheckBoxInputs = ControlUtils.FindAllChildren<CheckBox>(this, ExemptInputs).ToArray();
            CheckComboBoxInputs = ControlUtils.FindAllChildren<CheckComboBox>(this, ExemptInputs).ToArray();
            RadioButtonInputs = ControlUtils.FindAllChildren<RadioButton>(this, ExemptInputs).ToArray();
            PlzInputs = TextBoxInputs.Where(tb => "PLZ".Equals(tb.Tag)).ToArray();
            PlzOrtInputs = PlzInputs.Select(tb => ControlUtils.FindNextSibling<ComboBox>(tb) ?? throw new MissingMemberException()).ToArray();
            foreach (var tb in TextBoxInputs)
                Valid[tb] = true;
            foreach (var cb in ComboBoxInputs)
                cb.SelectionChanged += ComboBox_SelectionChanged;
            foreach (var cb in CheckComboBoxInputs)
                cb.ItemSelectionChanged += ComboBox_SelectionChanged;
        }

        private void OnClosing(object? sender, CancelEventArgs evt) {
            if ((IsCreating || IsEditing) && HasChanged) {
                var r = System.Windows.MessageBox.Show("Soll das Fenster wirklich geschlossen werden?", "Schlie�en best�tigen",
                    MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
                if (r != MessageBoxResult.Yes) {
                    evt.Cancel = true;
                    return;
                }
            }
            IsClosing = true;
        }

        private ComboBox GetPlzOrtInput(TextBox input) {
            return PlzOrtInputs[Array.IndexOf(PlzInputs, input)];
        }

        abstract protected void UpdateButtons();

        protected override async Task OnRenewContext() {
            for (int i = 0; i < PlzInputs.Length; i++)
                UpdatePlz(PlzInputs[i], PlzOrtInputs[i]);
        }

        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 FinishInputFilling() {
            FillOriginalValues();
            ValidateDefaultValues();
            ValidateRequiredInputs();
        }

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

        protected void ValidateRequiredInputs() {
            foreach (var input in RequiredInputs) {
                if (input is TextBox tb && tb.Text.Length == 0) {
                    ControlUtils.SetInputInvalid(input);
                    Valid[input] = false;
                } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null) {
                    ControlUtils.SetInputInvalid(input);
                } else if (input is CheckComboBox ccb && ccb.SelectedItem == null && ccb.ItemsSource != null) {
                    ControlUtils.SetInputInvalid(input);
                } else if (input is CheckBox ckb && ckb.IsChecked != true) {
                    ControlUtils.SetInputInvalid(input);
                    Valid[input] = false;
                } else if (input is RadioButton rb && rb.IsChecked != true) {
                    ControlUtils.SetInputInvalid(input);
                    Valid[input] = false;
                }
            }
        }

        protected void ValidateDefaultValues() {
            foreach (var input in DefaultValues.Keys) {
                if (InputIsNotDefault(input))
                    ControlUtils.SetInputNotDefault(input);
            }
        }

        protected void LockInputs() {
            foreach (var tb in TextBoxInputs)
                tb.IsReadOnly = true;
            foreach (var cb in ComboBoxInputs)
                cb.IsEnabled = false;
            foreach (var ccb in CheckComboBoxInputs)
                ccb.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 ccb in CheckComboBoxInputs)
                ccb.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 ClearDefaultValues() {
            DefaultValues.Clear();
        }

        protected void FillOriginalValues() {
            foreach (var tb in TextBoxInputs)
                OriginalValues[tb] = tb.Text;
            foreach (var cb in ComboBoxInputs)
                OriginalValues[cb] = cb.SelectedItem;
            foreach (var ccb in CheckComboBoxInputs)
                OriginalValues[ccb] = ccb.SelectedItems.Cast<object>().ToArray();
            foreach (var cb in CheckBoxInputs)
                OriginalValues[cb] = cb.IsChecked?.ToString();
            foreach (var rb in RadioButtonInputs)
                OriginalValues[rb] = rb.IsChecked?.ToString();
        }

        protected void SetOriginalValue(Control input, object? value) {
            OriginalValues[input] = value is bool b ? b.ToString() : value;
            if (InputHasChanged(input)) {
                ControlUtils.SetInputChanged(input);
            } else {
                ControlUtils.ClearInputState(input);
            }
        }

        protected void SetOriginalValue(Control input) {
            SetOriginalValue(input, ControlUtils.GetInputValue(input));
        }

        protected void UnsetOriginalValue(Control input) {
            OriginalValues.Remove(input);
            ControlUtils.ClearInputState(input);
        }

        protected void SetDefaultValue(Control input, object? value) {
            DefaultValues[input] = value is bool b ? b.ToString() : value;
            if (!InputHasChanged(input)) {
                if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            }
        }

        protected void SetDefaultValue(Control input) {
            SetDefaultValue(input, ControlUtils.GetInputValue(input));
        }

        protected void UnsetDefaultValue(Control input) {
            DefaultValues.Remove(input);
            if (!InputHasChanged(input)) {
                ControlUtils.ClearInputState(input);
            }
        }

        protected void ClearInputs(bool validate = true) {
            foreach (var tb in TextBoxInputs)
                tb.Text = "";
            foreach (var cb in ComboBoxInputs)
                cb.SelectedItem = null;
            foreach (var ccb in CheckComboBoxInputs)
                ccb.SelectedItems.Clear();
            foreach (var cb in CheckBoxInputs)
                cb.IsChecked = false;
            foreach (var rb in RadioButtonInputs)
                rb.IsChecked = false;
            if (validate) ValidateRequiredInputs();
        }

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

        protected bool GetInputValid(Control input) {
            return Valid[input];
        }

        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 CheckComboBox ccb) {
                return !ccb.SelectedItems.Cast<object>().ToArray().SequenceEqual(((object[]?)OriginalValues[ccb]) ?? Array.Empty<object>());
            } else if (input is CheckBox cb) {
                return (string?)OriginalValues[cb] != cb.IsChecked?.ToString();
            } else if (input is RadioButton rb) {
                return (string?)OriginalValues[rb] != rb.IsChecked?.ToString();
            } else {
                return false;
            }
        }

        protected bool InputIsNotDefault(Control input) {
            if (!DefaultValues.ContainsKey(input)) {
                return false;
            } else if (input is TextBox tb) {
                return DefaultValues[tb]?.ToString() != tb.Text;
            } else if (input is ComboBox sb) {
                return DefaultValues[sb] != sb.SelectedItem;
            } else if (input is CheckComboBox ccb) {
                return !ccb.SelectedItems.Cast<object>().ToArray().SequenceEqual(((object[]?)DefaultValues[ccb]) ?? Array.Empty<object>());
            } else if (input is CheckBox cb) {
                return (string?)DefaultValues[cb] != cb.IsChecked?.ToString();
            } else if (input is RadioButton rb) {
                return (string?)DefaultValues[rb] != rb.IsChecked?.ToString();
            } else {
                return false;
            }
        }

        protected bool HasChanged =>
            IsEditing && (
                !IsValid ||
                TextBoxInputs.Any(InputHasChanged) ||
                ComboBoxInputs.Any(InputHasChanged) ||
                CheckComboBoxInputs.Any(InputHasChanged) ||
                CheckBoxInputs.Any(InputHasChanged) ||
                RadioButtonInputs.Any(InputHasChanged)
            ) || IsCreating && (
                TextBoxInputs.Any(i => InputIsNotDefault(i) || (!i.IsReadOnly && i.Text != "")) ||
                ComboBoxInputs.Any(i => InputIsNotDefault(i) || (i.IsEnabled && i.SelectedItem != null)) ||
                CheckComboBoxInputs.Any(i => InputIsNotDefault(i) || i.SelectedItem != null) ||
                CheckBoxInputs.Any(InputIsNotDefault) ||
                RadioButtonInputs.Any(InputIsNotDefault)
            );

        protected void UpdatePlz(TextBox plzInput, ComboBox ortInput) {
            var plzInputValid = GetInputValid(plzInput);
            var item = ortInput.SelectedItem;
            var list = plzInputValid && plzInput.Text.Length == 4 ? Context.Postleitzahlen.Find(int.Parse(plzInput.Text))?.Orte.ToList() : null;
            ControlUtils.RenewItemsSource(ortInput, list, i => (i as AT_PlzDest)?.Id);
            if (list != null && ortInput.SelectedItem == null && list.Count == 1)
                ortInput.SelectedItem = list[0];
            UpdateComboBox(ortInput);
        }

        protected static void InitializeDelayTimer(TextBox tb, Action<object, TextChangedEventArgs> handler) {
            var timer = new DispatcherTimer {
                Interval = TimeSpan.FromMilliseconds(250)
            };
            timer.Tick += (object? sender, EventArgs evt) => {
                timer.Stop();
                var (oSender, oEvent) = ((object, TextChangedEventArgs))timer.Tag;
                handler(oSender, oEvent);
            };
            tb.TextChanged += (object sender, TextChangedEventArgs evt) => {
                timer.Stop();
                timer.Tag = (sender, evt);
                timer.Start();
            };
        }

        protected bool InputTextChanged(TextBox input) {
            return InputTextChanged(input, new ValidationResult(true, null));
        }

        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)) {
                    ControlUtils.SetInputChanged(input);
                } else if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            } else {
                ControlUtils.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 (DoShowWarningWindows && !res.IsValid && !IsClosing && (IsEditing || IsCreating))
                System.Windows.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) {
                ControlUtils.SetInputInvalid(input);
            } else if (InputHasChanged(input)) {
                ControlUtils.SetInputChanged(input);
            } else if (InputIsNotDefault(input)) {
                ControlUtils.SetInputNotDefault(input);
            } else {
                ControlUtils.ClearInputState(input);
            }
            UpdateButtons();
        }

        protected void RadioButton_Changed(object sender, RoutedEventArgs evt) {
            var input = (RadioButton)sender;
            if (SenderIsRequired(input) && input.IsChecked != true) {
                ControlUtils.SetInputInvalid(input);
            } else if (InputHasChanged(input)) {
                ControlUtils.SetInputChanged(input);
            } else if (InputIsNotDefault(input)) {
                ControlUtils.SetInputNotDefault(input);
            } else {
                ControlUtils.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);
                ControlUtils.SetInputInvalid(input);
            } else {
                ValidateInput(input, true);
                if (InputHasChanged(input)) {
                    ControlUtils.SetInputChanged(input);
                } else if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            }
            UpdateButtons();
        }

        private void UpdateComboBox(Control input) {
            bool valid = false;
            if (input is ComboBox cb) {
                valid = cb.ItemsSource == null || cb.SelectedItem != null || !RequiredInputs.Contains(input);
            } else if (input is CheckComboBox ccb) {
                valid = ccb.ItemsSource == null || ccb.SelectedItem != null || !RequiredInputs.Contains(input);
            }
            if (valid) {
                ValidateInput(input, true);
                if (InputHasChanged(input)) {
                    ControlUtils.SetInputChanged(input);
                } else if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            } else {
                ValidateInput(input, false);
                ControlUtils.SetInputInvalid(input);
            }
            UpdateButtons();
        }

        protected void ComboBox_SelectionChanged(object sender, RoutedEventArgs evt) {
            UpdateComboBox((Control)sender);
        }

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

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

        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 TimeInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckTime);
        }

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

        protected void PlzInput_TextChanged(object sender, RoutedEventArgs evt) {
            var plz = (TextBox)sender;
            InputTextChanged(plz, Validator.CheckPlz);
            if ("PLZ".Equals(plz.Tag))
                UpdatePlz(plz, GetPlzOrtInput(plz));
        }

        protected void PlzInput_LostFocus(object sender, RoutedEventArgs evt) {
            var plz = (TextBox)sender;
            InputLostFocus(plz, Validator.CheckPlz);
            if ("PLZ".Equals(plz.Tag))
                UpdatePlz(plz, GetPlzOrtInput(plz));
        }

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

        protected void EmailAddressInput_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 UstIdNrInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckUstIdNr);
        }

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

        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);
        }
    }
}