// This file is part of meshoptimizer library and is distributed under the terms of MIT License.
// Copyright (C) 2016-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)

type API = {
  ready: Promise<void>
  supported: boolean
  decodeVertexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, filter?: string) => void
  decodeIndexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void
  decodeIndexSequence: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void
  decodeGltfBuffer: (
    target: Uint8Array,
    count: number,
    size: number,
    source: Uint8Array,
    mode: string,
    filter?: string,
  ) => void
}

let generated: API

const MeshoptDecoder = () => {
  if (generated) return generated

  // Built with clang version 11.0.0 (https://github.com/llvm/llvm-project.git 0160ad802e899c2922bc9b29564080c22eb0908c)
  // Built from meshoptimizer 0.14
  const wasm_base =
    '[encoded data, 7357 characters]'
  const wasm_simd =
    '[encoded data, 10252 characters]'

  // Uses bulk-memory and simd extensions
  const detector = new Uint8Array([
    0,
    97,
    115,
    109,
    1,
    0,
    0,
    0,
    1,
    4,
    1,
    96,
    0,
    0,
    3,
    3,
    2,
    0,
    0,
    5,
    3,
    1,
    0,
    1,
    12,
    1,
    0,
    10,
    22,
    2,
    12,
    0,
    65,
    0,
    65,
    0,
    65,
    0,
    252,
    10,
    0,
    0,
    11,
    7,
    0,
    65,
    0,
    253,
    15,
    26,
    11,
  ])

  // Used to unpack wasm
  const wasmpack = new Uint8Array([
    32,
    0,
    65,
    253,
    3,
    1,
    2,
    34,
    4,
    106,
    6,
    5,
    11,
    8,
    7,
    20,
    13,
    33,
    12,
    16,
    128,
    9,
    116,
    64,
    19,
    113,
    127,
    15,
    10,
    21,
    22,
    14,
    255,
    66,
    24,
    54,
    136,
    107,
    18,
    23,
    192,
    26,
    114,
    118,
    132,
    17,
    77,
    101,
    130,
    144,
    27,
    87,
    131,
    44,
    45,
    74,
    156,
    154,
    70,
    167,
  ])

  if (typeof WebAssembly !== 'object') {
    // This module requires WebAssembly to function
    return {
      supported: false,
    }
  }

  let wasm = wasm_base

  if (WebAssembly.validate(detector)) {
    wasm = wasm_simd
  }

  let instance: any // WebAssembly.Instance

  const promise = WebAssembly.instantiate(unpack(wasm), {}).then((result) => {
    instance = result.instance
    instance.exports.__wasm_call_ctors()
  })

  function unpack(data: string) {
    const result = new Uint8Array(data.length)
    for (let i = 0; i < data.length; ++i) {
      const ch = data.charCodeAt(i)
      result[i] = ch > 96 ? ch - 71 : ch > 64 ? ch - 65 : ch > 47 ? ch + 4 : ch > 46 ? 63 : 62
    }
    let write = 0
    for (let i = 0; i < data.length; ++i) {
      result[write++] = result[i] < 60 ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i]
    }
    return result.buffer.slice(0, write)
  }

  function decode(
    fun: Function,
    target: Uint8Array,
    count: number,
    size: number,
    source: Uint8Array,
    filter?: Function,
  ) {
    const sbrk = instance.exports.sbrk
    const count4 = (count + 3) & ~3 // pad for SIMD filter
    const tp = sbrk(count4 * size)
    const sp = sbrk(source.length)
    const heap = new Uint8Array(instance.exports.memory.buffer)
    heap.set(source, sp)
    const res = fun(tp, count, size, sp, source.length)
    if (res === 0 && filter) {
      filter(tp, count4, size)
    }
    target.set(heap.subarray(tp, tp + count * size))
    sbrk(tp - sbrk(0))
    if (res !== 0) {
      throw new Error(`Malformed buffer data: ${res}`)
    }
  }

  const filters = {
    // legacy index-based enums for glTF
    0: '',
    1: 'meshopt_decodeFilterOct',
    2: 'meshopt_decodeFilterQuat',
    3: 'meshopt_decodeFilterExp',
    // string-based enums for glTF
    NONE: '',
    OCTAHEDRAL: 'meshopt_decodeFilterOct',
    QUATERNION: 'meshopt_decodeFilterQuat',
    EXPONENTIAL: 'meshopt_decodeFilterExp',
  }

  const decoders = {
    // legacy index-based enums for glTF
    0: 'meshopt_decodeVertexBuffer',
    1: 'meshopt_decodeIndexBuffer',
    2: 'meshopt_decodeIndexSequence',
    // string-based enums for glTF
    ATTRIBUTES: 'meshopt_decodeVertexBuffer',
    TRIANGLES: 'meshopt_decodeIndexBuffer',
    INDICES: 'meshopt_decodeIndexSequence',
  }

  generated = {
    ready: promise,
    supported: true,
    decodeVertexBuffer(target, count, size, source, filter) {
      decode(
        instance.exports.meshopt_decodeVertexBuffer,
        target,
        count,
        size,
        source,
        instance.exports[filters[filter as keyof typeof filters]],
      )
    },
    decodeIndexBuffer(target, count, size, source) {
      decode(instance.exports.meshopt_decodeIndexBuffer, target, count, size, source)
    },
    decodeIndexSequence(target, count, size, source) {
      decode(instance.exports.meshopt_decodeIndexSequence, target, count, size, source)
    },
    decodeGltfBuffer(target, count, size, source, mode, filter) {
      decode(
        instance.exports[decoders[mode as keyof typeof decoders]],
        target,
        count,
        size,
        source,
        instance.exports[filters[filter as keyof typeof filters]],
      )
    },
  }

  return generated
}

export { MeshoptDecoder }
