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

namespace ExtendedBox.General;

public struct BBox2Int : IEquatable<BBox2Int>
{
    [JsonInclude]
    public Vector2Int Mins;
    [JsonInclude]
    public Vector2Int Maxs;

    [JsonIgnore]
    public IEnumerable<Vector2Int> 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 Vector2Int Size => Maxs - Mins;
    [JsonIgnore]
    public readonly Vector2 Extents => Size * 0.5f;

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

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

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


    public static BBox2Int FromRadius(in Vector2Int radius)
    {
        var mins = new Vector2Int(-radius.x, -radius.y);
        var maxs = new Vector2Int(radius.x, radius.y);

        return new(mins, maxs);
    }

    public static BBox2Int FromMinsAndSize(in Vector2Int mins, Vector2Int size = default)
    {
        BBox2Int result = default;
        result.Mins = mins;
        result.Maxs = mins + size;
        return result;
    }

    public static BBox2Int FromPositionAndRadius(in Vector2Int center, Vector2Int radius = default)
    {
        BBox2Int result = default;
        result.Mins = center - radius;
        result.Maxs = center + radius;
        return result;
    }

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

        BBox2Int result = enumerator.Current;
        while(enumerator.MoveNext())
        {
            BBox2Int bbox = enumerator.Current;
            result = result.AddBBox(in bbox);
        }

        return result;
    }

    public static BBox2Int FromPoints(IEnumerable<Vector2Int> points)
    {
        using IEnumerator<Vector2Int> enumerator = points.GetEnumerator();
        if(!enumerator.MoveNext())
        {
            return default;
        }

        Vector2Int center = enumerator.Current;
        BBox2Int result = new(center, center);
        while(enumerator.MoveNext())
        {
            center = enumerator.Current;
            result = result.AddPoint(center);
        }

        return result;
    }

    public static BBox2Int FromPointsAndRadius(IEnumerable<Vector2Int> points, Vector2Int radius = default)
    {
        using IEnumerator<Vector2Int> enumerator = points.GetEnumerator();
        if(!enumerator.MoveNext())
        {
            return default;
        }

        Vector2Int center = enumerator.Current;
        BBox2Int result = FromPositionAndRadius(in center, radius);
        while(enumerator.MoveNext())
        {
            center = enumerator.Current;
            BBox2Int bbox = FromPositionAndRadius(in center, radius);
            result = result.AddBBox(in bbox);
        }

        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 BBox2Int Translate(in Vector2Int point)
    {
        BBox2Int 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 BBox2Int 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 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) => other.GetIntersection(this);
    public readonly BBox2Int GetIntersection(BBox2Int 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 BBox2Int AddPoint(in Vector2Int point)
    {
        BBox2Int result = this;
        result.Mins = Vector2Int.Min(Mins, point);
        result.Maxs = Vector2Int.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 BBox2Int AddBBox(in BBox2Int bbox)
    {
        BBox2Int result = this;
        result.Mins = Vector2Int.Min(Mins, bbox.Mins);
        result.Maxs = Vector2Int.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 BBox2Int Grow(in Vector2Int skin)
    {
        BBox2Int 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 *(BBox2Int c1, Vector2 c2)
    {
        return new(c1.Mins * c2, c1.Maxs * c2);
    }

    public static BBox2Int operator *(BBox2Int c1, Vector2Int c2)
    {
        c1.Mins *= c2;
        c1.Maxs *= c2;
        return c1;
    }

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

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

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

    public static bool operator !=(BBox2Int left, BBox2Int right)
    {
        return !(left == right);
    }

    public static implicit operator BBox2(BBox2Int bBoxInt) => new(bBoxInt.Mins, bBoxInt.Maxs);
    public static explicit operator BBox2Int(BBox2 bBoxInt) => new((Vector2Int)bBoxInt.Mins, (Vector2Int)bBoxInt.Maxs);
    #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 readonly BBox2Int Snap(int distance)
    {
        return new BBox2Int(Mins.SnapToGrid(distance), Maxs.SnapToGrid(distance));
    }



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

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

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