// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.GUI;

namespace FlaxEditor.GUI
{
    /// <summary>
    /// The generic curve editor control.
    /// </summary>
    /// <typeparam name="T">The keyframe value type.</typeparam>
    /// <seealso cref="CurveEditorBase" />
    public abstract partial class CurveEditor<T> : CurveEditorBase where T : new()
    {
        private class Popup : ContextMenuBase
        {
            private CustomEditorPresenter _presenter;
            private CurveEditor<T> _editor;
            private List<int> _keyframeIndices;
            private bool _isDirty;

            public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float height = 140.0f)
            : this(editor, height)
            {
                _presenter.Select(selection);
                _presenter.OpenAllGroups();
                _keyframeIndices = keyframeIndices;
                if (keyframeIndices != null && selection.Length != keyframeIndices.Count)
                    throw new Exception();
            }

            private Popup(CurveEditor<T> editor, float height)
            {
                _editor = editor;
                const float width = 340.0f;
                Size = new Float2(width, height);
                var panel1 = new Panel(ScrollBars.Vertical)
                {
                    Bounds = new Rectangle(0, 0.0f, width, height),
                    Parent = this
                };
                _presenter = new CustomEditorPresenter(null);
                _presenter.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop;
                _presenter.Panel.IsScrollable = true;
                _presenter.Panel.Parent = panel1;
                _presenter.Modified += OnModified;
            }

            private void OnModified()
            {
                if (!_isDirty)
                {
                    _editor.OnEditingStart();
                }
                _isDirty = true;

                if (_keyframeIndices != null)
                {
                    for (int i = 0; i < _presenter.SelectionCount; i++)
                    {
                        _editor.SetKeyframeInternal(_keyframeIndices[i], _presenter.Selection[i]);
                    }
                }
                else if (_presenter.Selection[0] is IAllKeyframesProxy proxy)
                {
                    proxy.Apply();
                }

                _editor.UpdateFPS();
                _editor.UpdateKeyframes();
                _editor.UpdateTooltips();
            }

            /// <inheritdoc />
            protected override void OnShow()
            {
                Focus();

                base.OnShow();
            }

            /// <inheritdoc />
            public override void Hide()
            {
                if (!Visible)
                    return;

                Focus(null);

                if (_isDirty)
                {
                    _editor.OnEdited();
                    _editor.OnEditingEnd();
                }

                if (_editor._popup == this)
                    _editor._popup = null;
                _presenter = null;
                _editor = null;
                _keyframeIndices = null;

                base.Hide();
            }

            /// <inheritdoc />
            public override bool OnKeyDown(KeyboardKeys key)
            {
                if (base.OnKeyDown(key))
                    return true;

                if (key == KeyboardKeys.Escape)
                {
                    Hide();
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// The single keyframe control.
        /// </summary>
        protected class KeyframePoint : Control
        {
            /// <summary>
            /// The parent curve editor.
            /// </summary>
            public CurveEditor<T> Editor;

            /// <summary>
            /// The keyframe index.
            /// </summary>
            public int Index;

            /// <summary>
            /// The component index.
            /// </summary>
            public int Component;

            /// <summary>
            /// Flag for selected keyframes.
            /// </summary>
            public bool IsSelected;

            /// <summary>
            /// Gets the point time and value on a curve.
            /// </summary>
            public Float2 Point => Editor.GetKeyframePoint(Index, Component);

            /// <summary>
            /// Gets the time of the keyframe point.
            /// </summary>
            public float Time => Editor.GetKeyframeTime(Index);

            /// <inheritdoc />
            public override void Draw()
            {
                var rect = new Rectangle(Float2.Zero, Size);
                var color = Editor.ShowCollapsed ? Color.Gray : Editor.Colors[Component];
                if (IsSelected)
                    color = Editor.ContainsFocus ? Color.YellowGreen : Color.Lerp(Color.Gray, Color.YellowGreen, 0.4f);
                if (IsMouseOver)
                    color *= 1.1f;
                Render2D.FillRectangle(rect, color);
            }

            /// <inheritdoc />
            public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
            {
                if (base.OnMouseDoubleClick(location, button))
                    return true;

                if (button == MouseButton.Left)
                {
                    Editor.EditKeyframes(this, location, new List<int> { Index });
                    return true;
                }

                return false;
            }

            /// <inheritdoc />
            protected override bool ShowTooltip => base.ShowTooltip && !Editor._contents._isMovingSelection;

            /// <summary>
            /// Updates the tooltip.
            /// </summary>
            public void UpdateTooltip()
            {
                var k = Editor.GetKeyframe(Index);
                var time = Editor.GetKeyframeTime(k);
                var value = Editor.GetKeyframeValue(k);
                if (Editor.ShowCollapsed)
                    TooltipText = string.Format("Time: {0}, Value: {1}", time, value);
                else
                    TooltipText = string.Format("Time: {0}, Value: {1}", time, Editor.Accessor.GetCurveValue(ref value, Component));
            }
        }

        /// <summary>
        /// The single keyframe tangent control.
        /// </summary>
        protected class TangentPoint : Control
        {
            /// <summary>
            /// The parent curve editor.
            /// </summary>
            public CurveEditor<T> Editor;

            /// <summary>
            /// The keyframe index.
            /// </summary>
            public int Index;

            /// <summary>
            /// The component index.
            /// </summary>
            public int Component;

            /// <summary>
            /// True if tangent is `In`, otherwise it's `Out`.
            /// </summary>
            public bool IsIn;

            /// <summary>
            /// The keyframe.
            /// </summary>
            public KeyframePoint Point;

            /// <summary>
            /// Gets the tangent value on curve.
            /// </summary>
            public float TangentValue
            {
                get => Editor.GetKeyframeTangentInternal(Index, IsIn, Component);
                set => Editor.SetKeyframeTangentInternal(Index, IsIn, Component, value);
            }

            /// <inheritdoc />
            public override void Draw()
            {
                var pointPos = PointFromParent(Point.Center);
                Render2D.DrawLine(Size * 0.5f, pointPos, Color.Gray);

                var rect = new Rectangle(Float2.Zero, Size);
                var color = Color.MediumVioletRed;
                if (IsMouseOver)
                    color *= 1.1f;
                Render2D.FillRectangle(rect, color);
            }

            /// <summary>
            /// Updates the tooltip.
            /// </summary>
            public void UpdateTooltip()
            {
                TooltipText = string.Format("Tangent {0}: {1}", IsIn ? "in" : "out", TangentValue);
            }
        }

        /// <summary>
        /// The timeline intervals metric area size (in pixels).
        /// </summary>
        protected static readonly float LabelsSize = 10.0f;

        /// <summary>
        /// The timeline units per second (on time axis).
        /// </summary>
        public static readonly float UnitsPerSecond = 100.0f;

        /// <summary>
        /// The keyframes size.
        /// </summary>
        protected static readonly Float2 KeyframesSize = new Float2(7.0f);

        /// <summary>
        /// The colors for the keyframe points.
        /// </summary>
        protected Color[] Colors = Utilities.Utils.CurveKeyframesColors;

        /// <summary>
        /// The curve time/value axes tick steps.
        /// </summary>
        protected float[] TickSteps = Utilities.Utils.CurveTickSteps;

        /// <summary>
        /// The curve contents area.
        /// </summary>
        protected ContentsBase _contents;

        /// <summary>
        /// The main UI panel with scroll bars.
        /// </summary>
        protected Panel _mainPanel;

        /// <summary>
        /// True if refresh keyframes positioning before drawing.
        /// </summary>
        protected bool _refreshAfterEdit;

        /// <summary>
        /// True if curve is collapsed.
        /// </summary>
        protected bool _showCollapsed;

        private float[] _tickStrengths;
        private Popup _popup;
        private float? _fps;

        private Color _contentsColor;
        private Color _linesColor;
        private Color _labelsColor;
        private Font _labelsFont;

        /// <summary>
        /// The keyframe UI points.
        /// </summary>
        protected readonly List<KeyframePoint> _points = new List<KeyframePoint>();

        /// <summary>
        /// The tangents UI points.
        /// </summary>
        protected readonly TangentPoint[] _tangents = new TangentPoint[2];

        /// <inheritdoc />
        public override Float2 ViewOffset
        {
            get => _mainPanel.ViewOffset;
            set
            {
                _mainPanel.ViewOffset = value;
                _mainPanel.FastScroll();
            }
        }

        /// <inheritdoc />
        public override Float2 ViewScale
        {
            get => _contents.Scale;
            set => _contents.Scale = Float2.Clamp(value, new Float2(0.0001f), new Float2(1000.0f));
        }

        /// <summary>
        /// The keyframes data accessor.
        /// </summary>
        public readonly IKeyframeAccess<T> Accessor = new KeyframeAccess() as IKeyframeAccess<T>;

        /// <summary>
        /// Gets a value indicating whether user is editing the curve.
        /// </summary>
        public bool IsUserEditing => _popup != null || _contents._leftMouseDown;

        /// <summary>
        /// The default value.
        /// </summary>
        public T DefaultValue;

        /// <inheritdoc />
        public override ScrollBars ScrollBars
        {
            get => _mainPanel.ScrollBars;
            set => _mainPanel.ScrollBars = value;
        }

        /// <inheritdoc />
        public override Type ValueType => typeof(T);

        /// <inheritdoc />
        public override float? FPS
        {
            get => _fps;
            set
            {
                if (_fps.HasValue == value.HasValue && (!value.HasValue || Mathf.NearEqual(_fps.Value, value.Value)))
                    return;

                _fps = value;
                UpdateFPS();
            }
        }

        /// <inheritdoc />
        public override bool ShowCollapsed
        {
            get => _showCollapsed;
            set
            {
                if (_showCollapsed == value)
                    return;

                _showCollapsed = value;
                UpdateKeyframes();
                UpdateTangents();
                if (value)
                {
                    // Synchronize selection for curve points when collapsed so all points fo the keyframe are selected
                    for (var i = 0; i < _points.Count; i++)
                    {
                        var p = _points[i];
                        if (p.IsSelected)
                        {
                            for (var j = 0; j < _points.Count; j++)
                            {
                                var q = _points[j];
                                if (q.Index == p.Index)
                                    q.IsSelected = true;
                            }
                        }
                    }

                    ShowWholeCurve();
                }
            }
        }

        /// <summary>
        /// Occurs when keyframes collection gets changed (keyframe added or removed).
        /// </summary>
        public event Action KeyframesChanged;

        /// <summary>
        /// Initializes a new instance of the <see cref="CurveEditor{T}"/> class.
        /// </summary>
        protected CurveEditor()
        {
            Accessor.GetDefaultValue(out DefaultValue);

            var style = Style.Current;
            _contentsColor = style.Background.RGBMultiplied(0.7f);
            _linesColor = style.ForegroundDisabled.RGBMultiplied(0.7f);
            _labelsColor = style.ForegroundDisabled;
            _labelsFont = style.FontSmall;

            _mainPanel = new Panel(ScrollBars.Both)
            {
                ScrollMargin = new Margin(150.0f),
                AlwaysShowScrollbars = true,
                AnchorPreset = AnchorPresets.StretchAll,
                Offsets = Margin.Zero,
                Parent = this
            };
            _contents = new ContentsBase(this)
            {
                ClipChildren = false,
                CullChildren = false,
                AutoFocus = false,
                Parent = _mainPanel,
                Bounds = Rectangle.Empty,
            };
        }

        /// <summary>
        /// Updates the keyframes to match the FPS.
        /// </summary>
        protected abstract void UpdateFPS();

        /// <summary>
        /// Updates the keyframes tooltips.
        /// </summary>
        protected void UpdateTooltips()
        {
            for (var i = 0; i < _points.Count; i++)
                _points[i].UpdateTooltip();
        }

        /// <summary>
        /// Called when keyframes collection gets changed (keyframe added or removed).
        /// </summary>
        protected virtual void OnKeyframesChanged()
        {
            KeyframesChanged?.Invoke();
        }

        /// <summary>
        /// Evaluates the animation curve value at the specified time.
        /// </summary>
        /// <param name="result">The interpolated value from the curve at provided time.</param>
        /// <param name="time">The time to evaluate the curve at.</param>
        /// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
        public abstract void Evaluate(out T result, float time, bool loop = false);

        /// <summary>
        /// Gets the time of the keyframe.
        /// </summary>
        /// <param name="index">The keyframe index.</param>
        /// <returns>The keyframe time.</returns>
        protected abstract float GetKeyframeTime(int index);

        /// <summary>
        /// Gets the time of the keyframe.
        /// </summary>
        /// <param name="keyframe">The keyframe object.</param>
        /// <returns>The keyframe time.</returns>
        protected abstract float GetKeyframeTime(object keyframe);

        /// <summary>
        /// Gets the value of the keyframe.
        /// </summary>
        /// <param name="keyframe">The keyframe object.</param>
        /// <returns>The keyframe value.</returns>
        protected abstract T GetKeyframeValue(object keyframe);

        /// <summary>
        /// Gets the value of the keyframe (single component).
        /// </summary>
        /// <param name="keyframe">The keyframe object.</param>
        /// <param name="component">The keyframe value component index.</param>
        /// <returns>The keyframe component value.</returns>
        protected abstract float GetKeyframeValue(object keyframe, int component);

        /// <summary>
        /// Adds a new keyframe at the given location (in keyframes space).
        /// </summary>
        /// <param name="keyframesPos">The new keyframe position (in keyframes space).</param>
        protected abstract void AddKeyframe(Float2 keyframesPos);

        /// <summary>
        /// Sets the keyframe data (internally).
        /// </summary>
        /// <param name="index">The keyframe index.</param>
        /// <param name="keyframe">The keyframe to set.</param>
        protected abstract void SetKeyframeInternal(int index, object keyframe);

        /// <summary>
        /// Sets the keyframe data (internally).
        /// </summary>
        /// <param name="index">The keyframe index.</param>
        /// <param name="time">The time to set.</param>
        /// <param name="value">The value to set.</param>
        /// <param name="component">The value component.</param>
        protected abstract void SetKeyframeInternal(int index, float time, float value, int component);

        /// <summary>
        /// Gets the keyframe tangent value (internally).
        /// </summary>
        /// <param name="index">The keyframe index.</param>
        /// <param name="isIn">True if tangent is `In`, otherwise it's `Out`.</param>
        /// <param name="component">The value component index.</param>
        /// <returns>The tangent component value.</returns>
        protected abstract float GetKeyframeTangentInternal(int index, bool isIn, int component);

        /// <summary>
        /// Sets the keyframe tangent value (internally).
        /// </summary>
        /// <param name="index">The keyframe index.</param>
        /// <param name="isIn">True if tangent is `In`, otherwise it's `Out`.</param>
        /// <param name="component">The value component index.</param>
        /// <param name="value">The tangent component value.</param>
        protected abstract void SetKeyframeTangentInternal(int index, bool isIn, int component, float value);

        /// <summary>
        /// Removes the keyframes data (internally).
        /// </summary>
        /// <param name="indicesToRemove">The list of indices of the keyframes to remove.</param>
        protected abstract void RemoveKeyframesInternal(HashSet<int> indicesToRemove);

        /// <summary>
        /// Called when showing a context menu. Can be used to add custom buttons with actions.
        /// </summary>
        /// <param name="cm">The menu.</param>
        /// <param name="selectionCount">The amount of selected keyframes.</param>
        protected virtual void OnShowContextMenu(ContextMenu.ContextMenu cm, int selectionCount)
        {
        }

        /// <summary>
        /// Gets proxy object for all keyframes list.
        /// </summary>
        /// <returns>The proxy object.</returns>
        protected abstract IAllKeyframesProxy GetAllKeyframesEditingProxy();

        /// <summary>
        /// Interface for keyframes editing proxy objects.
        /// </summary>
        protected interface IAllKeyframesProxy
        {
            /// <summary>
            /// Applies the proxy data to the editor.
            /// </summary>
            void Apply();
        }

        private void EditAllKeyframes(Control control, Float2 pos)
        {
            _popup = new Popup(this, new object[] { GetAllKeyframesEditingProxy() }, null, 400.0f);
            _popup.Show(control, pos);
        }

        private void EditKeyframes(Control control, Float2 pos)
        {
            var keyframeIndices = new List<int>();
            for (int i = 0; i < _points.Count; i++)
            {
                var p = _points[i];
                if (!p.IsSelected || keyframeIndices.Contains(p.Index))
                    continue;
                keyframeIndices.Add(p.Index);
            }
            EditKeyframes(control, pos, keyframeIndices);
        }

        private void EditKeyframes(Control control, Float2 pos, List<int> keyframeIndices)
        {
            var selection = new object[keyframeIndices.Count];
            var keyframes = GetKeyframes();
            for (int i = 0; i < keyframeIndices.Count; i++)
                selection[i] = keyframes[keyframeIndices[i]];
            _popup = new Popup(this, selection, keyframeIndices);
            _popup.Show(control, pos);
        }

        private void RemoveKeyframes()
        {
            if (KeyframesEditorContext != null)
                KeyframesEditorContext.OnKeyframesDelete(this);
            else
                RemoveKeyframesInner();
        }

        private void CopyKeyframes(KeyframePoint point = null)
        {
            float? timeOffset = null;
            if (point != null)
            {
                timeOffset = -point.Time;
            }
            else
            {
                for (int i = 0; i < _points.Count; i++)
                {
                    if (_points[i].IsSelected)
                    {
                        timeOffset = -_points[i].Time;
                        break;
                    }
                }
            }
            KeyframesEditorUtils.Copy(this, timeOffset);
        }

        private void RemoveKeyframesInner()
        {
            if (SelectionCount == 0)
                return;
            var indicesToRemove = new HashSet<int>();
            for (int i = 0; i < _points.Count; i++)
            {
                var p = _points[i];
                if (p.IsSelected)
                {
                    p.IsSelected = false;
                    indicesToRemove.Add(p.Index);
                }
            }

            OnEditingStart();
            RemoveKeyframesInternal(indicesToRemove);
            OnKeyframesChanged();
            OnEdited();
            OnEditingEnd();
        }

        /// <inheritdoc />
        public override void ShowWholeCurve()
        {
            _mainPanel.GetDesireClientArea(out var mainPanelArea);
            ViewScale = ApplyUseModeMask(EnableZoom, mainPanelArea.Size / _contents.Size, ViewScale);
            Float2 minPos = Float2.Maximum;
            foreach (var point in _points)
            {
                var pos = point.PointToParent(point.Location);
                Float2.Min(ref minPos, ref pos, out minPos);
            }
            var minPosPoint = _contents.PointToParent(ref minPos);
            var scroll = new Float2(_mainPanel.HScrollBar?.TargetValue ?? 0, _mainPanel.VScrollBar?.TargetValue ?? 0);
            scroll = ApplyUseModeMask(EnablePanning, minPosPoint, scroll);
            if (_mainPanel.HScrollBar != null)
                _mainPanel.HScrollBar.TargetValue = scroll.X;
            if (_mainPanel.VScrollBar != null)
                _mainPanel.VScrollBar.TargetValue = scroll.Y;

            UpdateKeyframes();
        }

        /// <inheritdoc />
        public override void Evaluate(out object result, float time, bool loop = false)
        {
            Evaluate(out var value, time, loop);
            result = value;
        }

        private int SelectionCount
        {
            get
            {
                int result = 0;
                if (ShowCollapsed)
                {
                    for (int i = 0; i < _points.Count; i++)
                        if (_points[i].Component == 0 && _points[i].IsSelected)
                            result++;
                }
                else
                {
                    for (int i = 0; i < _points.Count; i++)
                        if (_points[i].IsSelected)
                            result++;
                }
                return result;
            }
        }

        /// <inheritdoc />
        public override void ClearSelection()
        {
            for (int i = 0; i < _points.Count; i++)
            {
                _points[i].IsSelected = false;
            }
        }

        private void BulkSelectUpdate(bool select = true)
        {
            for (int i = 0; i < _points.Count; i++)
            {
                _points[i].IsSelected = select;
            }
        }

        /// <summary>
        /// Selects all keyframes.
        /// </summary>
        public void SelectAll()
        {
            BulkSelectUpdate(true);
        }

        /// <summary>
        /// Deselects all keyframes.
        /// </summary>
        public void DeselectAll()
        {
            BulkSelectUpdate(false);
        }

        /// <summary>
        /// Converts the input point from curve editor control space into the keyframes time/value coordinates.
        /// </summary>
        /// <param name="point">The point.</param>
        /// <param name="curveContentAreaBounds">The curve contents area bounds.</param>
        /// <returns>The result.</returns>
        protected Float2 PointToKeyframes(Float2 point, ref Rectangle curveContentAreaBounds)
        {
            // Curve Editor -> Main Panel
            point = _mainPanel.PointFromParent(point);

            // Main Panel -> Contents
            point = _contents.PointFromParent(point);

            // Contents -> Keyframes
            return new Float2(
                              (point.X + _contents.Location.X) / UnitsPerSecond,
                              (point.Y + _contents.Location.Y - curveContentAreaBounds.Height) / -UnitsPerSecond
                             );
        }

        /// <summary>
        /// Converts the input point from the keyframes time/value coordinates into the curve editor control space.
        /// </summary>
        /// <param name="point">The point.</param>
        /// <param name="curveContentAreaBounds">The curve contents area bounds.</param>
        /// <returns>The result.</returns>
        protected Float2 PointFromKeyframes(Float2 point, ref Rectangle curveContentAreaBounds)
        {
            // Keyframes -> Contents
            point = new Float2(
                               point.X * UnitsPerSecond - _contents.Location.X,
                               point.Y * -UnitsPerSecond + curveContentAreaBounds.Height - _contents.Location.Y
                              );

            // Contents -> Main Panel
            point = _contents.PointToParent(point);

            // Main Panel -> Curve Editor
            return _mainPanel.PointToParent(point);
        }

        private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange)
        {
            Utilities.Utils.DrawCurveTicks((float tick, float strength) =>
            {
                var p = PointFromKeyframes(axis * tick, ref viewRect);

                // Draw line
                var lineRect = new Rectangle
                (
                 viewRect.Location + (p - 0.5f) * axis,
                 Float2.Lerp(viewRect.Size, Float2.One, axis)
                );
                Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength));

                // Draw label
                string label = tick.ToString(CultureInfo.InvariantCulture);
                var labelRect = new Rectangle
                (
                 viewRect.X + 4.0f + (p.X * axis.X),
                 viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X),
                 50,
                 LabelsSize
                );
                Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f);
            }, TickSteps, ref _tickStrengths, min, max, pixelRange);
        }

        /// <summary>
        /// Draws the curve.
        /// </summary>
        /// <param name="viewRect">The main panel client area used as a view bounds.</param>
        protected abstract void DrawCurve(ref Rectangle viewRect);

        /// <inheritdoc />
        public override void Draw()
        {
            // Hack to refresh UI after keyframes edit
            if (_refreshAfterEdit)
            {
                _refreshAfterEdit = false;
                UpdateKeyframes();
            }

            var style = Style.Current;
            var rect = new Rectangle(Float2.Zero, Size);
            var viewRect = _mainPanel.GetClientArea();

            // Draw background
            if (ShowBackground)
            {
                Render2D.FillRectangle(rect, _contentsColor);
            }

            // Draw time and values axes
            if (ShowAxes != UseMode.Off)
            {
                var upperLeft = PointToKeyframes(viewRect.Location, ref viewRect);
                var bottomRight = PointToKeyframes(viewRect.Size, ref viewRect);

                var min = Float2.Min(upperLeft, bottomRight);
                var max = Float2.Max(upperLeft, bottomRight);
                var pixelRange = (max - min) * ViewScale * UnitsPerSecond;

                Render2D.PushClip(ref viewRect);

                if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical)
                    DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
                if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal)
                    DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);

                Render2D.PopClip();
            }

            // Draw curve
            if (!_showCollapsed)
            {
                Render2D.PushClip(ref rect);
                DrawCurve(ref viewRect);
                Render2D.PopClip();
            }

            // Draw selection rectangle
            if (_contents._leftMouseDown && !_contents._isMovingSelection && !_contents._isMovingTangent)
            {
                var selectionRect = Rectangle.FromPoints
                (
                 _mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)),
                 _mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos))
                );
                Render2D.FillRectangle(selectionRect, style.Selection);
                Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
            }

            base.Draw();

            // Draw border
            if (ContainsFocus)
            {
                Render2D.DrawRectangle(rect, style.BackgroundSelected);
            }
        }

        /// <inheritdoc />
        protected override void OnSizeChanged()
        {
            base.OnSizeChanged();

            UpdateKeyframes();
        }

        /// <inheritdoc />
        public override bool OnKeyDown(KeyboardKeys key)
        {
            if (base.OnKeyDown(key))
                return true;

            InputOptions options = Editor.Instance.Options.Options.Input;
            if (options.SelectAll.Process(this))
            {
                SelectAll();
                UpdateTangents();
                return true;
            }
            else if (options.DeselectAll.Process(this))
            {
                DeselectAll();
                UpdateTangents();
                return true;
            }
            else if (options.Delete.Process(this))
            {
                RemoveKeyframes();
                return true;
            }
            else if (options.Copy.Process(this))
            {
                CopyKeyframes();
                return true;
            }
            else if (options.Paste.Process(this))
            {
                KeyframesEditorUtils.Paste(this);
                return true;
            }
            else if (options.FocusSelection.Process(this))
            {
                ShowWholeCurve();
                return true;
            }

            return false;
        }

        /// <inheritdoc />
        public override void OnDestroy()
        {
            _mainPanel = null;
            _contents = null;
            _popup = null;
            _points.Clear();
            _labelsFont = null;
            Colors = null;
            DefaultValue = default;
            TickSteps = null;
            _tickStrengths = null;
            KeyframesChanged = null;
            KeyframesEditorContext = null;

            base.OnDestroy();
        }
    }

    /// <summary>
    /// The linear curve editor control.
    /// </summary>
    /// <typeparam name="T">The keyframe value type.</typeparam>
    /// <seealso cref="LinearCurve{T}"/>
    /// <seealso cref="CurveEditor{T}"/>
    /// <seealso cref="CurveEditorBase" />
    public class LinearCurveEditor<T> : CurveEditor<T> where T : new()
    {
        /// <summary>
        /// The keyframes collection.
        /// </summary>
        protected List<LinearCurve<T>.Keyframe> _keyframes = new List<LinearCurve<T>.Keyframe>();

        /// <summary>
        /// Gets the keyframes collection (read-only).
        /// </summary>
        public IReadOnlyList<LinearCurve<T>.Keyframe> Keyframes => _keyframes;

        /// <summary>
        /// Initializes a new instance of the <see cref="LinearCurveEditor{T}"/> class.
        /// </summary>
        public LinearCurveEditor()
        {
            for (int i = 0; i < _tangents.Length; i++)
            {
                _tangents[i] = new TangentPoint
                {
                    AutoFocus = false,
                    Size = KeyframesSize,
                    Editor = this,
                    Component = i / 2,
                    Parent = _contents,
                    Visible = false,
                    IsIn = false,
                };
            }
            for (int i = 0; i < _tangents.Length; i += 2)
            {
                _tangents[i].IsIn = true;
            }
        }

        /// <summary>
        /// Adds the new keyframe.
        /// </summary>
        /// <param name="k">The keyframe to add.</param>
        /// <returns>The index of the keyframe.</returns>
        public int AddKeyframe(LinearCurve<T>.Keyframe k)
        {
            var eps = Mathf.Epsilon;
            if (FPS.HasValue)
            {
                float fps = FPS.Value;
                k.Time = Mathf.Floor(k.Time * fps) / fps;
                eps = 1.0f / fps;
            }

            int pos = 0;
            while (pos < _keyframes.Count && _keyframes[pos].Time < k.Time)
                pos++;
            if (_keyframes.Count > pos && Mathf.Abs(_keyframes[pos].Time - k.Time) < eps)
                _keyframes[pos] = k;
            else
                _keyframes.Insert(pos, k);

            OnKeyframesChanged();
            OnEdited();
            return pos;
        }

        /// <summary>
        /// Sets the keyframes collection.
        /// </summary>
        /// <param name="keyframes">The keyframes.</param>
        public void SetKeyframes(IEnumerable<LinearCurve<T>.Keyframe> keyframes)
        {
            if (keyframes == null)
                throw new ArgumentNullException(nameof(keyframes));
            var keyframesArray = keyframes as LinearCurve<T>.Keyframe[] ?? keyframes.ToArray();
            if (_keyframes.SequenceEqual(keyframesArray))
                return;
            if (keyframesArray.Length > MaxKeyframes)
            {
                var tmp = keyframesArray;
                keyframesArray = new LinearCurve<T>.Keyframe[MaxKeyframes];
                Array.Copy(tmp, keyframesArray, MaxKeyframes);
            }

            _keyframes.Clear();
            _keyframes.AddRange(keyframesArray);
            _keyframes.Sort((a, b) => a.Time.CompareTo(b.Time));

            UpdateFPS();
            OnKeyframesChanged();
        }

        /// <inheritdoc />
        protected override void UpdateFPS()
        {
            if (FPS.HasValue)
            {
                float fps = FPS.Value;
                for (int i = 0; i < _keyframes.Count; i++)
                {
                    var k = _keyframes[i];
                    k.Time = Mathf.Floor(k.Time * fps) / fps;
                    _keyframes[i] = k;
                }
            }
        }

        /// <summary>
        /// Gets the keyframe point (in keyframes space).
        /// </summary>
        /// <param name="k">The keyframe.</param>
        /// <param name="component">The keyframe value component index.</param>
        /// <returns>The point in time/value space.</returns>
        private Float2 GetKeyframePoint(ref LinearCurve<T>.Keyframe k, int component)
        {
            return new Float2(k.Time, Accessor.GetCurveValue(ref k.Value, component));
        }

        private void DrawLine(LinearCurve<T>.Keyframe startK, LinearCurve<T>.Keyframe endK, int component, ref Rectangle viewRect)
        {
            var start = GetKeyframePoint(ref startK, component);
            var end = GetKeyframePoint(ref endK, component);

            var p1 = PointFromKeyframes(start, ref viewRect);
            var p2 = PointFromKeyframes(end, ref viewRect);

            var color = Colors[component].RGBMultiplied(0.6f);
            Render2D.DrawLine(p1, p2, color, 1.6f);
        }

        /// <inheritdoc />
        protected override void OnKeyframesChanged()
        {
            var components = Accessor.GetCurveComponents();
            while (_points.Count > _keyframes.Count * components)
            {
                var last = _points.Count - 1;
                _points[last].Dispose();
                _points.RemoveAt(last);
            }

            while (_points.Count < _keyframes.Count * components)
            {
                _points.Add(new KeyframePoint
                {
                    AutoFocus = false,
                    Size = KeyframesSize,
                    Editor = this,
                    Index = _points.Count / components,
                    Component = _points.Count % components,
                    Parent = _contents,
                });

                _refreshAfterEdit = true;
            }

            UpdateKeyframes();
            UpdateTooltips();

            base.OnKeyframesChanged();
        }

        /// <inheritdoc />
        public override void Evaluate(out T result, float time, bool loop = false)
        {
            var curve = new LinearCurve<T>
            {
                Keyframes = _keyframes.ToArray()
            };
            curve.Evaluate(out result, time, loop);
        }

        /// <inheritdoc />
        protected override float GetKeyframeTime(int index)
        {
            return _keyframes[index].Time;
        }

        /// <inheritdoc />
        protected override float GetKeyframeTime(object keyframe)
        {
            return ((LinearCurve<T>.Keyframe)keyframe).Time;
        }

        /// <inheritdoc />
        protected override T GetKeyframeValue(object keyframe)
        {
            return ((LinearCurve<T>.Keyframe)keyframe).Value;
        }

        /// <inheritdoc />
        protected override float GetKeyframeValue(object keyframe, int component)
        {
            var value = ((LinearCurve<T>.Keyframe)keyframe).Value;
            return Accessor.GetCurveValue(ref value, component);
        }

        /// <inheritdoc />
        protected override void AddKeyframe(Float2 keyframesPos)
        {
            var k = new LinearCurve<T>.Keyframe
            {
                Time = keyframesPos.X,
            };
            var components = Accessor.GetCurveComponents();
            for (int component = 0; component < components; component++)
            {
                Accessor.SetCurveValue(keyframesPos.Y, ref k.Value, component);
            }
            OnEditingStart();
            AddKeyframe(k);
            OnEditingEnd();
        }

        /// <inheritdoc />
        protected override void SetKeyframeInternal(int index, object keyframe)
        {
            _keyframes[index] = (LinearCurve<T>.Keyframe)keyframe;
        }

        /// <inheritdoc />
        protected override void SetKeyframeInternal(int index, float time, float value, int component)
        {
            var k = _keyframes[index];
            k.Time = time;
            Accessor.SetCurveValue(value, ref k.Value, component);
            _keyframes[index] = k;
        }

        /// <inheritdoc />
        protected override float GetKeyframeTangentInternal(int index, bool isIn, int component)
        {
            return 0.0f;
        }

        /// <inheritdoc />
        protected override void SetKeyframeTangentInternal(int index, bool isIn, int component, float value)
        {
        }

        /// <inheritdoc />
        protected override void RemoveKeyframesInternal(HashSet<int> indicesToRemove)
        {
            var keyframes = _keyframes;
            _keyframes = new List<LinearCurve<T>.Keyframe>();
            for (int i = 0; i < keyframes.Count; i++)
            {
                if (!indicesToRemove.Contains(i))
                    _keyframes.Add(keyframes[i]);
            }
        }

        sealed class AllKeyframesProxy : IAllKeyframesProxy
        {
            [HideInEditor, NoSerialize]
            public LinearCurveEditor<T> Editor;

            [Collection(CanReorderItems = false, Spacing = 10)]
            public LinearCurve<T>.Keyframe[] Keyframes;

            public void Apply()
            {
                Editor.SetKeyframes(Keyframes);
            }
        }

        /// <inheritdoc />
        protected override IAllKeyframesProxy GetAllKeyframesEditingProxy()
        {
            return new AllKeyframesProxy
            {
                Editor = this,
                Keyframes = _keyframes.ToArray(),
            };
        }

        /// <inheritdoc />
        public override object[] GetKeyframes()
        {
            var keyframes = new object[_keyframes.Count];
            for (int i = 0; i < keyframes.Length; i++)
                keyframes[i] = _keyframes[i];
            return keyframes;
        }

        /// <inheritdoc />
        public override void SetKeyframes(object[] keyframes)
        {
            var data = new LinearCurve<T>.Keyframe[keyframes.Length];
            for (int i = 0; i < keyframes.Length; i++)
            {
                if (keyframes[i] is LinearCurve<T>.Keyframe asT)
                    data[i] = asT;
                else if (keyframes[i] is LinearCurve<object>.Keyframe asObj)
                    data[i] = new LinearCurve<T>.Keyframe(asObj.Time, (T)asObj.Value);
            }
            SetKeyframes(data);
        }

        /// <inheritdoc />
        public override int AddKeyframe(float time, object value)
        {
            return AddKeyframe(new LinearCurve<T>.Keyframe(time, (T)value));
        }

        /// <inheritdoc />
        public override int AddKeyframe(float time, object value, object tangentIn, object tangentOut)
        {
            return AddKeyframe(new LinearCurve<T>.Keyframe(time, (T)value));
        }

        /// <inheritdoc />
        public override void GetKeyframe(int index, out float time, out object value, out object tangentIn, out object tangentOut)
        {
            var k = _keyframes[index];
            time = k.Time;
            value = k.Value;
            tangentIn = tangentOut = 0.0f;
        }

        /// <inheritdoc />
        public override object GetKeyframe(int index)
        {
            return _keyframes[index];
        }

        /// <inheritdoc />
        public override void SetKeyframeValue(int index, object value)
        {
            var k = _keyframes[index];
            k.Value = (T)value;
            _keyframes[index] = k;

            UpdateKeyframes();
            UpdateTooltips();
            OnEdited();
        }

        /// <inheritdoc />
        public override void SetKeyframeValue(int index, object value, object tangentIn, object tangentOut)
        {
            var k = _keyframes[index];
            k.Value = (T)value;
            _keyframes[index] = k;

            UpdateKeyframes();
            UpdateTooltips();
            OnEdited();
        }

        /// <inheritdoc />
        public override Float2 GetKeyframePoint(int index, int component)
        {
            var k = _keyframes[index];
            return new Float2(k.Time, Accessor.GetCurveValue(ref k.Value, component));
        }

        /// <inheritdoc />
        public override void OnKeyframesGet(string trackName, Action<string, float, object> get)
        {
            for (int i = 0; i < _keyframes.Count; i++)
            {
                var k = _keyframes[i];
                get(trackName, k.Time, k);
            }
        }

        /// <inheritdoc />
        public override void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
        {
            OnEditingStart();
            _keyframes.Clear();
            if (keyframes != null)
            {
                foreach (var e in keyframes)
                {
                    var k = (LinearCurve<T>.Keyframe)e.Value;
                    _keyframes.Add(new LinearCurve<T>.Keyframe(e.Key, k.Value));
                }
            }
            OnKeyframesChanged();
            OnEdited();
            OnEditingEnd();
        }

        /// <inheritdoc />
        public override int KeyframesCount => _keyframes.Count;

        /// <inheritdoc />
        public override void UpdateKeyframes()
        {
            if (_points.Count == 0)
            {
                // No keyframes
                _contents.Bounds = Rectangle.Empty;
                return;
            }

            var wasLocked = _mainPanel.IsLayoutLocked;
            _mainPanel.IsLayoutLocked = true;

            // Place keyframes
            Rectangle curveContentAreaBounds = _mainPanel.GetClientArea();
            var viewScale = ViewScale;
            for (int i = 0; i < _points.Count; i++)
            {
                var p = _points[i];
                var k = _keyframes[p.Index];

                var location = GetKeyframePoint(ref k, p.Component);
                var point = new Float2
                (
                 location.X * UnitsPerSecond - p.Width * 0.5f,
                 location.Y * -UnitsPerSecond - p.Height * 0.5f + curveContentAreaBounds.Height
                );

                if (_showCollapsed)
                {
                    point.Y = 1.0f;
                    p.Size = new Float2(KeyframesSize.X / viewScale.X, Height - 2.0f);
                    p.Visible = p.Component == 0;
                }
                else
                {
                    p.Size = KeyframesSize / viewScale;
                    p.Visible = true;
                }
                p.Location = point;
            }

            // Calculate bounds
            var bounds = _points[0].Bounds;
            for (var i = 1; i < _points.Count; i++)
                bounds = Rectangle.Union(bounds, _points[i].Bounds);

            // Adjust contents bounds to fill the curve area
            if (EnablePanning != UseMode.Off || !ShowCollapsed)
            {
                bounds.Width = Mathf.Max(bounds.Width, 1.0f);
                bounds.Height = Mathf.Max(bounds.Height, 1.0f);
                bounds.Location = ApplyUseModeMask(EnablePanning, bounds.Location, _contents.Location);
                bounds.Size = ApplyUseModeMask(EnablePanning, bounds.Size, _contents.Size);
                if (!_contents._isMovingSelection)
                    _contents.Bounds = bounds;
            }
            else if (_contents.Bounds == Rectangle.Empty)
            {
                _contents.Bounds = Rectangle.Union(bounds, new Rectangle(Float2.Zero, Float2.One));
            }

            // Offset the keyframes (parent container changed its location)
            var posOffset = _contents.Location;
            for (var i = 0; i < _points.Count; i++)
            {
                _points[i].Location -= posOffset;
            }

            UpdateTangents();

            if (!wasLocked)
                _mainPanel.UnlockChildrenRecursive();
            _mainPanel.PerformLayout();
        }

        /// <inheritdoc />
        public override void UpdateTangents()
        {
            for (int i = 0; i < _tangents.Length; i++)
                _tangents[i].Visible = false;
        }

        /// <inheritdoc />
        protected override void DrawCurve(ref Rectangle viewRect)
        {
            var components = Accessor.GetCurveComponents();
            for (int component = 0; component < components; component++)
            {
                if (ShowStartEndLines)
                {
                    var start = new LinearCurve<T>.Keyframe
                    {
                        Value = DefaultValue,
                        Time = -10000000.0f,
                    };
                    var end = new LinearCurve<T>.Keyframe
                    {
                        Value = DefaultValue,
                        Time = 10000000.0f,
                    };

                    if (_keyframes.Count == 0)
                    {
                        DrawLine(start, end, component, ref viewRect);
                    }
                    else
                    {
                        DrawLine(start, _keyframes[0], component, ref viewRect);
                        DrawLine(_keyframes[_keyframes.Count - 1], end, component, ref viewRect);
                    }
                }

                var color = Colors[component];
                for (int i = 1; i < _keyframes.Count; i++)
                {
                    var startK = _keyframes[i - 1];
                    var endK = _keyframes[i];

                    var start = GetKeyframePoint(ref startK, component);
                    var end = GetKeyframePoint(ref endK, component);

                    var p1 = PointFromKeyframes(start, ref viewRect);
                    var p2 = PointFromKeyframes(end, ref viewRect);

                    Render2D.DrawLine(p1, p2, color);
                }
            }
        }

        /// <inheritdoc />
        public override void OnDestroy()
        {
            _keyframes.Clear();

            base.OnDestroy();
        }
    }

    /// <summary>
    /// The Bezier curve editor control.
    /// </summary>
    /// <typeparam name="T">The keyframe value type.</typeparam>
    /// <seealso cref="BezierCurve{T}"/>
    /// <seealso cref="CurveEditor{T}"/>
    /// <seealso cref="CurveEditorBase" />
    public class BezierCurveEditor<T> : CurveEditor<T> where T : new()
    {
        /// <summary>
        /// The keyframes collection.
        /// </summary>
        protected List<BezierCurve<T>.Keyframe> _keyframes = new List<BezierCurve<T>.Keyframe>();

        /// <summary>
        /// Gets the keyframes collection (read-only).
        /// </summary>
        public IReadOnlyList<BezierCurve<T>.Keyframe> Keyframes => _keyframes;

        /// <summary>
        /// Initializes a new instance of the <see cref="BezierCurveEditor{T}"/> class.
        /// </summary>
        public BezierCurveEditor()
        {
            for (int i = 0; i < _tangents.Length; i++)
            {
                _tangents[i] = new TangentPoint
                {
                    AutoFocus = false,
                    Size = KeyframesSize,
                    Editor = this,
                    Component = i / 2,
                    Parent = _contents,
                    Visible = false,
                    IsIn = false,
                };
            }
            for (int i = 0; i < _tangents.Length; i += 2)
            {
                _tangents[i].IsIn = true;
            }
        }

        /// <summary>
        /// Adds the new keyframe.
        /// </summary>
        /// <param name="k">The keyframe to add.</param>
        /// <returns>The index of the keyframe.</returns>
        public int AddKeyframe(BezierCurve<T>.Keyframe k)
        {
            var eps = Mathf.Epsilon;
            if (FPS.HasValue)
            {
                float fps = FPS.Value;
                k.Time = Mathf.Floor(k.Time * fps) / fps;
                eps = 1.0f / fps;
            }

            int pos = 0;
            while (pos < _keyframes.Count && _keyframes[pos].Time < k.Time)
                pos++;
            if (_keyframes.Count > pos && Mathf.Abs(_keyframes[pos].Time - k.Time) < eps)
                _keyframes[pos] = k;
            else
                _keyframes.Insert(pos, k);

            OnKeyframesChanged();
            OnEdited();
            return pos;
        }

        /// <summary>
        /// Sets the keyframes collection.
        /// </summary>
        /// <param name="keyframes">The keyframes.</param>
        public void SetKeyframes(IEnumerable<BezierCurve<T>.Keyframe> keyframes)
        {
            if (keyframes == null)
                throw new ArgumentNullException(nameof(keyframes));
            var keyframesArray = keyframes as BezierCurve<T>.Keyframe[] ?? keyframes.ToArray();
            if (_keyframes.SequenceEqual(keyframesArray))
                return;
            if (keyframesArray.Length > MaxKeyframes)
            {
                var tmp = keyframesArray;
                keyframesArray = new BezierCurve<T>.Keyframe[MaxKeyframes];
                Array.Copy(tmp, keyframesArray, MaxKeyframes);
            }

            _keyframes.Clear();
            _keyframes.AddRange(keyframesArray);
            _keyframes.Sort((a, b) => a.Time.CompareTo(b.Time));

            UpdateFPS();

            OnKeyframesChanged();
        }

        private void ResetTangents()
        {
            bool edited = false;
            for (int i = 0; i < _points.Count; i++)
            {
                var p = _points[i];
                if (!p.IsSelected)
                    continue;
                if (!edited)
                    OnEditingStart();
                edited = true;

                var k = _keyframes[p.Index];

                if (p.Index > 0)
                {
                    Accessor.SetCurveValue(0.0f, ref k.TangentIn, p.Component);
                }

                if (p.Index < _keyframes.Count - 1)
                {
                    Accessor.SetCurveValue(0.0f, ref k.TangentOut, p.Component);
                }

                _keyframes[p.Index] = k;
            }
            if (!edited)
                return;

            UpdateTangents();
            OnEdited();
            OnEditingEnd();
        }

        private void SetTangentsLinear()
        {
            bool edited = false;
            for (int i = 0; i < _points.Count; i++)
            {
                var p = _points[i];
                if (!p.IsSelected)
                    continue;
                if (!edited)
                    OnEditingStart();
                edited = true;

                var k = _keyframes[p.Index];
                var value = Accessor.GetCurveValue(ref k.Value, p.Component);

                if (p.Index > 0)
                {
                    var o = _keyframes[p.Index - 1];
                    var oValue = Accessor.GetCurveValue(ref o.Value, p.Component);
                    var slope = (value - oValue) / (k.Time - o.Time);
                    slope = -slope;
                    Accessor.SetCurveValue(slope, ref k.TangentIn, p.Component);
                }

                if (p.Index < _keyframes.Count - 1)
                {
                    var o = _keyframes[p.Index + 1];
                    var oValue = Accessor.GetCurveValue(ref o.Value, p.Component);
                    var slope = (oValue - value) / (o.Time - k.Time);
                    Accessor.SetCurveValue(slope, ref k.TangentOut, p.Component);
                }

                _keyframes[p.Index] = k;
            }
            if (!edited)
                return;

            UpdateTangents();
            OnEdited();
            OnEditingEnd();
        }

        // Computes control points given knots k, this is the brain of the operation
        private static void ComputeControlPoints(float[] k, out float[] p1, out float[] p2)
        {
            // Reference: https://www.particleincell.com/2012/bezier-splines/

            var n = k.Length - 1;
            p1 = new float[n];
            p2 = new float[n];

            // rhs vector
            var a = new float[n];
            var b = new float[n];
            var c = new float[n];
            var r = new float[n];

            // left most segment
            a[0] = 0;
            b[0] = 2;
            c[0] = 1;
            r[0] = k[0] + 2 * k[1];

            // internal segments
            for (var i = 1; i < n - 1; i++)
            {
                a[i] = 1;
                b[i] = 4;
                c[i] = 1;
                r[i] = 4 * k[i] + 2 * k[i + 1];
            }

            // right segment
            a[n - 1] = 2;
            b[n - 1] = 7;
            c[n - 1] = 0;
            r[n - 1] = 8 * k[n - 1] + k[n];

            // solves Ax=b with the Thomas algorithm (from Wikipedia)
            for (var i = 1; i < n; i++)
            {
                var m = a[i] / b[i - 1];
                b[i] = b[i] - m * c[i - 1];
                r[i] = r[i] - m * r[i - 1];
            }

            p1[n - 1] = r[n - 1] / b[n - 1];
            for (var i = n - 2; i >= 0; --i)
                p1[i] = (r[i] - c[i] * p1[i + 1]) / b[i];

            // we have p1, now compute p2
            for (var i = 0; i < n - 1; i++)
                p2[i] = 2 * k[i + 1] - p1[i + 1];

            p2[n - 1] = 0.5f * (k[n] + p1[n - 1]);
        }

        private void SetTangentsSmooth()
        {
            var k1 = new T[_keyframes.Count];
            var k2 = new T[_keyframes.Count];
            var kk = new float[_keyframes.Count];
            var components = Accessor.GetCurveComponents();
            for (int component = 0; component < components; component++)
            {
                for (int i = 0; i < _keyframes.Count; i++)
                {
                    var v = _keyframes[i].Value;
                    kk[i] = Accessor.GetCurveValue(ref v, component);
                }
                ComputeControlPoints(kk, out var p1, out var p2);
                for (int i = 0; i < p1.Length; i++)
                {
                    Accessor.SetCurveValue(p1[i], ref k1[i], component);
                    Accessor.SetCurveValue(p2[i], ref k2[i], component);
                }
            }

            bool edited = false;
            for (int i = 0; i < _points.Count; i++)
            {
                var p = _points[i];
                if (!p.IsSelected)
                    continue;
                if (!edited)
                    OnEditingStart();
                edited = true;

                var k = _keyframes[p.Index];
                var value = Accessor.GetCurveValue(ref k.Value, p.Component);

                if (p.Index > 0)
                {
                    var o = _keyframes[p.Index - 1];
                    var slope = (Accessor.GetCurveValue(ref k2[p.Index], p.Component) - value) / (o.Time - k.Time);
                    Accessor.SetCurveValue(slope, ref k.TangentIn, p.Component);
                }

                if (p.Index < _keyframes.Count - 1)
                {
                    var o = _keyframes[p.Index + 1];
                    var slope = (Accessor.GetCurveValue(ref k1[p.Index], p.Component) - value) / (o.Time - k.Time);
                    Accessor.SetCurveValue(slope, ref k.TangentOut, p.Component);
                }

                _keyframes[p.Index] = k;
            }
            if (!edited)
                return;

            UpdateTangents();
            OnEdited();
            OnEditingEnd();
        }

        /// <inheritdoc />
        protected override void UpdateFPS()
        {
            if (FPS.HasValue)
            {
                float fps = FPS.Value;
                for (int i = 0; i < _keyframes.Count; i++)
                {
                    var k = _keyframes[i];
                    k.Time = Mathf.Floor(k.Time * fps) / fps;
                    _keyframes[i] = k;
                }
            }
        }

        /// <summary>
        /// Gets the keyframe point (in keyframes space).
        /// </summary>
        /// <param name="k">The keyframe.</param>
        /// <param name="component">The keyframe value component index.</param>
        /// <returns>The point in time/value space.</returns>
        private Float2 GetKeyframePoint(ref BezierCurve<T>.Keyframe k, int component)
        {
            return new Float2(k.Time, Accessor.GetCurveValue(ref k.Value, component));
        }

        private void DrawLine(BezierCurve<T>.Keyframe startK, BezierCurve<T>.Keyframe endK, int component, ref Rectangle viewRect)
        {
            var start = GetKeyframePoint(ref startK, component);
            var end = GetKeyframePoint(ref endK, component);

            var p1 = PointFromKeyframes(start, ref viewRect);
            var p2 = PointFromKeyframes(end, ref viewRect);

            var color = Colors[component].RGBMultiplied(0.6f);
            Render2D.DrawLine(p1, p2, color, 1.6f);
        }

        /// <inheritdoc />
        protected override void OnKeyframesChanged()
        {
            var components = Accessor.GetCurveComponents();
            while (_points.Count > _keyframes.Count * components)
            {
                var last = _points.Count - 1;
                _points[last].Dispose();
                _points.RemoveAt(last);
            }

            while (_points.Count < _keyframes.Count * components)
            {
                _points.Add(new KeyframePoint
                {
                    AutoFocus = false,
                    Size = KeyframesSize,
                    Editor = this,
                    Index = _points.Count / components,
                    Component = _points.Count % components,
                    Parent = _contents,
                });

                _refreshAfterEdit = true;
            }

            UpdateKeyframes();
            UpdateTooltips();

            base.OnKeyframesChanged();
        }

        /// <inheritdoc />
        public override void Evaluate(out T result, float time, bool loop = false)
        {
            var curve = new BezierCurve<T>
            {
                Keyframes = _keyframes.ToArray()
            };
            curve.Evaluate(out result, time, loop);
        }

        /// <inheritdoc />
        protected override float GetKeyframeTime(int index)
        {
            return _keyframes[index].Time;
        }

        /// <inheritdoc />
        protected override float GetKeyframeTime(object keyframe)
        {
            return ((BezierCurve<T>.Keyframe)keyframe).Time;
        }

        /// <inheritdoc />
        protected override T GetKeyframeValue(object keyframe)
        {
            return ((BezierCurve<T>.Keyframe)keyframe).Value;
        }

        /// <inheritdoc />
        protected override float GetKeyframeValue(object keyframe, int component)
        {
            var value = ((BezierCurve<T>.Keyframe)keyframe).Value;
            return Accessor.GetCurveValue(ref value, component);
        }

        /// <inheritdoc />
        protected override void AddKeyframe(Float2 keyframesPos)
        {
            var k = new BezierCurve<T>.Keyframe
            {
                Time = keyframesPos.X,
            };
            var components = Accessor.GetCurveComponents();
            for (int component = 0; component < components; component++)
            {
                Accessor.SetCurveValue(keyframesPos.Y, ref k.Value, component);
                Accessor.SetCurveValue(0.0f, ref k.TangentIn, component);
                Accessor.SetCurveValue(0.0f, ref k.TangentOut, component);
            }
            OnEditingStart();
            AddKeyframe(k);
            OnEditingEnd();
        }

        /// <inheritdoc />
        protected override void SetKeyframeInternal(int index, object keyframe)
        {
            _keyframes[index] = (BezierCurve<T>.Keyframe)keyframe;
        }

        /// <inheritdoc />
        protected override void SetKeyframeInternal(int index, float time, float value, int component)
        {
            var k = _keyframes[index];
            k.Time = time;
            Accessor.SetCurveValue(value, ref k.Value, component);
            _keyframes[index] = k;
        }

        /// <inheritdoc />
        protected override float GetKeyframeTangentInternal(int index, bool isIn, int component)
        {
            var k = _keyframes[index];
            var value = isIn ? k.TangentIn : k.TangentOut;
            return Accessor.GetCurveValue(ref value, component);
        }

        /// <inheritdoc />
        protected override void SetKeyframeTangentInternal(int index, bool isIn, int component, float value)
        {
            var k = _keyframes[index];
            if (isIn)
                Accessor.SetCurveValue(value, ref k.TangentIn, component);
            else
                Accessor.SetCurveValue(value, ref k.TangentOut, component);
            _keyframes[index] = k;
        }

        /// <inheritdoc />
        protected override void RemoveKeyframesInternal(HashSet<int> indicesToRemove)
        {
            var keyframes = _keyframes;
            _keyframes = new List<BezierCurve<T>.Keyframe>();
            for (int i = 0; i < keyframes.Count; i++)
            {
                if (!indicesToRemove.Contains(i))
                    _keyframes.Add(keyframes[i]);
            }
        }

        sealed class AllKeyframesProxy : IAllKeyframesProxy
        {
            [HideInEditor, NoSerialize]
            public BezierCurveEditor<T> Editor;

            [Collection(CanReorderItems = false, Spacing = 10)]
            public BezierCurve<T>.Keyframe[] Keyframes;

            public void Apply()
            {
                Editor.SetKeyframes(Keyframes);
            }
        }

        /// <inheritdoc />
        protected override IAllKeyframesProxy GetAllKeyframesEditingProxy()
        {
            return new AllKeyframesProxy
            {
                Editor = this,
                Keyframes = _keyframes.ToArray(),
            };
        }

        /// <inheritdoc />
        public override object[] GetKeyframes()
        {
            var keyframes = new object[_keyframes.Count];
            for (int i = 0; i < keyframes.Length; i++)
                keyframes[i] = _keyframes[i];
            return keyframes;
        }

        /// <inheritdoc />
        public override void SetKeyframes(object[] keyframes)
        {
            var data = new BezierCurve<T>.Keyframe[keyframes.Length];
            for (int i = 0; i < keyframes.Length; i++)
            {
                if (keyframes[i] is BezierCurve<T>.Keyframe asT)
                    data[i] = asT;
                else if (keyframes[i] is BezierCurve<object>.Keyframe asObj)
                    data[i] = new BezierCurve<T>.Keyframe(asObj.Time, (T)asObj.Value, (T)asObj.TangentIn, (T)asObj.TangentOut);
            }
            SetKeyframes(data);
        }

        /// <inheritdoc />
        public override int AddKeyframe(float time, object value)
        {
            return AddKeyframe(new BezierCurve<T>.Keyframe(time, (T)value));
        }

        /// <inheritdoc />
        public override int AddKeyframe(float time, object value, object tangentIn, object tangentOut)
        {
            return AddKeyframe(new BezierCurve<T>.Keyframe(time, (T)value, (T)tangentIn, (T)tangentOut));
        }

        /// <inheritdoc />
        public override void GetKeyframe(int index, out float time, out object value, out object tangentIn, out object tangentOut)
        {
            var k = _keyframes[index];
            time = k.Time;
            value = k.Value;
            tangentIn = k.TangentIn;
            tangentOut = k.TangentOut;
        }

        /// <inheritdoc />
        public override object GetKeyframe(int index)
        {
            return _keyframes[index];
        }

        /// <inheritdoc />
        public override void SetKeyframeValue(int index, object value)
        {
            var k = _keyframes[index];
            k.Value = (T)value;
            _keyframes[index] = k;

            UpdateKeyframes();
            UpdateTooltips();
            OnEdited();
        }

        /// <inheritdoc />
        public override void SetKeyframeValue(int index, object value, object tangentIn, object tangentOut)
        {
            var k = _keyframes[index];
            k.Value = (T)value;
            k.TangentIn = (T)tangentIn;
            k.TangentOut = (T)tangentOut;
            _keyframes[index] = k;

            UpdateKeyframes();
            UpdateTooltips();
            OnEdited();
        }

        /// <inheritdoc />
        public override Float2 GetKeyframePoint(int index, int component)
        {
            var k = _keyframes[index];
            return new Float2(k.Time, Accessor.GetCurveValue(ref k.Value, component));
        }

        /// <inheritdoc />
        public override void OnKeyframesGet(string trackName, Action<string, float, object> get)
        {
            for (int i = 0; i < _keyframes.Count; i++)
            {
                var k = _keyframes[i];
                get(trackName, k.Time, k);
            }
        }

        /// <inheritdoc />
        public override void OnKeyframesSet(List<KeyValuePair<float, object>> keyframes)
        {
            OnEditingStart();
            _keyframes.Clear();
            if (keyframes != null)
            {
                foreach (var e in keyframes)
                {
                    var k = (BezierCurve<T>.Keyframe)e.Value;
                    _keyframes.Add(new BezierCurve<T>.Keyframe(e.Key, k.Value, k.TangentIn, k.TangentOut));
                }
            }
            OnKeyframesChanged();
            OnEdited();
            OnEditingEnd();
        }

        /// <inheritdoc />
        public override int KeyframesCount => _keyframes.Count;

        /// <inheritdoc />
        public override void UpdateKeyframes()
        {
            if (_points.Count == 0)
            {
                // No keyframes
                _contents.Bounds = Rectangle.Empty;
                return;
            }

            _mainPanel.LockChildrenRecursive();

            // Place keyframes
            Rectangle curveContentAreaBounds = _mainPanel.GetClientArea();
            var viewScale = ViewScale;
            var pointsSize = _showCollapsed ? new Float2(4.0f / viewScale.X, Height - 2.0f) : KeyframesSize / viewScale;
            for (int i = 0; i < _points.Count; i++)
            {
                var p = _points[i];
                var k = _keyframes[p.Index];
                var point = GetKeyframePoint(ref k, p.Component);
                var location = new Float2
                (
                 point.X * UnitsPerSecond - pointsSize.X * 0.5f,
                 point.Y * -UnitsPerSecond - pointsSize.Y * 0.5f + curveContentAreaBounds.Height
                );
                if (_showCollapsed)
                {
                    location.Y = 1.0f;
                    p.Visible = p.Component == 0;
                }
                else
                {
                    p.Visible = true;
                }
                p.Bounds = new Rectangle(location, pointsSize);
            }

            // Calculate bounds
            var bounds = _points[0].Bounds;
            for (int i = 1; i < _points.Count; i++)
                bounds = Rectangle.Union(bounds, _points[i].Bounds);

            // Adjust contents bounds to fill the curve area
            if (EnablePanning != UseMode.Off || !ShowCollapsed)
            {
                bounds.Width = Mathf.Max(bounds.Width, 1.0f);
                bounds.Height = Mathf.Max(bounds.Height, 1.0f);
                bounds.Location = ApplyUseModeMask(EnablePanning, bounds.Location, _contents.Location);
                bounds.Size = ApplyUseModeMask(EnablePanning, bounds.Size, _contents.Size);
                if (!_contents._isMovingSelection)
                    _contents.Bounds = bounds;
            }
            else if (_contents.Bounds == Rectangle.Empty)
            {
                _contents.Bounds = Rectangle.Union(bounds, new Rectangle(Float2.Zero, Float2.One));
            }

            // Offset the keyframes (parent container changed its location)
            var posOffset = _contents.Location;
            for (var i = 0; i < _points.Count; i++)
            {
                _points[i].Location -= posOffset;
            }

            UpdateTangents();

            _mainPanel.UnlockChildrenRecursive();
            _mainPanel.PerformLayout();
        }

        /// <inheritdoc />
        public override void UpdateTangents()
        {
            // Find selected keyframe
            Rectangle curveContentAreaBounds = _mainPanel.GetClientArea();
            var selectedCount = 0;
            var selectedIndex = -1;
            KeyframePoint selectedKeyframe = null;
            var selectedComponent = -1;
            for (int i = 0; i < _points.Count; i++)
            {
                var p = _points[i];
                if (p.IsSelected)
                {
                    selectedIndex = p.Index;
                    selectedKeyframe = p;
                    selectedComponent = p.Component;
                    selectedCount++;
                }
            }

            // Place tangents (only for a single selected keyframe)
            if (selectedCount == 1 && !_showCollapsed)
            {
                var posOffset = _contents.Location;
                var k = _keyframes[selectedIndex];
                for (int i = 0; i < _tangents.Length; i++)
                {
                    var t = _tangents[i];

                    t.Index = selectedIndex;
                    t.Point = selectedKeyframe;
                    t.Component = selectedComponent;

                    var tangent = t.TangentValue;
                    var direction = t.IsIn ? -1.0f : 1.0f;
                    var offset = 30.0f;
                    var location = GetKeyframePoint(ref k, selectedComponent);
                    t.Size = KeyframesSize / ViewScale;
                    t.Location = new Float2
                    (
                     location.X * UnitsPerSecond - t.Width * 0.5f + offset * direction,
                     location.Y * -UnitsPerSecond - t.Height * 0.5f + curveContentAreaBounds.Height - offset * tangent
                    );

                    var isFirst = selectedIndex == 0 && t.IsIn;
                    var isLast = selectedIndex == _keyframes.Count - 1 && !t.IsIn;
                    t.Visible = !isFirst && !isLast;
                    t.UpdateTooltip();

                    if (t.Visible)
                        _tangents[i].Location -= posOffset;
                }
            }
            else
            {
                for (int i = 0; i < _tangents.Length; i++)
                {
                    _tangents[i].Visible = false;
                }
            }
        }

        /// <inheritdoc />
        protected override void OnShowContextMenu(ContextMenu.ContextMenu cm, int selectionCount)
        {
            if (selectionCount != 0)
            {
                cm.AddSeparator();
                cm.AddButton("Reset tangents", ResetTangents);
                cm.AddButton("Linear tangents", SetTangentsLinear);
                cm.AddButton("Smooth tangents", SetTangentsSmooth);
            }
        }

        /// <inheritdoc />
        protected override void DrawCurve(ref Rectangle viewRect)
        {
            var components = Accessor.GetCurveComponents();
            for (int component = 0; component < components; component++)
            {
                if (ShowStartEndLines)
                {
                    var start = new BezierCurve<T>.Keyframe
                    {
                        Value = DefaultValue,
                        Time = -10000000.0f,
                    };
                    var end = new BezierCurve<T>.Keyframe
                    {
                        Value = DefaultValue,
                        Time = 10000000.0f,
                    };

                    if (_keyframes.Count == 0)
                    {
                        DrawLine(start, end, component, ref viewRect);
                    }
                    else
                    {
                        DrawLine(start, _keyframes[0], component, ref viewRect);
                        DrawLine(_keyframes[_keyframes.Count - 1], end, component, ref viewRect);
                    }
                }

                var color = Colors[component];
                for (int i = 1; i < _keyframes.Count; i++)
                {
                    var startK = _keyframes[i - 1];
                    var endK = _keyframes[i];

                    var start = GetKeyframePoint(ref startK, component);
                    var end = GetKeyframePoint(ref endK, component);

                    var startTangent = Accessor.GetCurveValue(ref startK.TangentOut, component);
                    var endTangent = Accessor.GetCurveValue(ref endK.TangentIn, component);

                    var tangentScale = (endK.Time - startK.Time) / 3.0f;
                    var p1 = PointFromKeyframes(start, ref viewRect);
                    var p2 = PointFromKeyframes(start + new Float2(0, startTangent * tangentScale), ref viewRect);
                    var p3 = PointFromKeyframes(end + new Float2(0, endTangent * tangentScale), ref viewRect);
                    var p4 = PointFromKeyframes(end, ref viewRect);

                    Render2D.DrawSpline(p1, p2, p3, p4, color);
                }
            }
        }

        /// <inheritdoc />
        public override void OnDestroy()
        {
            _keyframes.Clear();

            base.OnDestroy();
        }
    }
}
