namespace Qrakhen.Qamp.Memory; // add to qrakhen.memory package for clout public abstract class TailBuffer { private T[] _data; public T[] Data { get => _data; } public int Capacity => _data.Length; public int Tail { get; private set; } public TailBuffer(int size = 0x80) { _data = new T[size]; } public TailBuffer(T[] data) { _data = new T[data.Length * 2]; Array.Copy(data, 0, _data, 0, data.Length); SetTail(data.Length); } public void Insert(int cursor, T[] data) { cursor = SeekTail(cursor); Prepare(cursor, data.Length); int length = data.Length; if (cursor == Tail) { // Just append here. Array.Copy(data, 0, _data, cursor, length); SetTail(cursor + length); } else { // Shift everything to the right of insertion Array.Copy(_data, cursor, _data, cursor + length, Tail - cursor); Array.Copy(data, 0, _data, cursor, length); SetTail(Tail + length); } } public void Delete(Range range) => Delete(range.From, range.To - range.From); public void Delete(int cursor, int count) { if (cursor + count >= Tail) SetTail(cursor); // Directly terminate at cursor, nothing is behind tail. else { // Move everything from end of deletion until tail to start of deletion. Array.Copy(_data, cursor + count, _data, cursor, Tail - (cursor + count)); SetTail(Tail - count); } } // never write beyond tail (there might be invalid data) private int SeekTail(int cursor) => cursor > Tail ? Tail : cursor; private void Prepare(int index, int count) { ArgumentOutOfRangeException.ThrowIfNegative(index); if (index + count <= _data.Length) return; while (index + count > _data.Length) { T[] grow = new T[_data.Length * 2]; Array.Copy(_data, grow, _data.Length); _data = grow; } } /// /// Sets the tail, or, if omitted, seeks the tail by finding the first null terminator. /// /// protected virtual void SetTail(int tail = -1) { Tail = tail; } }