using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Elwig.Controls {
    public class CheckComboBox : ListBox {

        public new static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(CheckComboBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChangedCallback));
        public new IList SelectedItems {
            get => (IList)GetValue(SelectedItemsProperty);
            set => SetValue(SelectedItemsProperty, value);
        }

        public static readonly DependencyProperty DelimiterProperty = DependencyProperty.Register(nameof(Delimiter), typeof(string), typeof(CheckComboBox), new FrameworkPropertyMetadata(", "));
        public string Delimiter {
            get => (string)GetValue(DelimiterProperty);
            set => SetValue(DelimiterProperty, value);
        }

        public static readonly DependencyProperty ListDisplayMemberPathProperty = DependencyProperty.Register(nameof(ListDisplayMemberPath), typeof(string), typeof(CheckComboBox), new FrameworkPropertyMetadata(null));
        public string ListDisplayMemberPath {
            get => (string)GetValue(ListDisplayMemberPathProperty);
            set => SetValue(ListDisplayMemberPathProperty, value);
        }

        public static readonly DependencyProperty AllItemsSelectedContentProperty = DependencyProperty.Register(nameof(AllItemsSelectedContent), typeof(string), typeof(CheckComboBox), new FrameworkPropertyMetadata("All"));
        public string AllItemsSelectedContent {
            get => (string)GetValue(AllItemsSelectedContentProperty);
            set => SetValue(AllItemsSelectedContentProperty, value);
        }

        public static readonly DependencyProperty IsSelectAllActiveProperty = DependencyProperty.Register(nameof(IsSelectAllActive), typeof(bool), typeof(CheckComboBox), new FrameworkPropertyMetadata(false));
        public bool IsSelectAllActive {
            get => (bool)GetValue(IsSelectAllActiveProperty);
            set => SetValue(IsSelectAllActiveProperty, value);
        }

        public static readonly DependencyProperty SelectAllContentProperty = DependencyProperty.Register(nameof(SelectAllContent), typeof(string), typeof(CheckComboBox), new FrameworkPropertyMetadata("All"));
        public string SelectAllContent {
            get => (string)GetValue(SelectAllContentProperty);
            set => SetValue(SelectAllContentProperty, value);
        }

        public static readonly DependencyProperty AllItemsSelectedProperty = DependencyProperty.Register(nameof(AllItemsSelected), typeof(bool?), typeof(CheckComboBox), new FrameworkPropertyMetadata(false));
        public bool? AllItemsSelected {
            get => (bool?)GetValue(AllItemsSelectedProperty);
            set => SetValue(AllItemsSelectedProperty, value);
        }

        public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register(nameof(IsDropDownOpen), typeof(bool), typeof(CheckComboBox), new FrameworkPropertyMetadata(false));
        public bool IsDropDownOpen {
            get => (bool)GetValue(IsDropDownOpenProperty);
            set => SetValue(IsDropDownOpenProperty, value);
        }

        public static readonly DependencyProperty MaxDropDownHeightProperty = DependencyProperty.Register(nameof(MaxDropDownHeight), typeof(double), typeof(CheckComboBox), new FrameworkPropertyMetadata(ComboBox.MaxDropDownHeightProperty.DefaultMetadata.DefaultValue));
        public double MaxDropDownHeight {
            get => (double)GetValue(MaxDropDownHeightProperty);
            set => SetValue(MaxDropDownHeightProperty, value);
        }

        public new static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent(nameof(SelectionChanged), RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(CheckComboBox));
        public new event SelectionChangedEventHandler SelectionChanged {
            add => AddHandler(SelectionChangedEvent, value);
            remove => RemoveHandler(SelectionChangedEvent, value);
        }

        static CheckComboBox() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckComboBox), new FrameworkPropertyMetadata(typeof(CheckComboBox)));
        }

        private bool _viewHandled;
        private bool _modelHandled;
        private TextBlock _textBox;

        public CheckComboBox() {
            SelectionMode = SelectionMode.Multiple;
            SelectedItems = new ObservableCollection<object>();
        }

        public override void OnApplyTemplate() {
            _textBox = (GetTemplateChild("TextBox") as TextBlock)!;
            var button = GetTemplateChild("Button") as Button;
            button!.Click += Button_MouseDown;
            var item = GetTemplateChild("SelectAllItem") as ListBoxItem;
            item!.PreviewMouseDown += SelectAllItem_MouseDown;
            if (SelectedItems is INotifyCollectionChanged collection) {
                collection.CollectionChanged += (s, e) => { SelectItems(); };
            }
            IsEnabledChanged += OnIsEnabledChanged;
            base.SelectionChanged += OnSelectionChanged;
            base.OnApplyTemplate();
        }

        private static void OnSelectedItemsChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
            if (sender is CheckComboBox ccb)
                ccb.OnSelectedItemsChanged();
        }

        private void OnSelectedItemsChanged() {
            if (SelectedItems is INotifyCollectionChanged collection) {
                collection.CollectionChanged += (s, e) => { SelectItems(); };
            }
            SelectItems();
        }

        private void Button_MouseDown(object sender, RoutedEventArgs evt) {
            IsDropDownOpen = !IsDropDownOpen;
        }

        private void SelectAllItem_MouseDown(object sender, RoutedEventArgs evt) {
            if (AllItemsSelected == false) {
                SelectAll();
            } else {
                UnselectAll();
            }
            evt.Handled = true;
        }

        private void OnSelectionChanged(object sender, SelectionChangedEventArgs evt) {
            SelectItemsReverse();
            var dmp = !string.IsNullOrEmpty(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrEmpty(DisplayMemberPath) ? DisplayMemberPath : null;
            if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
                AllItemsSelected = true;
            } else if (SelectedItems.Count == 0) {
                AllItemsSelected = false;
            } else {
                AllItemsSelected = null;
            }
            if (SelectedItems.Count > 1 && SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
                _textBox.Text = AllItemsSelectedContent;
            } else if (SelectedItems.Count == 0) {
                _textBox.Text = "";
            } else {
                _textBox.Text = string.Join(Delimiter,
                    dmp == null ? SelectedItems.Cast<object>() :
                    SelectedItems.Cast<object>()
                        .Select(i => i.GetType().GetProperty(dmp)?.GetValue(i))
                );
            }

            RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, evt.RemovedItems, evt.AddedItems));
        }

        private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs evt) {
            if (!IsEnabled) IsDropDownOpen = false;
        }

        private void SelectItems() {
            if (_viewHandled || _modelHandled)
                return;
            _viewHandled = true;
            base.SelectedItems.Clear();
            foreach (var item in SelectedItems)
                base.SelectedItems.Add(item);
            _viewHandled = false;
        }

        private void SelectItemsReverse() {
            if (_modelHandled || _viewHandled)
                return;
            _modelHandled = true;
            SelectedItems.Clear();
            foreach (var item in base.SelectedItems)
                SelectedItems.Add(item);
            _modelHandled = false;
        }
    }
}
