General/BBox2.cs
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace ExtendedBox.General;

public struct BBox2 : IEquatable<BBox2>
{
    [JsonInclude]
    public Vector2 Mins;
    [JsonInclude]
    public Vector2 Maxs;

    [JsonIgnore]
    public IEnumerable<Vector2> Corners
    {
        get
        {
            yield return new(Mins.x, Mins.y);
            yield return new(Maxs.x, Mins.y);
            yield return new(Maxs.x, Maxs.y);
            yield return new(Mins.x, Maxs.y);
        }
    }

    [JsonIgnore]
    public readonly Vector2 Center => Mins + Size * 0.5f;
    [JsonIgnore]
    public readonly Vector2 Size => Maxs - Mins;
    [JsonIgnore]
    public readonly Vector2 Extents => Size * 0.5f;

    [JsonIgnore]
    public readonly Vector2 RandomPointInside => Random.Shared.VectorInSquare(in this);

    [JsonIgnore]
    public readonly float Volume
    {
        get
        {
            Vector2 vector = Size.Abs();
            return vector.x * vector.y;
        }
    }

    #region constructors
    public BBox2(Vector2 mins, Vector2 maxs)
    {
        Mins = Vector2.Min(mins, maxs);
        Maxs = Vector2.Max(mins, maxs);
    }

    public static BBox2 FromRadius(float height, Vector2 radius)
    {
        var mins = new Vector2(-radius.x, -radius.y);
        var maxs = new Vector2(radius.x, radius.y);

        return new(mins, maxs);
    }

    public static BBox2 FromPositionAndSize(in Vector2 center, Vector2 size = default)
    {
        BBox2 result = default;
        result.Mins = center - size * 0.5f;
        result.Maxs = center + size * 0.5f;
        return result;
    }

    public static BBox2 FromBoxes(IEnumerable<BBox2> boxes)
    {
        using IEnumerator<BBox2> enumerator = boxes.GetEnumerator();
        if(!enumerator.MoveNext())
        {
            return default;
        }

        BBox2 result = enumerator.Current;
        while(enumerator.MoveNext())
        {
            BBox2 point = enumerator.Current;
            result = result.AddBBox(in point);
        }

        return result;
    }

    public static BBox2 FromPoints(IEnumerable<Vector2> points, Vector2 size = default)
    {
        using IEnumerator<Vector2> enumerator = points.GetEnumerator();
        if(!enumerator.MoveNext())
        {
            return default;
        }

        Vector2 center = enumerator.Current;
        BBox2 result = FromPositionAndSize(in center, size);
        while(enumerator.MoveNext())
        {
            center = enumerator.Current;
            BBox2 point = FromPositionAndSize(in center, size);
            result = result.AddBBox(in point);
        }

        return result;
    }
    #endregion

    #region convertation methods
    public readonly BBox2 Translate(in Vector2 point)
    {
        BBox2 result = this;
        result.Mins += point;
        result.Maxs += point;
        return result;
    }

    public readonly BBox2 Rotate(in float rotation) // TODO: optimize
    {
        BBox bbox = new(new Vector3(Mins.x, Mins.y, 0), new Vector3(Maxs.x, Maxs.y, 0));
        bbox = bbox.Rotate(Rotation.FromYaw(rotation));
        return new(new Vector2(bbox.Mins.x, bbox.Mins.y), new Vector2(bbox.Maxs.x, bbox.Maxs.y));
    }

    public readonly BBox2 Transform(Vector2 position, float rotation, Vector2 scale)
    {
        return Scale(in scale).Rotate(in rotation).Translate(in position);
    }

    public readonly BBox2 Scale(in Vector2 scale)
    {
        var mins = -scale * Extents + Center;
        var maxs = scale * Extents + Center;

        return new(mins, maxs);
    }

    public readonly bool Contains(in BBox2 b, bool includeMaxs = false)
    {
        if(includeMaxs)
        {
            return b.Mins.x >= Mins.x && b.Maxs.x <= Maxs.x &&
                b.Mins.y >= Mins.y && b.Maxs.y <= Maxs.y;
        }


        return b.Mins.x >= Mins.x && b.Maxs.x < Maxs.x &&
            b.Mins.y >= Mins.y && b.Maxs.y < Maxs.y;
    }

    public readonly bool Contains(in Vector2 b, bool includeMaxs = false)
    {
        if(includeMaxs)
        {
            return b.x >= Mins.x && b.x <= Maxs.x &&
                b.y >= Mins.y && b.y <= Maxs.y;
        }

        return b.x >= Mins.x && b.x < Maxs.x &&
            b.y >= Mins.y && b.y < Maxs.y;
    }

    public readonly bool Overlaps(in BBox2 b)
    {
        return Mins.x < b.Maxs.x && b.Mins.x < Maxs.x &&
            Mins.y < b.Maxs.y && b.Mins.y < Maxs.y;
    }

    public readonly BBox2 GetIntersection(BBox2 other)
    {
        if(!Overlaps(other))
            return new(Vector2Int.Zero, Vector2Int.Zero);

        return new(Mins.ComponentMax(other.Mins), Maxs.ComponentMin(other.Maxs));
    }

    public readonly BBox2 AddPoint(in Vector2 point)
    {
        BBox2 result = this;
        result.Mins = Vector2.Min(Mins, point);
        result.Maxs = Vector2.Max(Maxs, point);
        return result;
    }

    public readonly BBox2 AddBBox(in BBox2 bbox)
    {
        BBox2 result = this;
        result.Mins = Vector2.Min(Mins, bbox.Mins);
        result.Maxs = Vector2.Max(Maxs, bbox.Maxs);
        return result;
    }

    public readonly BBox2 Grow(in Vector2 skin)
    {
        BBox2 result = this;
        result.Mins -= skin;
        result.Maxs += skin;
        return result;
    }

    public readonly Vector2 ClosestPoint(in Vector2 point)
    {
        return point.Clamp(Mins, Maxs);
    }
    #endregion

    #region operators
    public static BBox2 operator *(BBox2 c1, Vector2 c2)
    {
        c1.Mins *= c2;
        c1.Maxs *= c2;
        return c1;
    }

    public static BBox2 operator +(BBox2 c1, Vector2 c2)
    {
        c1.Mins += c2;
        c1.Maxs += c2;
        return c1;
    }

    public static bool operator ==(BBox2 left, BBox2 right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(BBox2 left, BBox2 right)
    {
        return !(left == right);
    }
    #endregion


    public override readonly string ToString()
    {
        return $"mins {Mins}, maxs {Maxs}";
    }


    public readonly BBox2 Snap(float distance)
    {
        return new BBox2(Mins.SnapToGrid(distance), Maxs.SnapToGrid(distance));
    }



    public override readonly bool Equals(object? obj)
    {
        if(obj is BBox2 o)
            return Equals(o);
        return false;
    }

    public readonly bool Equals(BBox2 o)
    {
        return Mins == o.Mins && Maxs == o.Maxs;
    }

    public override readonly int GetHashCode()
    {
        return HashCode.Combine(Mins, Maxs);
    }
}