import { createCipheriv } from 'crypto'
import { toU8a } from '../u8a'

const BLOCK_LENGTH = 16
export const PRG_KEY_LENGTH = BLOCK_LENGTH
export const PRG_IV_LENGTH = 12
export const PRG_COUNTER_LENGTH = 4

const PRG_ALGORITHM = 'aes-128-ctr'

export type PRGParameters = {
  key: Uint8Array
  iv: Uint8Array
}

export class PRG {
  private readonly key: Uint8Array
  private readonly iv: Uint8Array

  private constructor(key: Uint8Array, iv: Uint8Array) {
    this.key = key
    this.iv = iv
  }

  static createPRG(params: PRGParameters): PRG {
    if (params.key.length != PRG_KEY_LENGTH || params.iv.length != PRG_IV_LENGTH) {
      throw Error(
        `Invalid input parameters. Expected a key of ${PRG_KEY_LENGTH} bytes and an initialization vector of ${PRG_IV_LENGTH} bytes.`
      )
    }

    return new PRG(params.key, params.iv)
  }

  digest(start: number, end: number): Uint8Array {
    if (start >= end) {
      throw Error(`Invalid range parameters. 'start' must be strictly smaller than 'end'.`)
    }

    const firstBlock = Math.floor(start / BLOCK_LENGTH)
    const startOffset = start % BLOCK_LENGTH

    const lastBlock = Math.ceil(end / BLOCK_LENGTH)
    const lastBlockSize = end % BLOCK_LENGTH

    const amountOfBlocks = lastBlock - firstBlock

    const iv = Uint8Array.from([...this.iv, ...toU8a(firstBlock, PRG_COUNTER_LENGTH)])

    return createCipheriv(PRG_ALGORITHM, this.key, iv)
      .update(new Uint8Array(amountOfBlocks * BLOCK_LENGTH))
      .subarray(startOffset, amountOfBlocks * BLOCK_LENGTH - (lastBlockSize > 0 ? BLOCK_LENGTH - lastBlockSize : 0))
  }
}