#if USE_TIMELINE
#if UNITY_2017_1_OR_NEWER
// Copyright (c) Pixel Crushers. All rights reserved.
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using System.Collections;
namespace PixelCrushers.DialogueSystem.SequencerCommands
{
    /// 
    /// Sequencer command Timeline(mode, timeline, [nowait], [nostop], [#:binding]...)
    /// or Timeline(speed, timeline, value)
    /// 
    /// Controls a Timeline (PlayableDirector).
    /// 
    /// - mode: Can be play, pause, resume, stop, or speed. If speed, the third parameter should be the new speed.
    /// - timeline: Name of a GameObject with a PlayableDirector, or a Timeline asset in a Resources folder or asset bundle. Default: speaker.
    /// - nowait: If specified, doesn't wait for the director to finish.
    /// - nostop: If specified, doesn't force the director to stop at the end of the sequencer command.
    /// - #:binding: If specified, the number indicates the track number, starting from zero. The binding is the name of a GameObject to bind to that track.
    /// 
    public class SequencerCommandTimeline : SequencerCommand
    {
        private PlayableDirector playableDirector = null;
        private TimelineAsset timelineAsset = null;
        private string mode;
        private Transform subject;
        private bool nostop = false;
        private bool nowait = false;
        private bool mustDestroyPlayableDirector = false;
        private bool mustDestroyPlayableAsset = false;
        public IEnumerator Start()
        {
            if (parameters == null || parameters.Length == 0)
            {
                Stop();
                yield break;
            }
            mode = GetParameter(0).ToLower();
            subject = GetSubject(1, Sequencer.Speaker);
            nowait = string.Equals(GetParameter(2), "nowait", System.StringComparison.OrdinalIgnoreCase) ||
                string.Equals(GetParameter(3), "nowait", System.StringComparison.OrdinalIgnoreCase);
            nostop = string.Equals(GetParameter(2), "nostop", System.StringComparison.OrdinalIgnoreCase) ||
                string.Equals(GetParameter(3), "nostop", System.StringComparison.OrdinalIgnoreCase);
            playableDirector = (subject != null) ? subject.GetComponent() : null;
            // If no suitable PlayableDirector was found, look for a Timeline asset in Resources:
            if (playableDirector == null || playableDirector.playableAsset == null)
            {
                DialogueManager.LoadAsset(GetParameter(1), typeof(TimelineAsset),
                    (asset) =>
                    {
                        timelineAsset = asset as TimelineAsset;
                        if (timelineAsset != null)
                        {
                            if (playableDirector == null)
                            {
                                playableDirector = new GameObject(GetParameter(1), typeof(PlayableDirector)).GetComponent();
                                mustDestroyPlayableDirector = true;
                            }
                            playableDirector.playableAsset = timelineAsset;
                            mustDestroyPlayableAsset = true;
                        }
                        StartCoroutine(Proceed());
                    });
            }
            else
            {
                yield return Proceed();
            }
        }
        private IEnumerator Proceed()
        { 
            if (playableDirector == null)
            {
                if (DialogueDebug.LogWarnings) Debug.LogWarning("Dialogue System: Sequencer: Timeline(" + GetParameters() +
                    "): Can't find playable director '" + GetParameter(1) + "'.");
            }
            else if (playableDirector.playableAsset == null)
            {
                if (DialogueDebug.LogWarnings) Debug.LogWarning("Dialogue System: Sequencer: Timeline(" + GetParameters() +
                    "): No timeline asset is assigned to the Playable Director.", playableDirector);
            }
            else
            {
                var isModeValid = (mode == "play" || mode == "pause" || mode == "resume" || mode == "stop" || mode == "speed");
                if (!isModeValid)
                {
                    if (DialogueDebug.LogWarnings) Debug.LogWarning("Dialogue System: Sequencer: Timeline(" + GetParameters() +
                        "): Invalid mode '" + mode + "'. Expected 'play', 'pause', 'resume', or 'stop'.");
                }
                else
                {
                    if (DialogueDebug.LogInfo) Debug.Log("Dialogue System: Sequencer: Timeline(" + GetParameters() + ")");
                    // Check for rebindings:
                    timelineAsset = playableDirector.playableAsset as TimelineAsset;
                    if (timelineAsset != null)
                    {
                        for (int i = 2; i < Parameters.Length; i++)
                        {
                            var s = GetParameter(i);
                            if (s.Contains(":"))
                            {
                                var colonPos = s.IndexOf(":");
                                var trackIndex = Tools.StringToInt(s.Substring(0, colonPos));
                                var bindName = s.Substring(colonPos + 1);
                                var track = timelineAsset.GetOutputTrack(trackIndex);
                                if (track != null)
                                {
                                    playableDirector.SetGenericBinding(track, Tools.GameObjectHardFind(bindName));
                                }
                            }
                        }
                    }
                    switch (mode)
                    {
                        case "play":
                            playableDirector.Play();
                            var endTime = nowait ? 0 : DialogueTime.time + playableDirector.playableAsset.duration;
                            while (DialogueTime.time < endTime || playableDirector.extrapolationMode == DirectorWrapMode.Loop)
                            {
                                yield return null;
                            }
                            break;
                        case "pause":
                            playableDirector.Pause();
                            nostop = true;
                            break;
                        case "resume":
                            playableDirector.Resume();
                            var resumedEndTime = nowait ? 0 : DialogueTime.time + playableDirector.playableAsset.duration - playableDirector.time;
                            while (DialogueTime.time < resumedEndTime || playableDirector.extrapolationMode == DirectorWrapMode.Loop)
                            {
                                yield return null;
                            }
                            break;
                        case "stop":
                            playableDirector.Stop();
                            nostop = false;
                            break;
                        case "speed":
                            playableDirector.playableGraph.GetRootPlayable(0).SetSpeed(GetParameterAsFloat(2));
                            nostop = true;
                            break;
                        default:
                            isModeValid = false;
                            break;
                    }
                }
            }
            Stop();
        }
        public void OnDestroy()
        {
            if (playableDirector != null && !nostop)
            {
                playableDirector.Stop();
                if (mustDestroyPlayableAsset)
                {
                    DialogueManager.UnloadAsset(playableDirector.playableAsset);
                    playableDirector.playableAsset = null;
                }
                if (mustDestroyPlayableDirector) Destroy(playableDirector.gameObject);
            }
        }
    }
}
#endif
#endif