using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;

namespace AdRotatorExtensions
{
	public static class AdRotatorExtensions
	{
		public static readonly DependencyProperty ItemsSourceProperty =
			DependencyProperty.RegisterAttached("ItemsSource", typeof(IEnumerable), typeof(AdRotatorExtensions), new PropertyMetadata(null, OnItemsSourceChanged));

		public static readonly DependencyProperty ItemChangeDelayProperty =
			DependencyProperty.RegisterAttached("ItemChangeDelay", typeof(Duration), typeof(AdRotatorExtensions), new PropertyMetadata(new Duration(TimeSpan.FromSeconds(0.3))));

		public static readonly DependencyProperty CurrentSelectedIndexProperty =
			DependencyProperty.RegisterAttached("CurrentSelectedIndex", typeof(int), typeof(AdRotatorExtensions), new PropertyMetadata(-1, OnCurrentSelectedIndexChanged));

		private static readonly DependencyProperty TimerProperty =
			DependencyProperty.RegisterAttached("Timer", typeof(DispatcherTimer), typeof(AdRotatorExtensions), null);

		public static IEnumerable GetItemsSource(DependencyObject obj)
		{
			return (IEnumerable)obj.GetValue(ItemsSourceProperty);
		}

		public static void SetItemsSource(DependencyObject obj, IEnumerable value)
		{
			obj.SetValue(ItemsSourceProperty, value);
		}

		public static Duration GetItemChangeDelay(DependencyObject obj)
		{
			return (Duration)obj.GetValue(ItemChangeDelayProperty);
		}

		public static void SetItemChangeDelay(DependencyObject obj, Duration value)
		{
			obj.SetValue(ItemChangeDelayProperty, value);
		}

		public static int GetCurrentSelectedIndex(DependencyObject obj)
		{
			return (int)obj.GetValue(CurrentSelectedIndexProperty);
		}

		public static void SetCurrentSelectedIndex(DependencyObject obj, int value)
		{
			obj.SetValue(CurrentSelectedIndexProperty, value);
		}

		private static DispatcherTimer GetTimer(DependencyObject obj)
		{
			return (DispatcherTimer)obj.GetValue(TimerProperty);
		}

		private static void SetTimer(DependencyObject obj, DispatcherTimer value)
		{
			obj.SetValue(TimerProperty, value);
		}

		private static void OnCurrentSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			UpdateCurrentlySelectedItem(d);
		}

		private static void MoveToNextElement(DependencyObject element)
		{
			var source = GetItemsSource(element);
			if (source != null)
			{
				var convertedSource = source.Cast<object>();
				var currentIndex = GetCurrentSelectedIndex(element);

				currentIndex = ++currentIndex % convertedSource.Count();
				SetCurrentSelectedIndex(element, currentIndex);
			}
		}

		private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var element = d as FrameworkElement;
			var itemsControl = d as ItemsControl;

			var oldValue = e.OldValue as IEnumerable;
			var newValue = e.NewValue as IEnumerable;

			if (element != null)
			{
				if (oldValue != null)
				{
					// Detach the Ad Rotator functionality.
					element.Loaded -= OnElementLoaded;
					element.Unloaded -= OnElementUnloaded;

					// If there is a timer attached, stop it.
					var timer = GetTimer(element);
					if (timer != null)
					{
						timer.Stop();
					}
				}

				if (newValue != null)
				{
					// Attach the Ad Rotator functionality.
					element.Loaded += OnElementLoaded;
					element.Unloaded += OnElementUnloaded;

					// If the target is an ItemsControl and its ItemsSource is not set, set it.
					if (itemsControl != null && itemsControl.ItemsSource == null && itemsControl.Items.Count == 0)
					{
						itemsControl.ItemsSource = newValue;
					}
				}
			}
		}

		private static void OnElementLoaded(object sender, RoutedEventArgs args)
		{
			var element = sender as DependencyObject;

			// Create the timer and hook-up to the events.
			var timer = new DispatcherTimer();
			timer.Interval = GetItemChangeDelay(element).TimeSpan;
			SetTimer(element, timer);

			timer.Tick += (s, e) => MoveToNextElement(element);

			timer.Start();

			// Make sure the currently pointed element is selected.
			UpdateCurrentlySelectedItem(element);
		}

		private static void OnElementUnloaded(object sender, RoutedEventArgs args)
		{
			var element = sender as FrameworkElement;

			GetTimer(element).Stop();
		}

		private static void UpdateCurrentlySelectedItem(DependencyObject element)
		{
			var contentControl = element as ContentControl;

			var source = GetItemsSource(element);

			// If there is no source we shouldn't do anything.
			if (source == null) return;

			// Find the actual index to be selected (if outside the boundaries of the collection)
			// and find the actual element to be selected.
			var convertedSource = source.Cast<object>();
			var currentIndex = GetCurrentSelectedIndex(element);
			var elementToSelect = convertedSource.ElementAtOrDefault(currentIndex);

			// Update the cotnent of the ContentControl if attached to a ContentControl.
			if (contentControl != null)
			{
				contentControl.Content = elementToSelect;
			}
		}
	}
}