using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;

namespace UnityEditor.Timeline
{
    partial class TimelineWindow
    {
        /// <summary>
        /// The public Breadcrumb navigation controller, accessible through TimelineEditorWindow
        /// </summary>
        public override TimelineNavigator navigator => new TimelineNavigator(this);

        /// <summary>
        /// Implementation of TimelineNavigator
        /// </summary>
        /// <remarks>
        /// Always use TimelineNavigator, not this class.
        ///
        /// The class acts as a handle on the TimelineWindow, and lets users navigate the breadcrumbs and dive into subtimelines
        /// </remarks>
        internal class TimelineNavigatorImpl
        {
            /// <summary>
            ///
            /// </summary>
            /// <param name="window"></param>
            public TimelineNavigatorImpl(IWindowStateProvider window)
            {
                if (window == null)
                    throw new ArgumentNullException(nameof(window),
                        "TimelineNavigator cannot be used with a null window");
                m_Window = window;
            }

            /// <summary>
            /// Creates a SequenceContext from the top of the breadcrumb stack
            /// </summary>
            /// <returns></returns>
            public SequenceContext GetCurrentContext()
            {
                return GetBreadcrumbs().LastOrDefault();
            }

            /// <summary>
            /// Creates a SequenceContext from the second to last breadcrumb in the list
            /// </summary>
            /// <returns>Valid context if there is a parent, Invalid context otherwise</returns>
            public SequenceContext GetParentContext()
            {
                //If the edit sequence is the master sequence, there is no parent context
                if (windowState.editSequence == windowState.masterSequence)
                    return SequenceContext.Invalid;
                var contexts = GetBreadcrumbs();
                var length = contexts.Count();
                return contexts.ElementAtOrDefault(length - 2);
            }

            /// <summary>
            /// Creates a SequenceContext from the top of the breadcrumb stack
            /// </summary>
            /// <returns>Always returns a valid SequenceContext</returns>
            public SequenceContext GetRootContext()
            {
                return GetBreadcrumbs().FirstOrDefault();
            }

            /// <summary>
            /// Creates SequenceContexts for all the child Timelines
            /// </summary>
            /// <returns>Collection of SequenceContexts. Can be empty if there are no valid child contexts</returns>
            public IEnumerable<SequenceContext> GetChildContexts()
            {
                return windowState.GetSubSequences();
            }

            /// <summary>
            /// Creates SequenceContexts from the breadcrumb stack, from top to bottom
            /// </summary>
            /// <returns>Collection of SequenceContexts. Should never be empty</returns>
            public IEnumerable<SequenceContext> GetBreadcrumbs()
            {
                return CollectBreadcrumbContexts();
            }

            /// <summary>
            /// Changes the current Timeline shown in the TimelineWindow to a new SequenceContext (if different and valid)
            /// </summary>
            /// <remarks>
            /// Should only ever accept SequenceContexts that are in the breadcrumbs. SetTimeline is the proper
            /// method to use to switch root Timelines.
            /// </remarks>
            /// <param name="context">A valid SequenceContext. <paramref name="context"/> should always be found in the breadcrumbs</param>
            /// <exception cref="System.ArgumentException"> The context is not valid</exception>
            /// <exception cref="System.InvalidOperationException"> The context is not a valid navigation destination.</exception>
            public void NavigateTo(SequenceContext context)
            {
                if (!context.IsValid())
                    throw new ArgumentException(
                        $"Argument {nameof(context)} is not valid. Check validity with SequenceContext.IsValid.");

                //If the provided context is the current context
                if (windowState.editSequence.hostClip == context.clip &&
                    windowState.editSequence.director == context.director &&
                    windowState.editSequence.asset == context.director.playableAsset)
                {
                    return; // Nothing to do
                }

                if (context.clip == null)
                {
                    if (context.director != windowState.masterSequence.director)
                        throw new InvalidOperationException($"{nameof(context)} is not a valid destination in this context. " +
                            $"To change the root context, use TimelineEditorWindow.SetTimeline instead.");
                }

                var children = GetChildContexts().ToArray();
                var breadcrumbs = CollectBreadcrumbContexts().ToArray();

                if (!children.Contains(context) && !breadcrumbs.Contains(context))
                {
                    throw new InvalidOperationException(
                        "The provided SequenceContext is not a valid destination. " +
                        "Use GetChildContexts or GetBreadcrumbs to acquire valid destination contexts.");
                }

                if (children.Contains(context))
                {
                    windowState.SetCurrentSequence(context.director.playableAsset as TimelineAsset, context.director, context.clip);
                    return;
                }

                var idx = Array.IndexOf(breadcrumbs, context);
                if (idx != -1)
                {
                    windowState.PopSequencesUntilCount(idx + 1);
                }
            }

            private IWindowState windowState
            {
                get
                {
                    if (m_Window == null || m_Window.windowState == null)
                        throw new InvalidOperationException("The Window associated to this instance has been destroyed");
                    return m_Window.windowState;
                }
            }

            private IEnumerable<SequenceContext> CollectBreadcrumbContexts()
            {
                return windowState.allSequences?.Select(s => new SequenceContext(s.director, s.hostClip));
            }

            private readonly IWindowStateProvider m_Window;
        }
    }
}