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

namespace UnityEditor.Timeline
{
    // Handles Undo animated properties on PlayableAssets from clips to create parameter animation

    static partial class TimelineRecording
    {
        internal static bool HasAnyPlayableAssetModifications(UndoPropertyModification[] modifications)
        {
            return modifications.Any(x => GetTarget(x) as IPlayableAsset != null);
        }

        internal static UndoPropertyModification[] ProcessPlayableAssetModification(UndoPropertyModification[] modifications, WindowState state, bool allowAdd)
        {
            // can't record without a director since the asset being modified might be a scene instance
            if (state == null || state.editSequence.director == null)
                return modifications;

            var remaining = new List<UndoPropertyModification>();
            foreach (UndoPropertyModification mod in modifications)
            {
                if (!ProcessPlayableAssetModification(mod, state, allowAdd))
                    remaining.Add(mod);
            }

            if (remaining.Count != modifications.Length)
            {
                TimelineEditor.Refresh(RefreshReason.ContentsModified);
            }

            return remaining.ToArray();
        }

        static bool ProcessPlayableAssetModification(UndoPropertyModification mod, WindowState state, bool allowAdd)
        {
            var target = GetTarget(mod) as IPlayableAsset;
            if (target == null)
                return false;

            var curvesOwner = AnimatedParameterUtility.ToCurvesOwner(target, state.editSequence.asset);
            if (curvesOwner == null || !curvesOwner.HasAnyAnimatableParameters())
                return false;

            return ProcessPlayableAssetRecording(mod, state, curvesOwner, allowAdd);
        }

        internal static TimelineClip FindClipWithAsset(TimelineAsset asset, IPlayableAsset target)
        {
            if (target == null || asset == null)
                return null;

            var clips = asset.flattenedTracks.SelectMany(x => x.clips);
            return clips.FirstOrDefault(x => x != null && x.asset != null && target == x.asset as IPlayableAsset);
        }

        static bool ProcessPlayableAssetRecording(UndoPropertyModification mod, WindowState state, ICurvesOwner curvesOwner, bool allowAdd)
        {
            if (mod.currentValue == null)
                return false;

            if (!curvesOwner.IsParameterAnimatable(mod.currentValue.propertyPath))
                return false;

            // only animate items with existing curves
            if (!allowAdd && !curvesOwner.IsParameterAnimated(mod.currentValue.propertyPath))
                return false;

            var localTime = state.editSequence.time;
            var timelineClip = curvesOwner as TimelineClip;
            if (timelineClip != null)
            {
                // don't use time global to local since it will possibly loop.
                localTime = timelineClip.ToLocalTimeUnbound(state.editSequence.time);
            }

            if (localTime < 0)
                return false;

            if (state.playing)
                return true; //absorb undo but don't record during playback

            // grab the value from the current modification
            float fValue;
            if (!ExpressionEvaluator.Evaluate(mod.currentValue.value, out fValue))
            {
                // case 916913 -- 'Add Key' menu item will passes 'True' or 'False' (instead of 1, 0)
                // so we need a special case to parse the boolean string
                bool bValue;
                if (!bool.TryParse(mod.currentValue.value, out bValue))
                {
                    Debug.Assert(false, "Invalid type in PlayableAsset recording");
                    return false;
                }

                fValue = bValue ? 1 : 0;
            }

            var added = curvesOwner.AddAnimatedParameterValueAt(mod.currentValue.propertyPath, fValue, (float)localTime);
            if (added && AnimationMode.InAnimationMode())
            {
                EditorCurveBinding binding = curvesOwner.GetCurveBinding(mod.previousValue.propertyPath);
                AnimationMode.AddPropertyModification(binding, mod.previousValue, true);
                curvesOwner.targetTrack.SetShowInlineCurves(true);
                if (state.GetWindow() != null && state.GetWindow().treeView != null)
                    state.GetWindow().treeView.CalculateRowRects();
            }

            return added;
        }

        static bool IsPlayableAssetProperty(SerializedProperty property)
        {
            return property.serializedObject.targetObject is IPlayableAsset;
        }
    }
}
