/** @internal NOTE: Internal APIs. Subject to change. Use of these APIs in production applications is not supported. */ /** */

import { Observable } from '@babylonjs/core/Misc/observable';

type TypedArrayLike<K> = ArrayLike<K> & {
    buffer: ArrayBufferLike;
    BYTES_PER_ELEMENT: number;
    set(array: ArrayLike<K>, offset?: number): void;
};

/**
 * A growable typed array.
 */
export class LazyResizableTypedArray<K, T extends TypedArrayLike<K>> {
    private _uint8: Uint8Array;

    /**
     * Direct access to the Typed array. Reference may change if {@link setMinLength} is called.
     * Therefore avoid storing a refernce to this buffer in other places.
     */
    public buffer: T;

    /** Triggered after (and if) buffer has been resized when {@link setMinLength} was called. */
    public onResized = new Observable<LazyResizableTypedArray<K, T>>();

    /** Constructor
     * @param _factory Array creation factory. Allows us to create any kind of resizable typed array.
     */
    public constructor(private readonly _factory: (length: number) => T) {
        this.buffer = _factory(0);
        this._uint8 = new Uint8Array(this.buffer.buffer);
    }

    /**
     * Uint8Array view of {@link buffer}.
     * @returns Uint8Array wrapping {@link buffer}.
     */
    public get uint8(): Uint8Array {
        return this._uint8;
    }

    /** `true` if {@link buffer} is empty. */
    public get isEmpty(): boolean {
        return this.buffer.length === 0;
    }

    /**
     * Sets minimum length of array. Array is only resized if `minLength` is larger than
     * current array size.
     * @param minLength New minimal size of array.
     * @returns `true` if array was resized.
     */
    public setMinLength(minLength: number): boolean {
        const isResized = minLength > this.buffer.length;
        if (isResized) {
            const tmp = this.buffer;
            this.buffer = this._factory(minLength);
            // TODO This copy can perhaps be made faster by copying 8 bytes at a time.
            this.buffer.set(tmp);
            this._uint8 = new Uint8Array(this.buffer.buffer);
            this.onResized.notifyObservers(this);
        }

        return isResized;
    }

    /** Empties array. ({@link buffer} is set to zero sized array.) */
    public reset(): void {
        this.buffer = this._factory(0);
        this._uint8 = new Uint8Array(this.buffer.buffer);
    }
}
