using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
using UnityEngineInternal; // for metro type extensions

namespace UnityEngine.Timeline
{
    public partial class TimelineAsset
    {
        /// <summary>
        /// Allows you to create a track and add it to the Timeline.
        /// </summary>
        /// <param name="type">The type of track to create. Must derive from TrackAsset.</param>
        /// <param name="parent">Track to parent to. This can be null.</param>
        /// <param name="name">Name to give the track.</param>
        /// <returns>The created track.</returns>
        /// <remarks>
        /// This method will throw an InvalidOperationException if the parent is not valid. The parent can be any GroupTrack, or a supported parent type of track. For example, this can be used to create override tracks in AnimationTracks.
        /// </remarks>
        public TrackAsset CreateTrack(Type type, TrackAsset parent, string name)
        {
            if (parent != null && parent.timelineAsset != this)
                throw new InvalidOperationException("Addtrack cannot parent to a track not in the Timeline");

            if (!typeof(TrackAsset).IsAssignableFrom(type))
                throw new InvalidOperationException("Supplied type must be a track asset");

            if (parent != null)
            {
                if (!TimelineCreateUtilities.ValidateParentTrack(parent, type))
                    throw new InvalidOperationException("Cannot assign a child of type " + type.Name + " to a parent of type " + parent.GetType().Name);
            }

            var baseName = name;
            if (string.IsNullOrEmpty(baseName))
            {
                baseName = type.Name;
#if UNITY_EDITOR
                baseName = UnityEditor.ObjectNames.NicifyVariableName(baseName);
#endif
            }

            var trackName = baseName;
            if (parent != null)
                trackName = TimelineCreateUtilities.GenerateUniqueActorName(parent.subTracksObjects, baseName);
            else
                trackName = TimelineCreateUtilities.GenerateUniqueActorName(trackObjects, baseName);

            TrackAsset newTrack = AllocateTrack(parent, trackName, type);

            return newTrack;
        }

        /// <summary>
        /// Creates a track and adds it to the Timeline Asset.
        /// </summary>
        /// <param name="parent">Track to parent to. This can be null.</param>
        /// <param name="trackName">The name of the track being created.</param>
        /// <typeparam name="T">The type of track being created. The track type must be derived from TrackAsset.</typeparam>
        /// <returns>Returns the created track.</returns>
        /// <remarks>
        /// This method will throw an InvalidOperationException if the parent is not valid. The parent can be any GroupTrack, or a supported parent type of track. For example, this can be used to create override tracks in AnimationTracks.
        /// </remarks>
        public T CreateTrack<T>(TrackAsset parent, string trackName) where T : TrackAsset, new()
        {
            return (T)CreateTrack(typeof(T), parent, trackName);
        }

        /// <summary>
        /// Creates a track and adds it to the Timeline Asset.
        /// </summary>
        /// <param name="trackName">The name of the track being created.</param>
        /// <typeparam name="T">The type of track being created. The track type must be derived from TrackAsset.</typeparam>
        /// <returns>Returns the created track.</returns>
        public T CreateTrack<T>(string trackName) where T : TrackAsset, new()
        {
            return (T)CreateTrack(typeof(T), null, trackName);
        }

        /// <summary>
        /// Creates a track and adds it to the Timeline Asset.
        /// </summary>
        /// <typeparam name="T">The type of track being created. The track type must be derived from TrackAsset.</typeparam>
        /// <returns>Returns the created track.</returns>
        public T CreateTrack<T>() where T : TrackAsset, new()
        {
            return (T)CreateTrack(typeof(T), null, null);
        }

        /// <summary>
        /// Delete a clip from this timeline.
        /// </summary>
        /// <param name="clip">The clip to delete.</param>
        /// <returns>Returns true if the removal was successful</returns>
        /// <remarks>
        /// This method will delete a clip and any assets owned by the clip.
        /// </remarks>
        public bool DeleteClip(TimelineClip clip)
        {
            if (clip == null || clip.GetParentTrack() == null)
            {
                return false;
            }
            if (this != clip.GetParentTrack().timelineAsset)
            {
                Debug.LogError("Cannot delete a clip from this timeline");
                return false;
            }

            TimelineUndo.PushUndo(clip.GetParentTrack(), "Delete Clip");
            if (clip.curves != null)
            {
                TimelineUndo.PushDestroyUndo(this, clip.GetParentTrack(), clip.curves);
            }

            // handle wrapped assets
            if (clip.asset != null)
            {
                DeleteRecordedAnimation(clip);

                // TODO -- we should flag assets and owned, instead of this check...
#if UNITY_EDITOR
                string path = UnityEditor.AssetDatabase.GetAssetPath(clip.asset);
                if (path == UnityEditor.AssetDatabase.GetAssetPath(this))
#endif
                {
                    TimelineUndo.PushDestroyUndo(this, clip.GetParentTrack(), clip.asset);
                }
            }

            var clipParentTrack = clip.GetParentTrack();
            clipParentTrack.RemoveClip(clip);
            clipParentTrack.CalculateExtrapolationTimes();

            return true;
        }

        /// <summary>
        /// Deletes a track from a timeline, including all clips and subtracks.
        /// </summary>
        /// <param name="track">The track to delete. It must be owned by this Timeline.</param>
        /// <returns>True if the track was deleted successfully.</returns>
        public bool DeleteTrack(TrackAsset track)
        {
            if (track.timelineAsset != this)
                return false;

            // push before we modify properties
            TimelineUndo.PushUndo(track, "Delete Track");
            TimelineUndo.PushUndo(this, "Delete Track");

            TrackAsset parent = track.parent as TrackAsset;
            if (parent != null)
                TimelineUndo.PushUndo(parent, "Delete Track");

            var children = track.GetChildTracks();
            foreach (var child in children)
            {
                DeleteTrack(child);
            }

            DeleteRecordedAnimation(track);

            var clipsToDelete = new List<TimelineClip>(track.clips);
            foreach (var clip in clipsToDelete)
            {
                DeleteClip(clip);
            }
            RemoveTrack(track);

            TimelineUndo.PushDestroyUndo(this, this, track);

            return true;
        }

        internal void MoveLastTrackBefore(TrackAsset asset)
        {
            if (m_Tracks == null || m_Tracks.Count < 2 || asset == null)
                return;

            var lastTrack = m_Tracks[m_Tracks.Count - 1];
            if (lastTrack == asset)
                return;

            for (int i = 0; i < m_Tracks.Count - 1; i++)
            {
                if (m_Tracks[i] == asset)
                {
                    for (int j = m_Tracks.Count - 1; j > i; j--)
                        m_Tracks[j] = m_Tracks[j - 1];
                    m_Tracks[i] = lastTrack;
                    Invalidate();
                    break;
                }
            }
        }

        TrackAsset AllocateTrack(TrackAsset trackAssetParent, string trackName, Type trackType)
        {
            if (trackAssetParent != null && trackAssetParent.timelineAsset != this)
                throw new InvalidOperationException("Addtrack cannot parent to a track not in the Timeline");

            if (!typeof(TrackAsset).IsAssignableFrom(trackType))
                throw new InvalidOperationException("Supplied type must be a track asset");

            var asset = (TrackAsset)CreateInstance(trackType);
            asset.name = trackName;

            const string createTrackUndoName = "Create Track";

            PlayableAsset parent = trackAssetParent != null ? trackAssetParent as PlayableAsset : this;
            TimelineCreateUtilities.SaveAssetIntoObject(asset, parent);
            TimelineUndo.RegisterCreatedObjectUndo(asset, createTrackUndoName);
            TimelineUndo.PushUndo(parent, createTrackUndoName);

            if (trackAssetParent != null)
                trackAssetParent.AddChild(asset);
            else //TimelineAsset is the parent
                AddTrackInternal(asset);

            return asset;
        }

        void DeleteRecordedAnimation(TrackAsset track)
        {
            var animTrack = track as AnimationTrack;
            if (animTrack != null && animTrack.infiniteClip != null)
                TimelineUndo.PushDestroyUndo(this, track, animTrack.infiniteClip);

            if (track.curves != null)
                TimelineUndo.PushDestroyUndo(this, track, track.curves);
        }

        void DeleteRecordedAnimation(TimelineClip clip)
        {
            if (clip == null)
                return;

            if (clip.curves != null)
                TimelineUndo.PushDestroyUndo(this, clip.GetParentTrack(), clip.curves);

            if (!clip.recordable)
                return;

            AnimationPlayableAsset asset = clip.asset as AnimationPlayableAsset;
            if (asset == null || asset.clip == null)
                return;

            TimelineUndo.PushDestroyUndo(this, asset, asset.clip);
        }
    }
}
