// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

utils.load('test/mjsunit/wasm/wasm-module-builder.js');

WasmInspectorTest = {}

WasmInspectorTest.evalWithUrl = (code, url) =>
    Protocol.Runtime
        .evaluate({'expression': code + '\n//# sourceURL=v8://test/' + url})
        .then(printIfFailure);

WasmInspectorTest.instantiateFromBuffer = function(bytes) {
  var buffer = new ArrayBuffer(bytes.length);
  var view = new Uint8Array(buffer);
  for (var i = 0; i < bytes.length; ++i) {
    view[i] = bytes[i] | 0;
  }
  const module = new WebAssembly.Module(buffer);
  return new WebAssembly.Instance(module);
}

WasmInspectorTest.instantiate = async function(bytes, instance_name = 'instance') {
  const instantiate_code = `var ${instance_name} = (${WasmInspectorTest.instantiateFromBuffer})(${JSON.stringify(bytes)});`;
  await WasmInspectorTest.evalWithUrl(instantiate_code, 'instantiate');
}

WasmInspectorTest.dumpScopeProperties = async function(message) {
  printIfFailure(message);
  for (var value of message.result.result) {
    var value_str = await getScopeValues(value.name, value.value);
    InspectorTest.log('   ' + value.name + ': ' + value_str);
  }
}

WasmInspectorTest.getWasmValue = function(wasmValue) {
  return typeof (wasmValue.value) === 'undefined' ?
      wasmValue.unserializableValue :
      wasmValue.value;
}

function printIfFailure(message) {
  if (!message.result) {
    InspectorTest.logMessage(message);
  }
  return message;
}

async function getScopeValues(name, value) {
  if (value.type == 'object') {
    if (value.subtype == 'typedarray') return value.description;
    if (name == 'instance') return dumpInstanceProperties(value);
    if (name == 'function tables') return dumpTables(value);

    let msg = await Protocol.Runtime.getProperties({objectId: value.objectId});
    printIfFailure(msg);
    const printProperty = function(elem) {
      const wasmValue = WasmInspectorTest.getWasmValue(elem.value);
      return `"${elem.name}": ${wasmValue} (${elem.value.subtype})`;
    }
    return msg.result.result.map(printProperty).join(', ');
  }
  return WasmInspectorTest.getWasmValue(value) + ' (' + value.subtype + ')';
}

function recursiveGetPropertiesWrapper(value, depth) {
  return recursiveGetProperties({result: {result: [value]}}, depth);
}

async function recursiveGetProperties(value, depth) {
  if (depth > 0) {
    const properties = await Promise.all(value.result.result.map(
        x => {return Protocol.Runtime.getProperties({objectId: x.value.objectId});}));
    const recursiveProperties = await Promise.all(properties.map(
        x => {return recursiveGetProperties(x, depth - 1);}));
    return recursiveProperties.flat();
  }
  return value;
}

async function dumpTables(tablesObj) {
  let msg = await Protocol.Runtime.getProperties({objectId: tablesObj.objectId});
  var tables_str = [];
  for (var table of msg.result.result) {
    const func_entries = await recursiveGetPropertiesWrapper(table, 2);
    var functions = [];
    for (var func of func_entries) {
      for (var value of func.result.result) {
        functions.push(`${value.name}: ${value.value.description}`);
      }
    }
    const functions_str = functions.join(', ');
    tables_str.push(`      ${table.name}: ${functions_str}`);
  }
  return '\n' + tables_str.join('\n');
}

async function dumpInstanceProperties(instanceObj) {
  function invokeGetter(property) {
    return this[JSON.parse(property)];
  }

  const exportsName = 'exports';
  let exportsObj = await Protocol.Runtime.callFunctionOn(
      {objectId: instanceObj.objectId,
      functionDeclaration: invokeGetter.toString(),
      arguments: [{value: JSON.stringify(exportsName)}]
    });
  printIfFailure(exportsObj);
  let exports = await Protocol.Runtime.getProperties(
      {objectId: exportsObj.result.result.objectId});
  printIfFailure(exports);

  const printExports = function(value) {
    return `"${value.name}" (${value.value.className})`;
  }
  const formattedExports = exports.result.result.map(printExports).join(', ');
  return `${exportsName}: ${formattedExports}`
}
