using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
using Aurora.Utils;

namespace Aurora.Design.Components.DataGrid
{
    public partial class DataGrid : Grid
    {
        #region Private Fields
        private ObservableCollection<object> _internalItems;

        private Dictionary<int, SortingOrder> _sortingOrders;

        #endregion Fields

        #region Constructor

        public DataGrid() : this(ListViewCachingStrategy.RetainElement)
        {
        }

        public DataGrid(ListViewCachingStrategy cachingStrategy)
        {
            InitializeComponent();
            BackgroundColor = Color.Transparent;

            _sortingOrders = new Dictionary<int, SortingOrder>();

            DataList.ItemTemplate = new DataGridRowTemplateSelector();

            DataList.ItemSelected += (s, e) =>
            {
                if (SelectionEnabled)
                {
                    SelectedItem = DataList.SelectedItem;
                }
                else
                {
                    DataList.SelectedItem = null;
                }

                ItemSelected?.Invoke(this, e);
            };

            DataList.Refreshing += (s, e) =>
            {
                Refreshing?.Invoke(this, e);
            };

            DataList.SetBinding(ListView.RowHeightProperty, new Binding("RowHeight", source: this));
        }
        #endregion Constructor

        #region Public Fields
        public event EventHandler Refreshing;
        public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;

        #endregion Public Fields

        #region Bindable properties
        public static readonly BindableProperty ActiveRowColorProperty =
            BindableProperty.Create(
                nameof(ActiveRowColor),
                typeof(Color),
                typeof(DataGrid),
                Color.FromRgb(128, 144, 160),
                coerceValue: (bindable, value) =>
                {
                    if (!(bindable as DataGrid).SelectionEnabled)
                        throw new InvalidOperationException("Datagrid must be SelectionEnabled=true to set ActiveRowColor");
                    return value;
                });

        public static readonly BindableProperty HeaderBackgroundProperty =
            BindableProperty.Create(
                nameof(HeaderBackground),
                typeof(Color),
                typeof(DataGrid),
                Color.White,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if (self._headerView != null && !self.HeaderBordersVisible)
                        self._headerView.BackgroundColor = (Color)newValue;
                });

        public static readonly BindableProperty BorderColorProperty =
            BindableProperty.Create(
                nameof(BorderColor),
                typeof(Color),
                typeof(DataGrid),
                Color.Black,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if (self.HeaderBordersVisible)
                        self._headerView.BackgroundColor = (Color)newValue;

                    if (self.Columns != null && self.ItemsSource != null)
                        self.Reload();
                });

        public static readonly BindableProperty RowsBackgroundColorPaletteProperty =
            BindableProperty.Create(nameof(RowsBackgroundColorPalette),
                typeof(IColorProvider),
                typeof(DataGrid),
                new PaletteCollection { default(Color) },
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if (self.Columns != null && self.ItemsSource != null)
                        self.Reload();
                });

        public static readonly BindableProperty RowsTextColorPaletteProperty =
            BindableProperty.Create(
                nameof(RowsTextColorPalette),
                typeof(IColorProvider),
                typeof(DataGrid),
                new PaletteCollection { Color.Black },
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if (self.Columns != null && self.ItemsSource != null)
                        self.Reload();
                });

        public static readonly BindableProperty ColumnsProperty =
            BindableProperty.Create(
                nameof(Columns),
                typeof(ColumnCollection),
                typeof(DataGrid),
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    (bindable as DataGrid).InitHeaderView();
                },
                defaultValueCreator: bindable => { return new ColumnCollection(); }
            );

        public static BindableProperty ItemsSourceProperty =
            BindableProperty.Create(
                propertyName: nameof(ItemsSource),
                returnType: typeof(IEnumerable),
                declaringType: typeof(DataGrid),
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    DataGrid self = bindable as DataGrid;
                    //ObservableCollection Tracking 
                    if (oldValue != null && oldValue is INotifyCollectionChanged)
                    {
                        (oldValue as INotifyCollectionChanged).CollectionChanged -= self.HandleItemsSourceCollectionChanged;
                    }

                    if (newValue != null && newValue is INotifyCollectionChanged)
                    {
                        (newValue as INotifyCollectionChanged).CollectionChanged += self.HandleItemsSourceCollectionChanged;

                        self.InternalItems = new ObservableCollection<object>(((IEnumerable<object>)newValue));
                        //Assign listview item source
                        self.DataList.ItemsSource = self.InternalItems;
                        self.DataList.SetBinding(ListView.ItemsSourceProperty, new Binding("ItemsSource", source: self));
                    }

                    if (self.SelectedItem != null && !self.InternalItems.Contains(self.SelectedItem))
                    {
                        self.SelectedItem = null;
                    }

                    if (self.NoDataView != null)
                    {
                        if (self.ItemsSource == null || self.InternalItems.Count() == 0)
                        {
                            self._noDataView.IsVisible = true;
                        }

                        else if (self._noDataView.IsVisible)
                        {
                            self._noDataView.IsVisible = false;
                        }
                    }
                });
        private void HandleItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {

            if (e.NewItems != null)
            {
                foreach (object item in e.NewItems)
                {
                    InternalItems.Add(item);
                }
            }

            if (e.OldItems != null)
            {
                foreach (object item in e.OldItems)
                {
                    InternalItems.Remove(item);
                }
            }
            if (SelectedItem != null && !InternalItems.Contains(SelectedItem))
            {
                SelectedItem = null;
            }
        }

        public static readonly BindableProperty RowHeightProperty =
            BindableProperty.Create(nameof(RowHeight), typeof(int), typeof(DataGrid), 40,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    self.DataList.RowHeight = (int)newValue;
                });


        public static readonly BindableProperty HeaderHeightProperty =
            BindableProperty.Create(nameof(HeaderHeight), typeof(int), typeof(DataGrid), 40,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    self._headerView.HeightRequest = (int)newValue;
                });

        public static readonly BindableProperty IsSortableProperty =
            BindableProperty.Create(nameof(IsSortable), typeof(bool), typeof(DataGrid), true);

        public static readonly BindableProperty FontSizeProperty =
            BindableProperty.Create(nameof(FontSize), typeof(double), typeof(DataGrid), 13.0);

        public static readonly BindableProperty FontFamilyProperty =
            BindableProperty.Create(
                nameof(FontFamily),
                typeof(string),
                typeof(DataGrid),
                Font.Default.FontFamily);

        public static readonly BindableProperty SelectedItemProperty =
            BindableProperty.Create(
                nameof(SelectedItem),
                typeof(object),
                typeof(DataGrid),
                null,
                BindingMode.TwoWay,
                coerceValue: (bindable, value) =>
                {
                    var self = bindable as DataGrid;
                    if (!self.SelectionEnabled && value != null)
                    {
                        throw new InvalidOperationException("Datagrid must be SelectionEnabled=true to set SelectedItem");
                    }
                    if (self.InternalItems != null && self.InternalItems.Contains(value))
                    {
                        return value;
                    }
                    else
                    {
                        return null;
                    }
                },
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if (self.DataList.SelectedItem != newValue)
                    {
                        self.DataList.SelectedItem = newValue;
                    }
                }
            );

        public static readonly BindableProperty SelectionEnabledProperty =
            BindableProperty.Create(nameof(SelectionEnabled), typeof(bool), typeof(DataGrid), true,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if (!self.SelectionEnabled && self.SelectedItem != null)
                    {
                        self.SelectedItem = null;
                    }
                });

        public static readonly BindableProperty PullToRefreshCommandProperty =
            BindableProperty.Create(nameof(PullToRefreshCommand), typeof(ICommand), typeof(DataGrid), null,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if (newValue == null)
                    {
                        self.DataList.IsPullToRefreshEnabled = false;
                        self.DataList.RefreshCommand = null;
                    }
                    else
                    {
                        self.DataList.IsPullToRefreshEnabled = true;
                        self.DataList.RefreshCommand = newValue as ICommand;
                    }
                });

        public static readonly BindableProperty IsRefreshingProperty =
            BindableProperty.Create(
                nameof(IsRefreshing),
                typeof(bool),
                typeof(DataGrid),
                false,
                BindingMode.TwoWay,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    (bindable as DataGrid).DataList.IsRefreshing = (bool)newValue;
                });

        public static readonly BindableProperty BorderThicknessProperty =
            BindableProperty.Create(
                nameof(BorderThickness),
                typeof(Thickness),
                typeof(DataGrid),
                new Thickness(1),
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    (bindable as DataGrid)._headerView.ColumnSpacing = ((Thickness)newValue).HorizontalThickness / 2;
                    (bindable as DataGrid)._headerView.Padding = ((Thickness)newValue).HorizontalThickness / 2;
                });

        public static readonly BindableProperty HeaderBordersVisibleProperty =
            BindableProperty.Create(
                nameof(HeaderBordersVisible),
                typeof(bool),
                typeof(DataGrid),
                true,
                propertyChanged: (bindable, oldValue, newValue) => (bindable as DataGrid)._headerView.BackgroundColor = (bool)newValue ? (bindable as DataGrid).BorderColor : (bindable as DataGrid).HeaderBackground);

        public static readonly BindableProperty SortedColumnIndexProperty =
            BindableProperty.Create(
                nameof(SortedColumnIndex),
                typeof(SortData),
                typeof(DataGrid),
                null,
                BindingMode.TwoWay,
                validateValue: (bindable, v) =>
                {
                    var self = bindable as DataGrid;
                    var sData = (SortData)v;

                    return
                        sData == null || //setted to null
                        self.Columns == null || // Columns binded but not setted
                        self.Columns.Count == 0 || //columns not setted yet
                        (sData.Index < self.Columns.Count && self.Columns.ElementAt(sData.Index).SortingEnabled);
                },
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if (oldValue != newValue)
                        self.SortItems((SortData)newValue);
                });


        public static readonly BindableProperty HeaderLabelStyleProperty =
            BindableProperty.Create(nameof(HeaderLabelStyle), typeof(Style), typeof(DataGrid));

        public static readonly BindableProperty AscendingIconProperty =
            BindableProperty.Create(
                nameof(AscendingIcon),
                typeof(ImageSource),
                typeof(DataGrid),
                ImageSource.FromResource("Xamarin.Forms.DataGrid.up.png",
                typeof(DataGrid).GetTypeInfo().Assembly));

        public static readonly BindableProperty DescendingIconProperty =
            BindableProperty.Create(
                nameof(DescendingIcon),
                typeof(ImageSource),
                typeof(DataGrid),
                ImageSource.FromResource("Xamarin.Forms.DataGrid.down.png",
                typeof(DataGrid).GetTypeInfo().Assembly));

        public static readonly BindableProperty DescendingIconStyleProperty =
            BindableProperty.Create(nameof(DescendingIconStyle), typeof(Style), typeof(DataGrid), null,

                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    var style = (newValue as Style).Setters.FirstOrDefault(x => x.Property == Image.SourceProperty);
                    if (style != null)
                    {
                        if (style.Value is string vs)
                            self.DescendingIcon = ImageSource.FromFile(vs);
                        else
                            self.DescendingIcon = (ImageSource)style.Value;
                    }
                });

        public static readonly BindableProperty AscendingIconStyleProperty =
            BindableProperty.Create(nameof(AscendingIconStyle), typeof(Style), typeof(DataGrid), null,
                coerceValue: (bindable, v) =>
                {
                    var self = bindable as DataGrid;

                    return v;
                },

                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    var self = bindable as DataGrid;
                    if ((newValue as Style).Setters.Any(x => x.Property == Image.SourceProperty))
                    {
                        var style = (newValue as Style).Setters.FirstOrDefault(x => x.Property == Image.SourceProperty);
                        if (style != null)
                        {
                            if (style.Value is string vs)
                                self.AscendingIcon = ImageSource.FromFile(vs);
                            else
                                self.AscendingIcon = (ImageSource)style.Value;
                        }
                    }
                });

        public static readonly BindableProperty NoDataViewProperty =
            BindableProperty.Create(nameof(NoDataView), typeof(View), typeof(DataGrid),
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    if (oldValue != newValue)
                        (bindable as DataGrid)._noDataView.Content = newValue as View;
                });
        #endregion

        #region Properties
        public Color ActiveRowColor
        {
            get { return (Color)GetValue(ActiveRowColorProperty); }
            set { SetValue(ActiveRowColorProperty, value); }
        }

        public Color HeaderBackground
        {
            get { return (Color)GetValue(HeaderBackgroundProperty); }
            set { SetValue(HeaderBackgroundProperty, value); }
        }

        [Obsolete("Please use HeaderLabelStyle", true)]
        public Color HeaderTextColor
        {
            get; set;
        }

        public Color BorderColor
        {
            get { return (Color)GetValue(BorderColorProperty); }
            set { SetValue(BorderColorProperty, value); }
        }

        public IColorProvider RowsBackgroundColorPalette
        {
            get { return (IColorProvider)GetValue(RowsBackgroundColorPaletteProperty); }
            set { SetValue(RowsBackgroundColorPaletteProperty, value); }
        }

        public IColorProvider RowsTextColorPalette
        {
            get { return (IColorProvider)GetValue(RowsTextColorPaletteProperty); }
            set { SetValue(RowsTextColorPaletteProperty, value); }
        }

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        internal ObservableCollection<object> InternalItems
        {
            get { return _internalItems; }
            set
            {
                if (value != _internalItems)
                {
                    _internalItems = value;
                    if (IsSortable && SortedColumnIndex != null)
                    {
                        SortItems(SortedColumnIndex);
                    }
                }
                DataList.ItemsSource = _internalItems;
            }
        }

        public ColumnCollection Columns
        {
            get { return (ColumnCollection)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }

        public double FontSize
        {
            get { return (double)GetValue(FontSizeProperty); }
            set { SetValue(FontSizeProperty, value); }
        }

        [Obsolete("Please use HeaderLabelStyle", true)]
        public double HeaderFontSize
        {
            get; set;
        }

        public string FontFamily
        {
            get { return (string)GetValue(FontFamilyProperty); }
            set { SetValue(FontFamilyProperty, value); }
        }

        public int RowHeight
        {
            get { return (int)GetValue(RowHeightProperty); }
            set { SetValue(RowHeightProperty, value); }
        }

        public int HeaderHeight
        {
            get { return (int)GetValue(HeaderHeightProperty); }
            set { SetValue(HeaderHeightProperty, value); }
        }

        public bool IsSortable
        {
            get { return (bool)GetValue(IsSortableProperty); }
            set { SetValue(IsSortableProperty, value); }
        }

        public bool SelectionEnabled
        {
            get { return (bool)GetValue(SelectionEnabledProperty); }
            set { SetValue(SelectionEnabledProperty, value); }
        }

        public object SelectedItem
        {
            get { return GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        public ICommand PullToRefreshCommand
        {
            get { return (ICommand)GetValue(PullToRefreshCommandProperty); }
            set { SetValue(PullToRefreshCommandProperty, value); }
        }

        public bool IsRefreshing
        {
            get { return (bool)GetValue(IsRefreshingProperty); }
            set { SetValue(IsRefreshingProperty, value); }
        }

        public Thickness BorderThickness
        {
            get { return (Thickness)GetValue(BorderThicknessProperty); }
            set { SetValue(BorderThicknessProperty, value); }
        }

        public bool HeaderBordersVisible
        {
            get { return (bool)GetValue(HeaderBordersVisibleProperty); }
            set { SetValue(HeaderBordersVisibleProperty, value); }
        }

        public SortData SortedColumnIndex
        {
            get { return (SortData)GetValue(SortedColumnIndexProperty); }
            set { SetValue(SortedColumnIndexProperty, value); }
        }

        public Style HeaderLabelStyle
        {
            get { return (Style)GetValue(HeaderLabelStyleProperty); }
            set { SetValue(HeaderLabelStyleProperty, value); }
        }

        public ImageSource AscendingIcon
        {
            get { return (ImageSource)GetValue(AscendingIconProperty); }
            set { SetValue(AscendingIconProperty, value); }
        }

        public ImageSource DescendingIcon
        {
            get { return (ImageSource)GetValue(DescendingIconProperty); }
            set { SetValue(DescendingIconProperty, value); }
        }

        public Style AscendingIconStyle
        {
            get { return (Style)GetValue(AscendingIconStyleProperty); }
            set { SetValue(AscendingIconStyleProperty, value); }
        }

        public Style DescendingIconStyle
        {
            get { return (Style)GetValue(DescendingIconStyleProperty); }
            set { SetValue(DescendingIconStyleProperty, value); }
        }

        public View NoDataView
        {
            get { return (View)GetValue(NoDataViewProperty); }
            set { SetValue(NoDataViewProperty, value); }
        }
        #endregion

        #region UI Methods
        protected override void OnParentSet()
        {
            base.OnParentSet();
            InitHeaderView();
        }

        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();
            SetColumnsBindingContext();
        }

        #endregion

        #region Private Methods

        private void Reload()
        {
            InternalItems = new ObservableCollection<object>(_internalItems);
        }
        private void SortItems(SortData sData)
        {
            if (InternalItems == null || sData.Index >= Columns.Count || !Columns[sData.Index].SortingEnabled)
                return;

            var items = InternalItems;
            var column = Columns[sData.Index];
            SortingOrder order = sData.Order;

            if (!IsSortable)
                throw new InvalidOperationException("This DataGrid is not sortable");
            else if (column.PropertyName == null)
                throw new InvalidOperationException("Please set the PropertyName property of Column");

            //Sort
            // if (order == SortingOrder.Descendant)
            //     items = items.OrderByDescending(x => ReflectionUtils.GetValueByPath(x, column.PropertyName)).ToList();
            // else
            //     items = items.OrderBy(x => ReflectionUtils.GetValueByPath(x, column.PropertyName)).ToList();

            column.SortingIcon.Style = (order == SortingOrder.Descendant) ?
                AscendingIconStyle ?? (Style)_headerView.Resources["DescendingIconStyle"] :
                DescendingIconStyle ?? (Style)_headerView.Resources["AscendingIconStyle"];

            //Support DescendingIcon property (if setted)
            if (!column.SortingIcon.Style.Setters.Any(x => x.Property == Image.SourceProperty))
            {
                if (order == SortingOrder.Descendant && DescendingIconProperty.DefaultValue != DescendingIcon)
                    column.SortingIcon.Source = DescendingIcon;
                if (order == SortingOrder.Ascendant && AscendingIconProperty.DefaultValue != AscendingIcon)
                    column.SortingIcon.Source = AscendingIcon;
            }

            for (int i = 0; i < Columns.Count; i++)
            {
                if (i != sData.Index)
                {
                    if (Columns[i].SortingIcon.Style != null)
                        Columns[i].SortingIcon.Style = null;
                    if (Columns[i].SortingIcon.Source != null)
                        Columns[i].SortingIcon.Source = null;
                    _sortingOrders[i] = SortingOrder.None;
                }
            }

            _internalItems = items;

            _sortingOrders[sData.Index] = order;
            SortedColumnIndex = sData;
        }

        private View GetHeaderViewForColumn(DataGridColumn column)
        {
            column.HeaderLabel.Style = column.HeaderLabelStyle ?? this.HeaderLabelStyle ?? (Style)_headerView.Resources["HeaderDefaultStyle"];

            Grid grid = new Grid
            {
                ColumnSpacing = 0,
            };

            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });

            if (IsSortable)
            {
                column.SortingIcon.Style = (Style)_headerView.Resources["ImageStyleBase"];

                grid.Children.Add(column.SortingIcon);
                Grid.SetColumn(column.SortingIcon, 1);

                TapGestureRecognizer tgr = new TapGestureRecognizer();
                tgr.Tapped += (s, e) =>
                {
                    int index = Columns.IndexOf(column);
                    SortingOrder order = _sortingOrders[index] == SortingOrder.Ascendant ? SortingOrder.Descendant : SortingOrder.Ascendant;

                    if (Columns.ElementAt(index).SortingEnabled)
                        SortedColumnIndex = new SortData(index, order);
                };
                grid.GestureRecognizers.Add(tgr);
            }

            grid.Children.Add(column.HeaderLabel);

            return grid;
        }

        private void InitHeaderView()
        {
            SetColumnsBindingContext();
            _headerView.Children.Clear();
            _headerView.ColumnDefinitions.Clear();
            _sortingOrders.Clear();

            _headerView.Padding = new Thickness(BorderThickness.Left, BorderThickness.Top, BorderThickness.Right, 0);
            _headerView.ColumnSpacing = BorderThickness.HorizontalThickness / 2;

            if (Columns != null)
            {
                foreach (var col in Columns)
                {
                    _headerView.ColumnDefinitions.Add(new ColumnDefinition { Width = col.Width });

                    var cell = GetHeaderViewForColumn(col);

                    _headerView.Children.Add(cell);
                    Grid.SetColumn(cell, Columns.IndexOf(col));

                    _sortingOrders.Add(Columns.IndexOf(col), SortingOrder.None);
                }
            }
        }

        private void SetColumnsBindingContext()
        {
            if (Columns != null)
                foreach (var c in Columns)
                    c.BindingContext = BindingContext;
        }

        #endregion Private Methods
    }
}
