// Copyright 2016 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.

// Flags: --expose-wasm

utils.load('test/inspector/wasm-inspector-test.js');

InspectorTest.log("Tests how wasm scripts are reported");

let contextGroup = new InspectorTest.ContextGroup();
let sessions = [
  // Main session.
  trackScripts(),
  // Extra session to verify that all inspectors get same messages.
  // See https://bugs.chromium.org/p/v8/issues/detail?id=9725.
  trackScripts(),
];

// Create module with given custom sections.
function createModule(...customSections) {
  var builder = new WasmModuleBuilder();
  builder.addFunction('nopFunction', kSig_v_v).addBody([kExprNop]);
  builder.addFunction('main', kSig_v_v)
      .addBody([kExprBlock, kWasmVoid, kExprI32Const, 2, kExprDrop, kExprEnd])
      .exportAs('main');
  for (var { name, value } of customSections) {
    builder.addCustomSection(name, value);
  }
  return builder.toArray();
}

function testFunction(bytes) {
  // Compilation triggers registration of wasm scripts.
  new WebAssembly.Module(new Uint8Array(bytes));
}

// Generate stable IDs.
let scriptIds = {};
function nextStableId(id) {
  if (!(id in scriptIds)) {
    scriptIds[id] = Object.keys(scriptIds).length;
  }
  return scriptIds[id];
}

contextGroup.addScript(testFunction.toString(), 0, 0, 'v8://test/testFunction');

InspectorTest.log(
    'Check that each inspector gets a wasm script at module creation time.');

// Sample .debug_info section.
// Content doesn't matter, as we don't try to parse it in V8,
// but should be non-empty to check that we're skipping it correctly.
const embeddedDWARFSection = {
  name: '.debug_info',
  value: [1, 2, 3, 4, 5]
};

// Sample external_debug_info section set to "abc".
const externalDWARFSection = {
  name: 'external_debug_info',
  value: [3, 97, 98, 99]
};

// Sample sourceMappingURL section set to "abc".
const sourceMapSection = {
  name: 'sourceMappingURL',
  value: [3, 97, 98, 99]
};

sessions[0]
    .Protocol.Runtime
    .evaluate({
      'expression': `//# sourceURL=v8://test/runTestRunction

      // no debug info
      testFunction([${createModule()}]);

      // shared script for identical modules
      testFunction([${createModule()}]);

      // External DWARF
      testFunction([${createModule(externalDWARFSection)}]);

      // Embedded DWARF
      testFunction([${createModule(embeddedDWARFSection)}]);

      // Source map
      testFunction([${createModule(sourceMapSection)}]);

      // SourceMap + External DWARF
      testFunction([${createModule(sourceMapSection, externalDWARFSection)}]);

      // External DWARF + SourceMap (different order)
      testFunction([${createModule(externalDWARFSection, sourceMapSection)}]);

      // Embedded DWARF + External DWARF
      testFunction([${
          createModule(embeddedDWARFSection, externalDWARFSection)}]);

      // External + Embedded DWARF (different order)
      testFunction([${
          createModule(externalDWARFSection, embeddedDWARFSection)}]);

      // Embedded DWARF + source map
      testFunction([${createModule(embeddedDWARFSection, sourceMapSection)}]);

      // Source map + Embedded DWARF (different order)
      testFunction([${createModule(sourceMapSection, embeddedDWARFSection)}]);
      `
    })
    .then(
        () => (
            // At this point all scripts were parsed.
            // Stop tracking and wait for script sources in each session.
            Promise.all(sessions.map(session => session.getScripts()))))
    .catch(err => {
      InspectorTest.log(err.stack);
    })
    .then(() => InspectorTest.completeTest());

function trackScripts(debuggerParams) {
  let {id: sessionId, Protocol} = contextGroup.connect();
  let scripts = [];

  Protocol.Debugger.enable(debuggerParams);
  Protocol.Debugger.onScriptParsed(handleScriptParsed);

  async function loadScript({
    url,
    scriptId,
    sourceMapURL,
    startColumn,
    endColumn,
    codeOffset,
    debugSymbols
  }) {
    let stableId = nextStableId(scriptId);
    InspectorTest.log(`Session #${sessionId}: Script #${
        scripts.length} parsed. URL: ${url}. Script ID: ${
        stableId}, Source map URL: ${sourceMapURL}, debug symbols: ${
        debugSymbols.type}:${debugSymbols.externalURL}. module begin: ${
        startColumn}, module end: ${endColumn}, code offset: ${codeOffset}`);
    let {result: {scriptSource, bytecode}} =
        await Protocol.Debugger.getScriptSource({scriptId});
    if (bytecode) {
      if (scriptSource) {
        InspectorTest.log('Unexpected scriptSource with bytecode: ');
        InspectorTest.log(scriptSource);
      }
      // Binary value is represented as base64 in JSON, decode it.
      bytecode = InspectorTest.decodeBase64(bytecode);
      // Check that it can be parsed back to a WebAssembly module.
      let module = new WebAssembly.Module(bytecode);
      scriptSource =
          `
Raw: ${Array.from(bytecode, b => ('0' + b.toString(16)).slice(-2)).join(' ')}
Imports: [${
              WebAssembly.Module.imports(module)
                  .map(i => `${i.name}: ${i.kind} from "${i.module}"`)
                  .join(', ')}]
Exports: [${
              WebAssembly.Module.exports(module)
                  .map(e => `${e.name}: ${e.kind}`)
                  .join(', ')}]
      `.trim();
    }
    InspectorTest.log(`Session #${sessionId}: Source for ${url}:`);
    InspectorTest.log(scriptSource);
  }

  function handleScriptParsed({params}) {
    if (params.url.startsWith('wasm://')) {
      scripts.push(loadScript(params));
    }
  }

  return {
    Protocol,
    getScripts: () => Promise.all(scripts),
  };
}
