Editor/Sprite/SpriteEditor/Timeline/Timeline.cs
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;

namespace SpriteTools.SpriteEditor.Timeline;

public class Timeline : Widget
{
    public SpriteResource Sprite { get; set; }
    public MainWindow MainWindow { get; }

    public List<FrameButton> Buttons = new();

    ScrollArea scrollArea;
    IconButton buttonPlay;

    IntegerControlWidget widgetCurrentFrame;
    Label labelFrameCount;
    FloatSlider sliderFrameSize;

    public int CurrentFrame
    {
        get => MainWindow.CurrentFrameIndex + 1;
        set
        {
            if (value <= 0) MainWindow.CurrentFrameIndex = 0;
            else if (value > MainWindow.SelectedAnimation.Frames.Count) MainWindow.CurrentFrameIndex = MainWindow.SelectedAnimation.Frames.Count - 1;
            else MainWindow.CurrentFrameIndex = value - 1;
        }
    }

    public Timeline(MainWindow mainWindow) : base(null)
    {
        MainWindow = mainWindow;

        Name = "Timeline";
        WindowTitle = "Timeline";
        SetWindowIcon("view_column");

        Layout = Layout.Column();

        MinimumWidth = 512f;
        MinimumHeight = 128f;

        var bannerLayout = Layout.Row();
        bannerLayout.Margin = 4;

        var label1 = new Label(this);
        label1.Text = "Frame:";
        bannerLayout.Add(label1);
        bannerLayout.AddSpacingCell(4);

        this.GetSerialized().TryGetProperty(nameof(CurrentFrame), out var currentFrameIndex);
        widgetCurrentFrame = new IntegerControlWidget(currentFrameIndex);
        widgetCurrentFrame.MaximumWidth = 64f;
        bannerLayout.Add(widgetCurrentFrame);
        bannerLayout.AddSpacingCell(4);

        labelFrameCount = new Label(this);
        labelFrameCount.Text = "/ 0";
        bannerLayout.Add(labelFrameCount);

        bannerLayout.AddStretchCell();

        var buttonFrameFirst = new IconButton("first_page");
        buttonFrameFirst.StatusTip = "First Frame";
        buttonFrameFirst.OnClick = () => { MainWindow.FrameFirst(); };
        bannerLayout.Add(buttonFrameFirst);
        bannerLayout.AddSpacingCell(4);

        var buttonFramePrevious = new IconButton("navigate_before");
        buttonFramePrevious.StatusTip = "Previous Frame";
        buttonFramePrevious.OnClick = () => { MainWindow.FramePrevious(); };
        bannerLayout.Add(buttonFramePrevious);
        bannerLayout.AddSpacingCell(4);

        buttonPlay = new IconButton("play_arrow");
        buttonPlay.OnClick = () =>
        {
            MainWindow.PlayPause();
        };
        bannerLayout.Add(buttonPlay);
        bannerLayout.AddSpacingCell(4);
        UpdatePlayButton();

        var buttonFrameNext = new IconButton("navigate_next");
        buttonFrameNext.StatusTip = "Next Frame";
        buttonFrameNext.OnClick = () => { MainWindow.FrameNext(); };
        bannerLayout.Add(buttonFrameNext);
        bannerLayout.AddSpacingCell(4);

        var buttonFrameLast = new IconButton("last_page");
        buttonFrameLast.StatusTip = "Last Frame";
        buttonFrameLast.OnClick = () => { MainWindow.FrameLast(); };
        bannerLayout.Add(buttonFrameLast);
        bannerLayout.AddSpacingCell(4);

        bannerLayout.AddStretchCell();

        var text = bannerLayout.Add(new Label("Frame Size:"));
        text.HorizontalSizeMode = SizeMode.CanShrink;
        bannerLayout.AddSpacingCell(4);
        sliderFrameSize = new FloatSlider(this);
        sliderFrameSize.HorizontalSizeMode = SizeMode.CanGrow;
        sliderFrameSize.Minimum = 16f;
        sliderFrameSize.Maximum = 128f;
        sliderFrameSize.Step = 1f;
        sliderFrameSize.Value = FrameButton.FrameSize;
        sliderFrameSize.MinimumWidth = 128f;
        sliderFrameSize.OnValueEdited = () =>
        {
            FrameButton.FrameSize = sliderFrameSize.Value;
            Update();
        };
        bannerLayout.Add(sliderFrameSize);

        Layout.Add(bannerLayout);

        scrollArea = new ScrollArea(this);
        scrollArea.Canvas = new Widget();
        scrollArea.Canvas.Layout = Layout.Row();
        scrollArea.Canvas.Layout.Spacing = 4;

        Layout.Add(scrollArea);

        SetSizeMode(SizeMode.Default, SizeMode.CanShrink);

        UpdateFrameList();

        MainWindow.OnAnimationSelected += UpdateFrameList;
        MainWindow.OnPlayPause += UpdatePlayButton;
    }

    public override void OnDestroyed()
    {
        base.OnDestroyed();

        MainWindow.OnAnimationSelected -= UpdateFrameList;
        MainWindow.OnPlayPause -= UpdatePlayButton;
    }

    [EditorEvent.Hotload]
    void Hotload()
    {
        UpdateFrameList();
        UpdatePlayButton();
    }

    void UpdatePlayButton()
    {
        buttonPlay.Icon = MainWindow.Playing ? "pause" : "play_arrow";
        buttonPlay.StatusTip = MainWindow.Playing ? "Pause Animation" : "Play Animation";
        buttonPlay.Update();
    }

    public void UpdateFrameList()
    {
        if (MainWindow?.SelectedAnimation is null) return;

        scrollArea.Canvas.Layout.Clear(true);
        scrollArea.Canvas.VerticalSizeMode = SizeMode.Flexible;
        scrollArea.Canvas.HorizontalSizeMode = SizeMode.Flexible;

        Buttons.Clear();
        if (MainWindow.SelectedAnimation.Frames is not null)
        {
            int index = 0;
            foreach (var frame in MainWindow.SelectedAnimation.Frames)
            {
                var frameButton = new FrameButton(this, MainWindow, index);
                scrollArea.Canvas.Layout.Add(frameButton);
                Buttons.Add(frameButton);
                index++;
            }
        }

        if (MainWindow.SelectedAnimation.LoopStart is not null && MainWindow.SelectedAnimation.LoopStart < 0)
        {
            MainWindow.SelectedAnimation.LoopStart = null;
        }
        if (MainWindow.SelectedAnimation.LoopEnd is not null && MainWindow.SelectedAnimation.LoopEnd >= MainWindow.SelectedAnimation.Frames.Count)
        {
            MainWindow.SelectedAnimation.LoopEnd = null;
        }

        var addButton = new IconButton("add");
        addButton.Width = 128;
        addButton.Height = 128;
        addButton.Size = new Vector2(128, 128);
        addButton.OnClick = () => LoadImage();

        scrollArea.Canvas.Layout.Add(addButton);
        widgetCurrentFrame.Range = new Vector2(1, MainWindow.SelectedAnimation.Frames.Count);
        widgetCurrentFrame.RangeClamped = true;
        widgetCurrentFrame.HasRange = true;
    }

    void LoadImage()
    {
        if (MainWindow.SelectedAnimation is null) return;

        var picker = AssetPicker.Create(this, AssetType.ImageFile, new() { EnableMultiselect = true });
        picker.Window.StateCookie = "SpriteEditor.Import";
        picker.Window.RestoreFromStateCookie();
        picker.Window.Title = $"Import Frame - {MainWindow.Sprite.ResourceName} - {MainWindow.SelectedAnimation.Name}";
        // picker.Assets = new List<Asset>() { Asset };
        // picker.OnAssetHighlighted = x => Asset = x.First();
        picker.OnAssetPicked = x =>
        {
            MainWindow.PushUndo($"Add Frame to {MainWindow.SelectedAnimation.Name}");
            List<SpriteAnimationFrame> frames = new List<SpriteAnimationFrame>();
            foreach (var asset in x)
            {
                frames.Add(new SpriteAnimationFrame(asset.GetSourceFile()));
            }
            MainWindow.SelectedAnimation.Frames.AddRange(frames);
            MainWindow.PushRedo();
            UpdateFrameList();
        };
        picker.Window.Show();
    }

    protected override void OnWheel(WheelEvent e)
    {
        base.OnWheel(e);

        if (e.HasCtrl)
        {
            float value = FrameButton.FrameSize + e.Delta / 10f;
            value = value.Clamp(16, 128);
            sliderFrameSize.Value = value;
            FrameButton.FrameSize = value;
            Update();
        }
    }

    protected override void OnKeyPress(KeyEvent e)
    {
        base.OnKeyPress(e);

        if (e.Key == KeyCode.Delete || e.Key == KeyCode.Backspace)
        {
            if (MainWindow.SelectedAnimation is null) return;

            List<int> indexes = new();
            foreach (var button in FrameButton.Selected)
            {
                if (!(button?.IsValid ?? false)) continue;
                indexes.Add(button.FrameIndex);
            }

            if (indexes.Count > 0)
            {
                indexes.Sort();
                MainWindow.PushUndo($"Remove {indexes.Count} Frame(s) from {MainWindow.SelectedAnimation.Name}");

                for (int i = indexes.Count - 1; i >= 0; i--)
                {
                    MainWindow.SelectedAnimation.Frames.RemoveAt(indexes[i]);
                }

                MainWindow.PushRedo();
            }

            UpdateFrameList();
        }
    }

    protected override void OnKeyRelease(KeyEvent e)
    {
        base.OnKeyRelease(e);

        if (e.Key == KeyCode.Left)
        {
            MainWindow.FramePrevious();
        }
        else if (e.Key == KeyCode.Right)
        {
            MainWindow.FrameNext();
        }
    }

    [EditorEvent.Frame]
    void Frame()
    {
        if (MainWindow.SelectedAnimation is null)
        {
            labelFrameCount.Text = "/ 0";
            return;
        }

        labelFrameCount.Text = $"/ {MainWindow.SelectedAnimation.Frames.Count}";
    }

}