/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import JSZip from 'jszip';
import {ArrayUtils} from './array_utils';
import {FunctionUtils, OnProgressUpdateType} from './function_utils';

export type OnFile = (file: File, parentArchive: File | undefined) => void;

export class FileUtils {
  //allow: letters/numbers/underscores with delimiters . - # (except at start and end)
  static readonly DOWNLOAD_FILENAME_REGEX = /^\w+?((|#|-|\.)\w+)+$/;
  static readonly ILLEGAL_FILENAME_CHARACTERS_REGEX = /[^A-Za-z0-9-#._]/g;

  static getFileExtension(filename: string): string | undefined {
    const lastDot = filename.lastIndexOf('.');
    if (lastDot === -1) {
      return undefined;
    }
    return filename.slice(lastDot + 1);
  }

  static removeDirFromFileName(name: string): string {
    if (name.includes('/')) {
      const startIndex = name.lastIndexOf('/') + 1;
      return name.slice(startIndex);
    } else {
      return name;
    }
  }

  static removeExtensionFromFilename(name: string): string {
    if (name.includes('.')) {
      const lastIndex = name.lastIndexOf('.');
      return name.slice(0, lastIndex);
    } else {
      return name;
    }
  }

  static async createZipArchive(files: File[]): Promise<Blob> {
    const zip = new JSZip();
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      const blob = await file.arrayBuffer();
      zip.file(file.name, blob);
    }
    return await zip.generateAsync({type: 'blob'});
  }

  static async unzipFile(
    file: Blob,
    onProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING,
  ): Promise<File[]> {
    const unzippedFiles: File[] = [];
    const zip = new JSZip();
    const content = await zip.loadAsync(file);

    const filenames = Object.keys(content.files);
    for (const [index, filename] of filenames.entries()) {
      const file = content.files[filename];
      if (file.dir) {
        // Ignore directories
        continue;
      } else {
        const fileBlob = await file.async('blob');
        const unzippedFile = new File([fileBlob], filename);
        unzippedFiles.push(unzippedFile);
      }

      onProgressUpdate((100 * (index + 1)) / filenames.length);
    }

    return unzippedFiles;
  }

  static async decompressGZipFile(file: File): Promise<File> {
    const decompressionStream = new (window as any).DecompressionStream('gzip');
    const decompressedStream = file.stream().pipeThrough(decompressionStream);
    const fileBlob = await new Response(decompressedStream).blob();
    return new File(
      [fileBlob],
      FileUtils.removeExtensionFromFilename(file.name),
    );
  }

  static async isZipFile(file: File): Promise<boolean> {
    return FileUtils.isMatchForMagicNumber(file, FileUtils.PK_ZIP_MAGIC_NUMBER);
  }

  static async isGZipFile(file: File): Promise<boolean> {
    return FileUtils.isMatchForMagicNumber(file, FileUtils.GZIP_MAGIC_NUMBER);
  }

  private static async isMatchForMagicNumber(
    file: File,
    magicNumber: number[],
  ): Promise<boolean> {
    const bufferStart = new Uint8Array((await file.arrayBuffer()).slice(0, 2));
    return ArrayUtils.equal(bufferStart, magicNumber);
  }

  private static readonly GZIP_MAGIC_NUMBER = [0x1f, 0x8b];
  private static readonly PK_ZIP_MAGIC_NUMBER = [0x50, 0x4b];
}