using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Xamarin.Forms
{
	internal class NavigationProxy : INavigation
	{
		INavigation _inner;
		Lazy<List<Page>> _modalStack = new Lazy<List<Page>>(() => new List<Page>());

		Lazy<List<Page>> _pushStack = new Lazy<List<Page>>(() => new List<Page>());

		public INavigation Inner
		{
			get { return _inner; }
			set
			{
				if (_inner == value)
					return;
				_inner = value;
				// reverse so that things go into the new stack in the same order
				// null out to release memory that will likely never be needed again

				if (ReferenceEquals(_inner, null))
				{
					_pushStack = new Lazy<List<Page>>(() => new List<Page>());
					_modalStack = new Lazy<List<Page>>(() => new List<Page>());
				}
				else
				{
					if (_pushStack != null && _pushStack.IsValueCreated)
					{
						foreach (Page page in _pushStack.Value)
						{
							_inner.PushAsync(page);
						}
					}

					if (_modalStack != null && _modalStack.IsValueCreated)
					{
						foreach (Page page in _modalStack.Value)
						{
							_inner.PushModalAsync(page);
						}
					}

					_pushStack = null;
					_modalStack = null;
				}
			}
		}

		public void InsertPageBefore(Page page, Page before)
		{
			OnInsertPageBefore(page, before);
		}

		public IReadOnlyList<Page> ModalStack
		{
			get { return GetModalStack(); }
		}

		public IReadOnlyList<Page> NavigationStack
		{
			get { return GetNavigationStack(); }
		}

		public Task<Page> PopAsync()
		{
			return OnPopAsync(true);
		}

		public Task<Page> PopAsync(bool animated)
		{
			return OnPopAsync(animated);
		}

		public Task<Page> PopModalAsync()
		{
			return OnPopModal(true);
		}

		public Task<Page> PopModalAsync(bool animated)
		{
			return OnPopModal(animated);
		}

		public Task PopToRootAsync()
		{
			return OnPopToRootAsync(true);
		}

		public Task PopToRootAsync(bool animated)
		{
			return OnPopToRootAsync(animated);
		}

		public Task PushAsync(Page root)
		{
			return PushAsync(root, true);
		}

		public Task PushAsync(Page root, bool animated)
		{
			if (root.RealParent != null)
				throw new InvalidOperationException("Page must not already have a parent.");
			return OnPushAsync(root, animated);
		}

		public Task PushModalAsync(Page modal)
		{
			return PushModalAsync(modal, true);
		}

		public Task PushModalAsync(Page modal, bool animated)
		{
			if (modal.RealParent != null)
				throw new InvalidOperationException("Page must not already have a parent.");
			return OnPushModal(modal, animated);
		}

		public void RemovePage(Page page)
		{
			OnRemovePage(page);
		}

		protected virtual IReadOnlyList<Page> GetModalStack()
		{
			INavigation currentInner = Inner;
			return currentInner == null ? _modalStack.Value : currentInner.ModalStack;
		}

		protected virtual IReadOnlyList<Page> GetNavigationStack()
		{
			INavigation currentInner = Inner;
			return currentInner == null ? _pushStack.Value : currentInner.NavigationStack;
		}

		protected virtual void OnInsertPageBefore(Page page, Page before)
		{
			INavigation currentInner = Inner;
			if (currentInner == null)
			{
				int index = _pushStack.Value.IndexOf(before);
				if (index == -1)
					throw new ArgumentException("before must be in the pushed stack of the current context");
				_pushStack.Value.Insert(index, page);
			}
			else
			{
				currentInner.InsertPageBefore(page, before);
			}
		}

		protected virtual Task<Page> OnPopAsync(bool animated)
		{
			INavigation inner = Inner;
			return inner == null ? Task.FromResult(Pop()) : inner.PopAsync(animated);
		}

		protected virtual Task<Page> OnPopModal(bool animated)
		{
			INavigation innerNav = Inner;
			return innerNav == null ? Task.FromResult(PopModal()) : innerNav.PopModalAsync(animated);
		}

		protected virtual Task OnPopToRootAsync(bool animated)
		{
			INavigation currentInner = Inner;
			if (currentInner == null)
			{
				Page root = _pushStack.Value.Last();
				_pushStack.Value.Clear();
				_pushStack.Value.Add(root);
				return Task.FromResult(root);
			}
			return currentInner.PopToRootAsync(animated);
		}

		protected virtual Task OnPushAsync(Page page, bool animated)
		{
			INavigation currentInner = Inner;
			if (currentInner == null)
			{
				_pushStack.Value.Add(page);
				return Task.FromResult(page);
			}
			return currentInner.PushAsync(page, animated);
		}

		protected virtual Task OnPushModal(Page modal, bool animated)
		{
			INavigation currentInner = Inner;
			if (currentInner == null)
			{
				_modalStack.Value.Add(modal);
				return Task.FromResult<object>(null);
			}
			return currentInner.PushModalAsync(modal, animated);
		}

		protected virtual void OnRemovePage(Page page)
		{
			INavigation currentInner = Inner;
			if (currentInner == null)
			{
				_pushStack.Value.Remove(page);
			}
			else
			{
				currentInner.RemovePage(page);
			}
		}

		Page Pop()
		{
			List<Page> list = _pushStack.Value;
			Page result = list[list.Count - 1];
			list.RemoveAt(list.Count - 1);
			return result;
		}

		Page PopModal()
		{
			List<Page> list = _modalStack.Value;
			Page result = list[list.Count - 1];
			list.RemoveAt(list.Count - 1);
			return result;
		}
	}
}