/**
 * @license
 * Copyright 2019 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 */

/** @constructor */
function WasmOffsetConverter(wasmBytes, wasmModule) {
  // This class parses a WASM binary file, and constructs a mapping from
  // function indices to the start of their code in the binary file, as well
  // as parsing the name section to allow conversion of offsets to function names.
  //
  // The main purpose of this module is to enable the conversion of function
  // index and offset from start of function to an offset into the WASM binary.
  // This is needed to look up the WASM source map as well as generate
  // consistent program counter representations given v8's non-standard
  // WASM stack trace format.
  //
  // v8 bug: https://crbug.com/v8/9172
  //
  // This code is also used to check if the candidate source map offset is
  // actually part of the same function as the offset we are looking for,
  // as well as providing the function names for a given offset.

  // current byte offset into the WASM binary, as we parse it
  // the first section starts at offset 8.
  var offset = 8;

  // the index of the next function we see in the binary
  var funcidx = 0;

  // map from function index to byte offset in WASM binary
  this.offset_map = {};
  this.func_starts = [];

  // map from function index to names in WASM binary
  this.name_map = {};

  // number of imported functions this module has
  this.import_functions = 0;

  // the buffer unsignedLEB128 will read from.
  var buffer = wasmBytes;

  function unsignedLEB128() {
    // consumes an unsigned LEB128 integer starting at `offset`.
    // changes `offset` to immediately after the integer
    var result = 0;
    var shift = 0;
    do {
      var byte = buffer[offset++];
      result += (byte & 0x7F) << shift;
      shift += 7;
    } while (byte & 0x80);
    return result;
  }

  function skipLimits() {
    var flags = unsignedLEB128();
    unsignedLEB128(); // initial size
    var hasMax = (flags & 1) != 0;
    if (hasMax) {
      unsignedLEB128();
    }
  }

  binary_parse:
  while (offset < buffer.length) {
    var start = offset;
    var type = buffer[offset++];
    var end = unsignedLEB128() + offset;
    switch (type) {
      case 2: // import section
        // we need to find all function imports and increment funcidx for each one
        // since functions defined in the module are numbered after all imports
        var count = unsignedLEB128();

        while (count-- > 0) {
          // skip module
          offset = unsignedLEB128() + offset;
          // skip name
          offset = unsignedLEB128() + offset;

          var kind = buffer[offset++];
          switch (kind) {
            case 0: // function import
              ++funcidx;
              unsignedLEB128(); // skip function type
              break;
            case 1: // table import
              ++offset; // FIXME: should be SLEB128
              skipLimits();
              break;
            case 2: // memory import
              skipLimits();
              break;
            case 3: // global import
              offset += 2; // skip type id byte and mutability byte
              break;
            case 4: // tag import
              ++offset; // // FIXME: should be SLEB128
              break;
#if ASSERTIONS
            default: throw 'bad import kind: ' + kind;
#endif
          }
        }
        this.import_functions = funcidx;
        break;
      case 10: // code section
        var count = unsignedLEB128();
        while (count-- > 0) {
          var size = unsignedLEB128();
          this.offset_map[funcidx++] = offset;
          this.func_starts.push(offset);
          offset += size;
        }
        break binary_parse;
    }
    offset = end;
  }

  var sections = WebAssembly.Module.customSections(wasmModule, "name");
  for (var i = 0; i < sections.length; ++i) {
    buffer = new Uint8Array(sections[i]);
    if (buffer[0] != 1) // not a function name section
      continue;
    offset = 1;
    unsignedLEB128(); // skip byte count
    var count = unsignedLEB128();
    while (count-- > 0) {
      var index = unsignedLEB128();
      var length = unsignedLEB128();
      this.name_map[index] = UTF8ArrayToString(buffer, offset, length);
      offset += length;
    }
  }
}

WasmOffsetConverter.prototype.convert = function (funcidx, offset) {
  return this.offset_map[funcidx] + offset;
}

WasmOffsetConverter.prototype.getIndex = function (offset) {
  var lo = 0;
  var hi = this.func_starts.length;
  var mid;

  while (lo < hi) {
    mid = Math.floor((lo + hi) / 2);
    if (this.func_starts[mid] > offset) {
      hi = mid;
    } else {
      lo = mid + 1;
    }
  }
  return lo + this.import_functions - 1;
}

WasmOffsetConverter.prototype.isSameFunc = function (offset1, offset2) {
  return this.getIndex(offset1) == this.getIndex(offset2);
}

WasmOffsetConverter.prototype.getName = function (offset) {
  var index = this.getIndex(offset);
  return this.name_map[index] || ('wasm-function[' + index + ']');
}
