// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; namespace SixLabors.PolygonClipper { /// /// Represents a status line for the sweep line algorithm, maintaining a sorted collection of sweep events. /// /// Performance Characteristics: /// - **Insertion**: O(n) in the worst case. The operation consists of: /// 1. A binary search (O(log n)) to determine the correct insertion point. /// 2. A shift operation to move subsequent elements in the list (O(k)), where k is the number of elements /// after the insertion index. In the worst case, this can approach O(n). /// - **Removal**: O(n) in the worst case. After finding the index of the element to remove, subsequent /// elements in the list need to be shifted (O(k)), where k is the number of elements after the removed index. /// - **Next/Previous Access**: O(1) after the index is known, as the list provides constant-time indexing. /// /// The implementation ensures efficient neighbor traversal (next/previous) at O(1), making it suitable for /// algorithms where neighboring elements are accessed frequently. The use of `BinarySearch` minimizes the cost /// of insertion/removal compared to naive search-based approaches. /// [DebuggerDisplay("Count = {Count}")] internal sealed class StatusLine { private const int DefaultCapacity = 16; private readonly List sortedEvents; private readonly SegmentComparer comparer = new(); public StatusLine() : this(DefaultCapacity) { } public StatusLine(int capacity) => this.sortedEvents = new List(capacity > 0 ? capacity : DefaultCapacity); /// /// Gets the number of events in the status line. /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.sortedEvents.Count; } /// /// Gets the minimum sweep event in the status line (first in sort order). /// public SweepEvent Min { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.sortedEvents[0]; } /// /// Gets the maximum sweep event in the status line (last in sort order). /// public SweepEvent Max { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.sortedEvents[^1]; } /// /// Gets the retained list capacity. /// public int RetainedCapacity => this.sortedEvents.Capacity; /// /// Gets the event at the specified index. /// /// The index of the event. /// The sweep event at the given index. public SweepEvent this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.sortedEvents[index]; } /// /// Clears active events and ensures the desired capacity. /// /// Desired minimum capacity. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset(int capacity) { this.sortedEvents.Clear(); if (capacity > this.sortedEvents.Capacity) { this.sortedEvents.EnsureCapacity(capacity); } } /// /// Adds a sweep event into the status line, maintaining sorted order. /// /// The sweep event to insert. /// The index where the event was inserted. public int Add(SweepEvent e) { int index = this.sortedEvents.BinarySearch(e, this.comparer); if (index < 0) { index = ~index; // Get the correct insertion point } this.sortedEvents.Insert(index, e); e.PosSL = index; return index; } /// /// Removes a sweep event from the status line. /// /// The index of the event to remove. /// /// Thrown if is less than 0 or greater than or equal to the number of events. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveAt(int index) => this.sortedEvents.RemoveAt(index); /// /// Finds the current index of a sweep event in the status line. /// /// The event to locate. /// The index of the event, or -1 if it is not present. public int IndexOf(SweepEvent e) { List events = this.sortedEvents; int count = events.Count; int hint = e.PosSL; if ((uint)hint < (uint)count && ReferenceEquals(events[hint], e)) { return hint; } int index = events.BinarySearch(e, this.comparer); if (index >= 0) { if (ReferenceEquals(events[index], e)) { e.PosSL = index; return index; } // BinarySearch can return any comparer-equal slot. Scan local ties by reference. for (int i = index - 1; i >= 0 && this.comparer.Compare(events[i], e) == 0; i--) { if (ReferenceEquals(events[i], e)) { e.PosSL = i; return i; } } for (int i = index + 1; i < count && this.comparer.Compare(events[i], e) == 0; i++) { if (ReferenceEquals(events[i], e)) { e.PosSL = i; return i; } } } // Fail-safe reference lookup for correctness if comparer order is temporarily unstable. for (int i = 0; i < count; i++) { if (ReferenceEquals(events[i], e)) { e.PosSL = i; return i; } } return -1; } /// /// Gets the next sweep event relative to the given index. /// /// The reference index. /// The next sweep event, or null if none exists. [MethodImpl(MethodImplOptions.AggressiveInlining)] public SweepEvent? Next(int index) { if (index >= 0 && index < this.sortedEvents.Count - 1) { return this.sortedEvents[index + 1]; } return null; } /// /// Gets the previous sweep event relative to the given index. /// /// The reference index. /// The previous sweep event, or null if none exists. [MethodImpl(MethodImplOptions.AggressiveInlining)] public SweepEvent? Prev(int index) { if (index > 0 && index < this.sortedEvents.Count) { return this.sortedEvents[index - 1]; } return null; } } }