// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
export const BASE64_CODES = new Uint8Array(123);
for (let index = 0; index < BASE64_CHARS.length; ++index) {
  BASE64_CODES[BASE64_CHARS.charCodeAt(index)] = index;
}

/**
 * Decodes Base64-encoded data from a string without performing any kind of checking.
 */
export function decode(input: string): Uint8Array<ArrayBuffer> {
  let bytesLength = ((input.length * 3) / 4) >>> 0;
  if (input.charCodeAt(input.length - 2) === 0x3d /* '=' */) {
    bytesLength -= 2;
  } else if (input.charCodeAt(input.length - 1) === 0x3d /* '=' */) {
    bytesLength -= 1;
  }

  const bytes = new Uint8Array(bytesLength);
  for (let index = 0, offset = 0; index < input.length; index += 4) {
    const a = BASE64_CODES[input.charCodeAt(index + 0)];
    const b = BASE64_CODES[input.charCodeAt(index + 1)];
    const c = BASE64_CODES[input.charCodeAt(index + 2)];
    const d = BASE64_CODES[input.charCodeAt(index + 3)];
    bytes[offset++] = (a << 2) | (b >> 4);
    bytes[offset++] = ((b & 0x0f) << 4) | (c >> 2);
    bytes[offset++] = ((c & 0x03) << 6) | (d & 0x3f);
  }
  return bytes;
}

/**
 * Note: if input can be very large (larger than the max string size), callers should
 * expect this to throw an error.
 */
export function encode(input: BlobPart): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => reject(new Error('failed to convert to base64: internal error'));
    reader.onload = () => {
      // The input was too large to encode as a string. The caller should anticipate
      // this and use a workaround. See TimelinePanel.ts innerSaveToFile for an example.
      // For more information, see crbug.com/436482118.
      if (reader.result === '') {
        reject(new Error('failed to convert to base64: input too large to encode as base64 string'));
        return;
      }

      // This string can be very large, so take care to not double memory. `split`
      // was used here before, which always results in new strings in V8. By using
      // slice instead, we leverage the sliced string optimization in V8 and avoid
      // doubling the memory requirement (even if temporarily: that is a potential
      // source of OOM crashes given large enough input, such as is common with
      // Performance traces).
      const blobAsUrl = reader.result as string;
      const index = blobAsUrl.indexOf(',');
      const base64 = blobAsUrl.slice(index + 1);
      resolve(base64);
    };

    reader.readAsDataURL(new Blob([input]));
  });
}
