export interface StringBuilder {
  byteLength: number;
  appendChar: (char: number) => void;
  appendBuf: (buf: Uint8Array, start?: number, end?: number) => void;
  reset: () => void;
  toString: () => string;
}

export class NonBufferedString implements StringBuilder {
  private decoder = new TextDecoder("utf-8");
  private string = "";
  public byteLength = 0;

  public appendChar(char: number): void {
    this.string += String.fromCharCode(char);
    this.byteLength += 1;
  }

  public appendBuf(buf: Uint8Array, start = 0, end: number = buf.length): void {
    this.string += this.decoder.decode(buf.subarray(start, end));
    this.byteLength += end - start;
  }

  public reset(): void {
    this.string = "";
    this.byteLength = 0;
  }

  public toString(): string {
    return this.string;
  }
}

export class BufferedString implements StringBuilder {
  private decoder = new TextDecoder("utf-8");
  private buffer: Uint8Array;
  private bufferOffset = 0;
  private string = "";
  public byteLength = 0;

  public constructor(bufferSize: number) {
    this.buffer = new Uint8Array(bufferSize);
  }

  public appendChar(char: number): void {
    if (this.bufferOffset >= this.buffer.length) this.flushStringBuffer();
    this.buffer[this.bufferOffset++] = char;
    this.byteLength += 1;
  }

  public appendBuf(buf: Uint8Array, start = 0, end: number = buf.length): void {
    const size = end - start;
    if (this.bufferOffset + size > this.buffer.length) this.flushStringBuffer();
    this.buffer.set(buf.subarray(start, end), this.bufferOffset);
    this.bufferOffset += size;
    this.byteLength += size;
  }

  private flushStringBuffer(): void {
    this.string += this.decoder.decode(
      this.buffer.subarray(0, this.bufferOffset)
    );
    this.bufferOffset = 0;
  }

  public reset(): void {
    this.string = "";
    this.bufferOffset = 0;
    this.byteLength = 0;
  }
  public toString(): string {
    this.flushStringBuffer();
    return this.string;
  }
}
