import { ReadMethod } from "./types";

export class BinaryReader {
  protected index: number = 0;

  constructor(
    protected readonly buffer: DataView,
  ) { }

  readBoolean(): boolean {
    return this.readUInt8() != 0;
  }

  readSInt8(): number {
    const value = this.buffer.getInt8(this.index);

    this.index++;

    return value;
  }

  readUInt8(): number {
    const value = this.buffer.getUint8(this.index);

    this.index++;

    return value;
  }

  readSInt16(): number {
    const value = this.buffer.getInt16(this.index, true);

    this.index += 2;

    return value;
  }

  readUInt16(): number {
    const value = this.buffer.getUint16(this.index, true);

    this.index += 2;

    return value;
  }

  readSInt32(): number {
    const value = this.buffer.getInt32(this.index, true);

    this.index += 4;

    return value;
  }

  readUInt32(): number {
    const value = this.buffer.getUint32(this.index, true);

    this.index += 4;

    return value;
  }

  readSInt64(): bigint {
    const value = this.buffer.getBigInt64(this.index, true);

    this.index += 8;

    return value;
  }

  readUInt64(): bigint {
    const value = this.buffer.getBigUint64(this.index, true);

    this.index += 8;

    return value;
  }

  readFloat32(): number {
    const value = this.buffer.getFloat32(this.index, true);

    this.index += 4;

    return value;
  }

  readFloat64(): number {
    const value = this.buffer.getFloat64(this.index, true);

    this.index += 8;

    return value;
  }

  readString(length: number, encoding?: string): string {
    const decoder = new TextDecoder(encoding);

    return decoder.decode(this.buffer.buffer.slice(this.index, this.index += length));
  }

  async readFixedString(length: number, encoding?: string): Promise<string> {
    const decoder = new TextDecoder(encoding);

    const slice = new Uint8Array(this.buffer.buffer.slice(this.index, this.index += length));
    const foundIndex = slice.findIndex(byte => byte == 0);
    const end = foundIndex === -1 ? this.buffer.byteLength : foundIndex

    return decoder.decode(slice.slice(0, end));
  }

  async readArray<T>(size: number, readMethod: ReadMethod<T, BinaryReader>): Promise<T[]> {
    const array = new Array<T>(size);

    for (let i = 0; i < size; i++) {
      array[i] = await this.read(readMethod);
    }

    return array;
  }

  async read<T>(readMethod: ReadMethod<T, BinaryReader>): Promise<T> {
    if ("deserialize" in readMethod) {
      return await readMethod.deserialize(this);
    }

    return await readMethod(this);
  }
}
