using System; using System.Collections.Generic; using System.Linq; using KitsuneCafe.Sys.Numerics; namespace KitsuneCafe.Sys { public record Index(int Value, bool IsFromEnd = false) { public static Index Start = new(0); public static Index End = new(0, true); public static Index NegativeInfinity = new(int.MinValue); public static Index Infinity = new(int.MaxValue); public static Index FromStart(int value) => new(value); public static Index FromEnd(int value) => new(value, true); public int GetOffset(int length) => IsFromEnd ? length - Value : Value; public static implicit operator int(Index value) => value.IsFromEnd ? -value.Value : value.Value; public static implicit operator Index(int value) => new(value, value < 0); public static implicit operator global::System.Index(Index index) => new(index.Value, index.IsFromEnd); public static implicit operator Index(global::System.Index index) => new(index.Value, index.IsFromEnd); } public record Range(Index Start, Index End) { public static Range All = new(Index.Start, Index.End); public static Range StartAt(Index start) => new(start, Index.End); public static Range EndAt(Index end) => new(Index.Start, end); public static Range OfLength(Index offset, int length) => new(offset, offset + length); public ClampingRange Clamp() => new(Start, End); public ShiftingRange Shift() => new(Start, End); public WrappingRange Wrap() => new(Start, End); public static implicit operator System.Range(Range range) => new(range.Start, range.End); public static implicit operator Range(System.Range range) => new(range.Start, range.End); public virtual (int Offset, int Length) GetOffsetAndLength(int length) { var start = Start.GetOffset(length); var end = End.GetOffset(length); return (start, end - start); } public virtual bool Contains(int length, Index index) { var start = Start.GetOffset(length); var end = End.GetOffset(length); return index >= start && index < end; } public virtual IEnumerable Indices(int length) { var (start, len) = GetOffsetAndLength(length); UnityEngine.Debug.Log($"making indices: start {start}, length {len}"); for (int i = start; i < start + len; i++) { yield return i; } } public virtual int[] ToArray(int length) { UnityEngine.Debug.Log($"making array of indices of length {length}"); return Indices(length).ToArray(); } public void Deconstruct(out Index start, out Index end) { start = Start; end = End; } } public record ClampingRange(Index Start, Index End) : Range(Start, End) { public override (int Offset, int Length) GetOffsetAndLength(int length) { var (start, len) = base.GetOffsetAndLength(length); return (start, Math.Clamp(len, 0, length - 1)); } } public record ShiftingRange(Index Start, Index End) : Range(Start, End) { public override (int Offset, int Length) GetOffsetAndLength(int length) { var (start, len) = base.GetOffsetAndLength(length); int newStart = start; int newEnd = start + len; if (newEnd > length) { int offset = newEnd - length; newStart -= offset; newEnd -= offset; } if (newStart < 0) { int shiftAmount = 0 - newStart; newStart += shiftAmount; newEnd += shiftAmount; } int newLength = Math.Min(newEnd - newStart, length); int newOffset = newStart; return (newOffset, newLength); } }; public record WrappingRange(Index Start, Index End) : Range(Start, End) { public static WrappingRange WrapFrom(Index start) => new(start, Index.Infinity); public static WrappingRange WrapUntil(Index end) => new(Index.NegativeInfinity, end); public override (int Offset, int Length) GetOffsetAndLength(int length) { var start = Start.GetOffset(length); var end = End.GetOffset(length); var len = start + Math.Abs(end - start); return (start, len); } public override bool Contains(int length, Index index) { if (Start <= End) { return base.Contains(length, index); } else { return (Start <= index && index < length) || (0 <= index && index < End); } } public override IEnumerable Indices(int length) { var (start, len) = GetOffsetAndLength(length); for (int i = start; i < len; i++) { yield return i % length; } } } }