Editor/Tileset/TilesetTools/Tools/RectangleTileTool.cs
using System;
using System.Collections.Generic;
using Editor;
using Sandbox;

namespace SpriteTools.TilesetTool.Tools;

/// <summary>
/// Used to paint tiles on the selected layer.
/// </summary>
[Title("Rectangle")]
[Icon("crop_square")]
[Alias("tileset-tools.rectangle-tool")]
[Group("1")]
[Order(0)]
public class RectangleTileTool : BaseTileTool
{
    public RectangleTileTool(TilesetTool parent) : base(parent) { }

    /// <summary>
    /// If enabled, the rectangle will only draw the border and not be filled.
    /// </summary>
    [Group("Rectangle Tool"), Property] public bool Hollow { get; set; } = false;

    Vector2 startPos;
    Vector2 lastMin;
    Vector2 lastMax;
    Vector2 lastTilePos;
    bool holding = false;
    bool deleting = false;

    public override void OnUpdate()
    {
        if (!CanUseTool()) return;

        var pos = GetGizmoPos();
        Parent._sceneObject.Transform = new Transform(pos, Rotation.Identity, 1);
        Parent._sceneObject.RenderingEnabled = true;

        var tileSize = Parent.SelectedLayer.TilesetResource.GetTileSize();
        var tilePos = (pos - Parent.SelectedComponent.WorldPosition) / tileSize;

        if (holding)
        {
            var positions = new List<Vector2>();

            var min = Vector2.Min(startPos - tilePos, 0);
            var max = Vector2.Max(startPos - tilePos, 0);

            if (deleting)
            {
                Parent._sceneObject.RenderingEnabled = false;
                using (Gizmo.Scope("delete"))
                {
                    Gizmo.Draw.Color = Color.Red.WithAlpha(0.5f);
                    Gizmo.Draw.SolidBox(new BBox(tilePos * tileSize + min * tileSize, tilePos * tileSize + max * tileSize + tileSize));
                }
                Parent._sceneObject.SetPositions(new List<Vector2> { Vector2.Zero });
            }
            else
            {
                positions = GetPositions(min, max);
                Parent._sceneObject.RenderingEnabled = true;
                if (tilePos != lastTilePos || min != lastMin || max != lastMax)
                {
                    UpdateTilePositions(positions);
                    lastMin = min;
                    lastMax = max;
                    lastTilePos = tilePos;
                }
            }

            if (!Gizmo.IsLeftMouseDown && !Gizmo.IsRightMouseDown)
            {
                var brush = AutotileBrush;
                holding = false;
                Parent._sceneObject.ClearPositions();

                if (deleting)
                {
                    positions = GetPositions(min, max);
                    foreach (var ppos in positions)
                    {
                        if (brush is null)
                            Parent.EraseTile(tilePos + ppos, false);
                        else
                            Parent.EraseAutoTile(brush, (Vector2Int)(tilePos + ppos));
                    }
                    Parent.SelectedComponent.IsDirty = true;
                }
                else
                {
                    var tile = TilesetTool.Active.SelectedTile;
                    if (brush is null)
                    {
                        foreach (var ppos in positions)
                        {
                            Parent.PlaceTile((Vector2Int)(tilePos + ppos), tile.Id, Vector2Int.Zero, false);
                        }
                    }
                    else
                    {
                        foreach (var ppos in positions)
                        {
                            Parent.PlaceAutotile(brush.Id, (Vector2Int)(tilePos + ppos), false);
                        }

                        foreach (var ppos in positions)
                        {
                            Parent.SelectedLayer.UpdateAutotile(brush.Id, (Vector2Int)(tilePos + ppos), false);
                        }
                    }
                    Parent.SelectedComponent.IsDirty = true;
                }
                _componentUndoScope?.Dispose();
                _componentUndoScope = null;
            }
        }
        else if (Gizmo.IsLeftMouseDown || Gizmo.IsRightMouseDown)
        {
            deleting = Gizmo.IsRightMouseDown;
            if (_componentUndoScope is null)
            {
                _componentUndoScope = SceneEditorSession.Active.UndoScope(deleting ? "Erase Tile Rectangle" : "Paint Tile Rectangle")
                    .WithComponentChanges(Parent.SelectedComponent).Push();
            }
            startPos = tilePos;
            holding = true;
        }
        else
        {
            if (tilePos != lastTilePos)
            {
                UpdateTilePositions(new List<Vector2> { 0 });
                lastTilePos = tilePos;
            }
        }
    }

    List<Vector2> GetPositions(Vector2 min, Vector2 max)
    {
        var positions = new List<Vector2>();

        if (min == max)
        {
            positions.Add(min);
            return positions;
        }

        if (Vector2.Distance(min, max) < 2_500)
        {
            for (int x = (int)min.x; x <= (int)max.x; x++)
            {
                for (int y = (int)min.y; y <= (int)max.y; y++)
                {
                    if (Hollow && (x != min.x && x != max.x && y != min.y && y != max.y)) continue;
                    positions.Add(new Vector2(x, y));
                }
            }
        }

        return positions;
    }

    [Shortcut("tileset-tools.rectangle-tool", "r", typeof(SceneViewportWidget))]
    public static void ActivateSubTool()
    {
        if (EditorToolManager.CurrentModeName != nameof(TilesetTool)) return;
        EditorToolManager.SetSubTool(nameof(RectangleTileTool));
    }
}