// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

/* eslint-disable camelcase */
// Forked from PicoGL: https://github.com/tsherif/picogl.js/blob/master/examples/utils/utils.js
// Copyright (c) 2017 Tarek Sherif, The MIT License (MIT)

import type {TextureFormat, TextureLevel} from '@loaders.gl/schema';
import {extractMipmapImages} from '../utils/extract-mipmap-images';

const PVR_CONSTANTS: Record<string, number> = {
  MAGIC_NUMBER: 0x03525650,
  MAGIC_NUMBER_EXTRA: 0x50565203,
  HEADER_LENGTH: 13,
  HEADER_SIZE: 52,
  MAGIC_NUMBER_INDEX: 0,
  PIXEL_FORMAT_INDEX: 2,
  COLOUR_SPACE_INDEX: 4,
  HEIGHT_INDEX: 6,
  WIDTH_INDEX: 7,
  MIPMAPCOUNT_INDEX: 11,
  METADATA_SIZE_INDEX: 12
};

const PVR_TEXTURE_FORMATS: Record<number, TextureFormat[]> = {
  0: ['pvrtc-rgb2unorm-webgl'],
  1: ['pvrtc-rgba2unorm-webgl'],
  2: ['pvrtc-rgb4unorm-webgl'],
  3: ['pvrtc-rgba4unorm-webgl'],
  6: ['etc1-rgb-unorm-webgl'],
  7: ['bc1-rgb-unorm-webgl'],
  9: ['bc2-rgba-unorm'],
  11: ['bc3-rgba-unorm'],
  22: ['etc2-rgb8unorm'],
  23: ['etc2-rgba8unorm'],
  24: ['etc2-rgb8a1unorm'],
  25: ['eac-r11unorm'],
  26: ['eac-rg11unorm'],
  27: ['astc-4x4-unorm', 'astc-4x4-unorm-srgb'],
  28: ['astc-5x4-unorm', 'astc-5x4-unorm-srgb'],
  29: ['astc-5x5-unorm', 'astc-5x5-unorm-srgb'],
  30: ['astc-6x5-unorm', 'astc-6x5-unorm-srgb'],
  31: ['astc-6x6-unorm', 'astc-6x6-unorm-srgb'],
  32: ['astc-8x5-unorm', 'astc-8x5-unorm-srgb'],
  33: ['astc-8x6-unorm', 'astc-8x6-unorm-srgb'],
  34: ['astc-8x8-unorm', 'astc-8x8-unorm-srgb'],
  35: ['astc-10x5-unorm', 'astc-10x5-unorm-srgb'],
  36: ['astc-10x6-unorm', 'astc-10x6-unorm-srgb'],
  37: ['astc-10x8-unorm', 'astc-10x8-unorm-srgb'],
  38: ['astc-10x10-unorm', 'astc-10x10-unorm-srgb'],
  39: ['astc-12x10-unorm', 'astc-12x10-unorm-srgb'],
  40: ['astc-12x12-unorm', 'astc-12x12-unorm-srgb']
};

const PVR_SIZE_FUNCTIONS: Record<number, (width: number, height: number) => number> = {
  0: pvrtc2bppSize,
  1: pvrtc2bppSize,
  2: pvrtc4bppSize,
  3: pvrtc4bppSize,
  6: dxtEtcSmallSize,
  7: dxtEtcSmallSize,
  9: dxtEtcAstcBigSize,
  11: dxtEtcAstcBigSize,
  22: dxtEtcSmallSize,
  23: dxtEtcAstcBigSize,
  24: dxtEtcSmallSize,
  25: dxtEtcSmallSize,
  26: dxtEtcAstcBigSize,
  27: dxtEtcAstcBigSize,
  28: atc5x4Size,
  29: atc5x5Size,
  30: atc6x5Size,
  31: atc6x6Size,
  32: atc8x5Size,
  33: atc8x6Size,
  34: atc8x8Size,
  35: atc10x5Size,
  36: atc10x6Size,
  37: atc10x8Size,
  38: atc10x10Size,
  39: atc12x10Size,
  40: atc12x12Size
};

/**
 * Check if data is in "PVR" format by its magic number
 * @param data - binary data of compressed texture
 * @returns true - data in "PVR" format, else - false
 */
export function isPVR(data: ArrayBuffer): boolean {
  const header = new Uint32Array(data, 0, PVR_CONSTANTS.HEADER_LENGTH);
  const version = header[PVR_CONSTANTS.MAGIC_NUMBER_INDEX];

  return version === PVR_CONSTANTS.MAGIC_NUMBER || version === PVR_CONSTANTS.MAGIC_NUMBER_EXTRA;
}

/**
 * Parse texture data as "PVR" format
 * @param data - binary data of compressed texture
 * @returns Array of the texture levels
 * @see http://cdn.imgtec.com/sdk-documentation/PVR+File+Format.Specification.pdf
 */
export function parsePVR(data: ArrayBuffer): TextureLevel[] {
  const header = new Uint32Array(data, 0, PVR_CONSTANTS.HEADER_LENGTH);

  const pvrFormat = header[PVR_CONSTANTS.PIXEL_FORMAT_INDEX];
  const colourSpace = header[PVR_CONSTANTS.COLOUR_SPACE_INDEX];
  const textureFormats = PVR_TEXTURE_FORMATS[pvrFormat] || [];
  const textureFormat =
    textureFormats.length > 1 && colourSpace ? textureFormats[1] : textureFormats[0];

  const sizeFunction = PVR_SIZE_FUNCTIONS[pvrFormat];

  const mipMapLevels = header[PVR_CONSTANTS.MIPMAPCOUNT_INDEX];

  const width = header[PVR_CONSTANTS.WIDTH_INDEX];
  const height = header[PVR_CONSTANTS.HEIGHT_INDEX];

  const dataOffset = PVR_CONSTANTS.HEADER_SIZE + header[PVR_CONSTANTS.METADATA_SIZE_INDEX];

  const image = new Uint8Array(data, dataOffset);

  return extractMipmapImages(image, {
    mipMapLevels,
    width,
    height,
    sizeFunction,
    textureFormat
  });
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
function pvrtc2bppSize(width: number, height: number): number {
  width = Math.max(width, 16);
  height = Math.max(height, 8);

  return (width * height) / 4;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
function pvrtc4bppSize(width: number, height: number): number {
  width = Math.max(width, 8);
  height = Math.max(height, 8);

  return (width * height) / 2;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/
// Size for:
// COMPRESSED_RGB_S3TC_DXT1_EXT
// COMPRESSED_R11_EAC
// COMPRESSED_SIGNED_R11_EAC
// COMPRESSED_RGB8_ETC2
// COMPRESSED_SRGB8_ETC2
// COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2
// COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2
function dxtEtcSmallSize(width: number, height: number): number {
  return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
// Size for:
// COMPRESSED_RGBA_S3TC_DXT3_EXT
// COMPRESSED_RGBA_S3TC_DXT5_EXT
// COMPRESSED_RG11_EAC
// COMPRESSED_SIGNED_RG11_EAC
// COMPRESSED_RGBA8_ETC2_EAC
// COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
// COMPRESSED_RGBA_ASTC_4x4_KHR
function dxtEtcAstcBigSize(width: number, height: number): number {
  return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc5x4Size(width: number, height: number): number {
  return Math.floor((width + 4) / 5) * Math.floor((height + 3) / 4) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc5x5Size(width: number, height: number): number {
  return Math.floor((width + 4) / 5) * Math.floor((height + 4) / 5) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc6x5Size(width: number, height: number): number {
  return Math.floor((width + 5) / 6) * Math.floor((height + 4) / 5) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc6x6Size(width: number, height: number): number {
  return Math.floor((width + 5) / 6) * Math.floor((height + 5) / 6) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc8x5Size(width: number, height: number): number {
  return Math.floor((width + 7) / 8) * Math.floor((height + 4) / 5) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc8x6Size(width: number, height: number): number {
  return Math.floor((width + 7) / 8) * Math.floor((height + 5) / 6) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc8x8Size(width: number, height: number): number {
  return Math.floor((width + 7) / 8) * Math.floor((height + 7) / 8) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc10x5Size(width: number, height: number): number {
  return Math.floor((width + 9) / 10) * Math.floor((height + 4) / 5) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc10x6Size(width: number, height: number): number {
  return Math.floor((width + 9) / 10) * Math.floor((height + 5) / 6) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc10x8Size(width: number, height: number): number {
  return Math.floor((width + 9) / 10) * Math.floor((height + 7) / 8) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc10x10Size(width: number, height: number): number {
  return Math.floor((width + 9) / 10) * Math.floor((height + 9) / 10) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc12x10Size(width: number, height: number): number {
  return Math.floor((width + 11) / 12) * Math.floor((height + 9) / 10) * 16;
}

// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc12x12Size(width: number, height: number): number {
  return Math.floor((width + 11) / 12) * Math.floor((height + 11) / 12) * 16;
}
