// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {assertTrue} from './assert';

/**
 * A dynamically resizable array buffer implementation for efficient
 * storage and manipulation of binary data. It starts with a specified
 * initial size and grows as needed to accommodate appended data.
 * Efficiently grows the buffer using an exponential strategy up to 32MB,
 * and then linearly in 32MB increments to minimize reallocation overhead.
 * Provides methods to append data, shrink the size, clear the buffer,
 * and retrieve the stored data as a `Uint8Array`.
 */
export class ResizableArrayBuffer {
  private buf: Uint8Array;
  private _size = 0;

  constructor(private readonly initialSize = 128) {
    this.buf = new Uint8Array(initialSize);
  }

  append(data: ArrayLike<number>) {
    const capacityNeeded = this._size + data.length;
    if (this.capacity < capacityNeeded) {
      this.grow(capacityNeeded);
    }
    this.buf.set(data, this._size);
    this._size = capacityNeeded;
  }

  shrink(newSize: number) {
    assertTrue(newSize <= this._size);
    this._size = newSize;
  }

  clear() {
    this.buf = new Uint8Array(this.initialSize);
    this._size = 0;
  }

  get(): Uint8Array {
    return this.buf.subarray(0, this._size);
  }

  get size(): number {
    return this._size;
  }

  get capacity(): number {
    return this.buf.length;
  }

  private grow(capacityNeeded: number) {
    let newSize = this.buf.length;
    const MB32 = 32 * 1024 * 1024;
    do {
      newSize = newSize < MB32 ? newSize * 2 : newSize + MB32;
    } while (newSize < capacityNeeded);
    const newBuf = new Uint8Array(newSize);
    assertTrue(newBuf.length >= capacityNeeded);
    newBuf.set(this.buf);
    this.buf = newBuf;
  }
}
