/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
import {SecurityException} from '../exception/security_exception';

import {PbKeysetKey, PbOutputPrefixType} from './proto';

/**
 * Constants and methods that deal with the format of the outputs handled by
 * Tink.
 *
 * @static
 * @final
 */
export class CryptoFormat {
  /**
   * Generates the prefix for the outputs handled by the given 'key'.
   * Throws an exception if the prefix type of 'key' is invalid.
   *
   *
   */
  static getOutputPrefix(key: PbKeysetKey): Uint8Array {
    switch (key.getOutputPrefixType()) {
      case PbOutputPrefixType.LEGACY:

      // fall through
      case PbOutputPrefixType.CRUNCHY:
        return CryptoFormat.makeOutputPrefix(
            key.getKeyId(), CryptoFormat.LEGACY_START_BYTE);
      case PbOutputPrefixType.TINK:
        return CryptoFormat.makeOutputPrefix(
            key.getKeyId(), CryptoFormat.TINK_START_BYTE);
      case PbOutputPrefixType.RAW:
        return CryptoFormat.RAW_PREFIX;
      default:
        throw new SecurityException('Unsupported key prefix type.');
    }
  }

  /**
   * Makes output prefix which consits of 4 bytes of key id in Big Endian
   * representation followed by 1 byte of key type identifier.
   *
   * @static
   *
   */
  private static makeOutputPrefix(keyId: number, keyTypeIdentifier: number):
      Uint8Array {
    let res = [keyTypeIdentifier];
    res = res.concat(CryptoFormat.numberAsBigEndian(keyId));
    return new Uint8Array(res);
  }

  /**
   * Returns the given number as Uint8Array in Big Endian format.
   *
   * Given number has to be a non-negative integer smaller than 2^32.
   *
   * @static
   *
   */
  private static numberAsBigEndian(n: number): number[] {
    if (!Number.isInteger(n) || n < 0 || n >= 2 ** 32) {
      throw new InvalidArgumentsException(
          'Number has to be unsigned 32-bit integer.');
    }
    const numberOfBytes = 4;
    const res = new Array(numberOfBytes);
    for (let i = 0; i < numberOfBytes; i++) {
      res[i] = 255 & n >> 8 * (numberOfBytes - i - 1);
    }
    return res;
  }

  /**
   * Prefix size of Tink and Legacy key types.
   */
  static readonly NON_RAW_PREFIX_SIZE = 5;

  /**
   * Prefix size of Legacy key types.
   */
  static readonly LEGACY_PREFIX_SIZE = CryptoFormat.NON_RAW_PREFIX_SIZE;

  /**
   * Legacy starts with 0 and is followed by 4-byte key id.
   */
  static readonly LEGACY_START_BYTE = 0;

  /**
   * Prefix size of Tink key types.
   */
  static readonly TINK_PREFIX_SIZE = CryptoFormat.NON_RAW_PREFIX_SIZE;

  /**
   * Tink starts with 1 and is followed by 4-byte key id.
   */
  static readonly TINK_START_BYTE = 1;

  /**
   * Raw prefix should have length 0.
   */
  static readonly RAW_PREFIX_SIZE = 0;

  /**
   * Raw prefix is empty Uint8Array.
   */
  static readonly RAW_PREFIX = new Uint8Array(0);
}
