/*
 * Copyright 2021 Juan Cruz Viotti
 *
 * 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.
 */

export default class ResizableBuffer {
  private buffer: Buffer;
  private written: number;

  public constructor (buffer: Buffer) {
    this.buffer = buffer
    this.written = 0
  }

  public getOriginalSize (): number {
    return this.buffer.length
  }

  public getSize (): number {
    return this.written
  }

  public getBuffer (): Buffer {
    return this.buffer.slice(0, this.written)
  }

  public writeUInt8 (value: number, offset: number): number {
    this.grow(1)
    const cursor: number = this.buffer.writeUInt8(value, offset)
    this.written = Math.max(this.written, cursor)
    return cursor
  }

  public writeUInt16LE (value: number, offset: number): number {
    this.grow(2)
    const cursor: number = this.buffer.writeUInt16LE(value, offset)
    this.written = Math.max(this.written, cursor)
    return cursor
  }

  public writeUIntLE (value: number, offset: number, byteLength: number): number {
    this.grow(byteLength)
    const cursor: number = this.buffer.writeUIntLE(value, offset, byteLength)
    this.written = Math.max(this.written, cursor)
    return cursor
  }

  public write (value: string, offset: number, length: number, encoding: BufferEncoding): number {
    this.grow(length)
    const bytesWritten: number = this.buffer.write(value, offset, length, encoding)
    this.written = Math.max(this.written, offset + bytesWritten)
    return bytesWritten
  }

  public writeDoubleLE (value: number, offset: number): number {
    this.grow(8)
    const cursor: number = this.buffer.writeDoubleLE(value, offset)
    this.written = Math.max(this.written, cursor)
    return cursor
  }

  public readUInt8 (offset: number): number {
    return this.buffer.readUInt8(offset)
  }

  public readUInt16LE (offset: number): number {
    return this.buffer.readUInt16LE(offset)
  }

  public readUIntLE (offset: number, byteLength: number): number {
    return this.buffer.readUIntLE(offset, byteLength)
  }

  public toString (encoding: BufferEncoding, start: number, end: number): string {
    return this.buffer.toString(encoding, start, end)
  }

  public readDoubleLE (offset: number): number {
    return this.buffer.readDoubleLE(offset)
  }

  public slice (start: number, end: number): Buffer {
    return this.buffer.slice(start, end)
  }

  public writeBuffer (offset: number, buffer: Buffer): number {
    this.grow(buffer.length)
    const bytesWritten: number = buffer.copy(this.buffer, offset)
    this.written = Math.max(this.written + buffer.length, offset + buffer.length)
    return bytesWritten
  }

  private grow (bytes: number): void {
    if (this.written + bytes > this.buffer.length) {
      this.buffer = Buffer.concat([
        this.buffer,

        // Allocate the required amount of bytes but also
        // add some extra room (twice the buffer size)
        // to minimize future allocations.
        Buffer.allocUnsafe((this.buffer.length * 2) + bytes)
      ])
    }
  }
}
