import Hash from "../crypto/hash";
import Base58 from "./base58";
import type { BufferParams } from "../common/interfaces";
import BufferUtils from "../utils/buffer.utils";

export default class Base58Check {

  private buf?: Uint8Array;

  constructor(obj?: Uint8Array | string | BufferParams) {
    if (BufferUtils.isBuffer(obj)) {
      let buf = obj;
      this.fromBuffer(buf);
    } else if (typeof obj === 'string') {
      let str = obj;
      this.fromString(str);
    } else if (obj) {
      this.set(obj);
    }
  }

  public static validChecksum(data: string | Uint8Array, checksum?: string | Uint8Array): boolean {
    if (typeof data === 'string') {
      data = Uint8Array.from(Base58.decode(data));
    }
    if (typeof checksum === 'string') {
      checksum =  Uint8Array.from(Base58.decode(checksum));
    }
    if (!checksum) {
      checksum = data.subarray(-4);
      data = data.subarray(0, -4);
    }
    return BufferUtils.bufferToHex(Base58Check.checksum(data)) === BufferUtils.bufferToHex(checksum);
  };
  
  public static decode(s: string): Uint8Array {
    if (typeof s !== 'string') {
      throw new Error('Input must be a string');
    }
  
    let buf = Uint8Array.from(Base58.decode(s));
  
    if (buf.length < 4) {
      throw new Error("Input string too short");
    }

    let data = buf.subarray(0, -4);
    let csum = buf.subarray(-4);
  
    let hash = Hash.sha256sha256(data);
    let hash4 = hash.subarray(0, 4);
  
    if (BufferUtils.bufferToHex(csum) !== BufferUtils.bufferToHex(hash4)) {
      throw new Error("Checksum mismatch");
    }
  
    return data;
  }
  
  public static checksum(buffer: Uint8Array): Uint8Array {
    return Hash.sha256sha256(buffer).subarray(0, 4);
  }
  
  public static encode(buf: Uint8Array): string {
    if (!BufferUtils.isBuffer(buf))
      throw new Error('Input must be a buffer');
    const checkedBuf = new Uint8Array(buf.length + 4);
    const hash = Base58Check.checksum(buf);
    checkedBuf.set(buf, 0);
    checkedBuf.set(hash, buf.length);
    return Base58.encode(checkedBuf);
  }

  public toBuffer(): Uint8Array | undefined {
    return this.buf;
  }
  
  public toString(): string {
    return this.buf ? Base58Check.encode(this.buf) : "";
  }

  private fromBuffer(buf: Uint8Array): this {
    this.buf = buf;
    return this;
  }
  
  private fromString(str: string): this {
    let buf = Base58Check.decode(str);
    this.buf = buf;
    return this;
  }

  private set(obj: BufferParams): this {
    this.buf = obj.buf || this.buf || undefined;
    return this;
  }
}