using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Calendar.Args;
using Wino.Calendar.ViewModels.Data;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Calendar;
using Wino.Helpers;

namespace Wino.Calendar.Controls
{
    public class WinoCalendarControl : Control
    {
        private const string PART_WinoFlipView = nameof(PART_WinoFlipView);
        private const string PART_IdleGrid = nameof(PART_IdleGrid);

        public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
        public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;

        public event EventHandler ScrollPositionChanging;

        #region Dependency Properties

        public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection<DayRangeRenderModel>), typeof(WinoCalendarControl), new PropertyMetadata(null));
        public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1));
        public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null));
        public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveCanvasChanged)));
        public static readonly DependencyProperty IsFlipIdleProperty = DependencyProperty.Register(nameof(IsFlipIdle), typeof(bool), typeof(WinoCalendarControl), new PropertyMetadata(true, new PropertyChangedCallback(OnIdleStateChanged)));
        public static readonly DependencyProperty ActiveScrollViewerProperty = DependencyProperty.Register(nameof(ActiveScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveVerticalScrollViewerChanged)));

        public static readonly DependencyProperty VerticalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(VerticalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
        public static readonly DependencyProperty HorizontalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(HorizontalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
        public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(CalendarOrientation), typeof(WinoCalendarControl), new PropertyMetadata(CalendarOrientation.Horizontal, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
        public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(CalendarDisplayType), typeof(WinoCalendarControl), new PropertyMetadata(CalendarDisplayType.Day));

        /// <summary>
        /// Gets or sets the day-week-month-year display type.
        /// Orientation is not determined by this property, but Orientation property.
        /// This property is used to determine the template to use for the calendar.
        /// </summary>
        public CalendarDisplayType DisplayType
        {
            get { return (CalendarDisplayType)GetValue(DisplayTypeProperty); }
            set { SetValue(DisplayTypeProperty, value); }
        }

        public CalendarOrientation Orientation
        {
            get { return (CalendarOrientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        public ItemsPanelTemplate VerticalItemsPanelTemplate
        {
            get { return (ItemsPanelTemplate)GetValue(VerticalItemsPanelTemplateProperty); }
            set { SetValue(VerticalItemsPanelTemplateProperty, value); }
        }

        public ItemsPanelTemplate HorizontalItemsPanelTemplate
        {
            get { return (ItemsPanelTemplate)GetValue(HorizontalItemsPanelTemplateProperty); }
            set { SetValue(HorizontalItemsPanelTemplateProperty, value); }
        }

        public DayRangeRenderModel SelectedFlipViewDayRange
        {
            get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); }
            set { SetValue(SelectedFlipViewDayRangeProperty, value); }
        }

        public ScrollViewer ActiveScrollViewer
        {
            get { return (ScrollViewer)GetValue(ActiveScrollViewerProperty); }
            set { SetValue(ActiveScrollViewerProperty, value); }
        }

        public WinoDayTimelineCanvas ActiveCanvas
        {
            get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
            set { SetValue(ActiveCanvasProperty, value); }
        }

        public bool IsFlipIdle
        {
            get { return (bool)GetValue(IsFlipIdleProperty); }
            set { SetValue(IsFlipIdleProperty, value); }
        }

        /// <summary>
        /// Gets or sets the collection of day ranges to render.
        /// Each day range usually represents a week, but it may support other ranges.
        /// </summary>
        public ObservableCollection<DayRangeRenderModel> DayRanges
        {
            get { return (ObservableCollection<DayRangeRenderModel>)GetValue(DayRangesProperty); }
            set { SetValue(DayRangesProperty, value); }
        }

        public int SelectedFlipViewIndex
        {
            get { return (int)GetValue(SelectedFlipViewIndexProperty); }
            set { SetValue(SelectedFlipViewIndexProperty, value); }
        }

        #endregion

        private WinoCalendarFlipView InternalFlipView;
        private Grid IdleGrid;

        public WinoCalendarControl()
        {
            DefaultStyleKey = typeof(WinoCalendarControl);
            SizeChanged += CalendarSizeChanged;
        }

        private static void OnCalendarOrientationPropertiesUpdated(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
        {
            if (calendar is WinoCalendarControl control)
            {
                control.ManageCalendarOrientation();
            }
        }

        private static void OnIdleStateChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
        {
            if (calendar is WinoCalendarControl calendarControl)
            {
                calendarControl.UpdateIdleState();
            }
        }


        private static void OnActiveVerticalScrollViewerChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
        {
            if (calendar is WinoCalendarControl calendarControl)
            {
                if (e.OldValue is ScrollViewer oldScrollViewer)
                {
                    calendarControl.DeregisterScrollChanges(oldScrollViewer);
                }

                if (e.NewValue is ScrollViewer newScrollViewer)
                {
                    calendarControl.RegisterScrollChanges(newScrollViewer);
                }

                calendarControl.ManageHighlightedDateRange();
            }
        }


        private static void OnActiveCanvasChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
        {
            if (calendar is WinoCalendarControl calendarControl)
            {
                if (e.OldValue is WinoDayTimelineCanvas oldCanvas)
                {
                    // Dismiss any selection on the old canvas.
                    calendarControl.DeregisterCanvas(oldCanvas);
                }

                if (e.NewValue is WinoDayTimelineCanvas newCanvas)
                {
                    calendarControl.RegisterCanvas(newCanvas);
                }

                calendarControl.ManageHighlightedDateRange();
            }
        }

        private void ManageCalendarOrientation()
        {
            if (InternalFlipView == null || HorizontalItemsPanelTemplate == null || VerticalItemsPanelTemplate == null) return;

            InternalFlipView.ItemsPanel = Orientation == CalendarOrientation.Horizontal ? HorizontalItemsPanelTemplate : VerticalItemsPanelTemplate;
        }

        private void ManageHighlightedDateRange()
            => SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel;

        private void DeregisterCanvas(WinoDayTimelineCanvas canvas)
        {
            if (canvas == null) return;

            canvas.SelectedDateTime = null;
            canvas.TimelineCellSelected -= ActiveTimelineCellSelected;
            canvas.TimelineCellUnselected -= ActiveTimelineCellUnselected;
        }

        private void RegisterCanvas(WinoDayTimelineCanvas canvas)
        {
            if (canvas == null) return;

            canvas.SelectedDateTime = null;
            canvas.TimelineCellSelected += ActiveTimelineCellSelected;
            canvas.TimelineCellUnselected += ActiveTimelineCellUnselected;
        }

        private void RegisterScrollChanges(ScrollViewer scrollViewer)
        {
            if (scrollViewer == null) return;

            scrollViewer.ViewChanging += ScrollViewChanging;
        }

        private void DeregisterScrollChanges(ScrollViewer scrollViewer)
        {
            if (scrollViewer == null) return;

            scrollViewer.ViewChanging -= ScrollViewChanging;
        }

        private void ScrollViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
            => ScrollPositionChanging?.Invoke(this, EventArgs.Empty);

        private void CalendarSizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (ActiveCanvas == null) return;

            ActiveCanvas.SelectedDateTime = null;
        }

        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView;
            IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid;

            UpdateIdleState();
            ManageCalendarOrientation();
        }

        private void UpdateIdleState()
        {
            InternalFlipView.Opacity = IsFlipIdle ? 0 : 1;
            IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed;
        }

        private void ActiveTimelineCellUnselected(object sender, TimelineCellUnselectedArgs e)
            => TimelineCellUnselected?.Invoke(this, e);

        private void ActiveTimelineCellSelected(object sender, TimelineCellSelectedArgs e)
            => TimelineCellSelected?.Invoke(this, e);

        public void NavigateToDay(DateTime dateTime) => InternalFlipView.NavigateToDay(dateTime);

        public async void NavigateToHour(TimeSpan timeSpan)
        {
            if (ActiveScrollViewer == null) return;

            // Total height of the FlipViewItem is the same as vertical ScrollViewer to position day headers.

            await Task.Yield();
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
            {
                double hourHeght = 60;
                double totalHeight = ActiveScrollViewer.ScrollableHeight;
                double scrollPosition = timeSpan.TotalHours * hourHeght;

                ActiveScrollViewer.ChangeView(null, scrollPosition, null, disableAnimation: false);
            });
        }
        public void ResetTimelineSelection()
        {
            if (ActiveCanvas == null) return;

            ActiveCanvas.SelectedDateTime = null;
        }

        public void GoNextRange()
        {
            if (InternalFlipView == null) return;

            InternalFlipView.GoNextFlip();
        }

        public void GoPreviousRange()
        {
            if (InternalFlipView == null) return;

            InternalFlipView.GoPreviousFlip();
        }

        public void UnselectActiveTimelineCell()
        {
            if (ActiveCanvas == null) return;

            ActiveCanvas.SelectedDateTime = null;
        }

        public CalendarItemControl GetCalendarItemControl(CalendarItemViewModel calendarItemViewModel)
        {
            return this.FindDescendants<CalendarItemControl>().FirstOrDefault(a => a.CalendarItem == calendarItemViewModel);
        }
    }
}
