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

#include <iostream>

#include "test/wasm-api-tests/wasm-api-test.h"

namespace v8 {
namespace internal {
namespace wasm {

using ::wasm::Frame;
using ::wasm::Message;

namespace {

own<Trap> IdentityCallback(const Val args[], Val results[]) {
  results[0] = args[0].copy();
  return nullptr;
}

}  // namespace

TEST_F(WasmCapiTest, HostRef) {
  ValueType rr_reps[] = {kWasmExternRef, kWasmExternRef};
  ValueType ri_reps[] = {kWasmExternRef, kWasmI32};
  ValueType ir_reps[] = {kWasmI32, kWasmExternRef};
  // Naming convention: result_params_sig.
  FunctionSig r_r_sig(1, 1, rr_reps);
  FunctionSig v_r_sig(0, 1, rr_reps);
  FunctionSig r_v_sig(1, 0, rr_reps);
  FunctionSig v_ir_sig(0, 2, ir_reps);
  FunctionSig r_i_sig(1, 1, ri_reps);
  uint32_t func_index = builder()->AddImport(CStrVector("f"), &r_r_sig);
  const bool kMutable = true;
  uint32_t global_index = builder()->AddExportedGlobal(
      kWasmExternRef, kMutable, WasmInitExpr::RefNullConst(HeapType::kExtern),
      CStrVector("global"));
  uint32_t table_index = builder()->AddTable(kWasmExternRef, 10);
  builder()->AddExport(CStrVector("table"), kExternalTable, table_index);
  byte global_set_code[] = {WASM_SET_GLOBAL(global_index, WASM_GET_LOCAL(0))};
  AddExportedFunction(CStrVector("global.set"), global_set_code,
                      sizeof(global_set_code), &v_r_sig);
  byte global_get_code[] = {WASM_GET_GLOBAL(global_index)};
  AddExportedFunction(CStrVector("global.get"), global_get_code,
                      sizeof(global_get_code), &r_v_sig);
  byte table_set_code[] = {
      WASM_TABLE_SET(table_index, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))};
  AddExportedFunction(CStrVector("table.set"), table_set_code,
                      sizeof(table_set_code), &v_ir_sig);
  byte table_get_code[] = {WASM_TABLE_GET(table_index, WASM_GET_LOCAL(0))};
  AddExportedFunction(CStrVector("table.get"), table_get_code,
                      sizeof(table_get_code), &r_i_sig);
  byte func_call_code[] = {WASM_CALL_FUNCTION(func_index, WASM_GET_LOCAL(0))};
  AddExportedFunction(CStrVector("func.call"), func_call_code,
                      sizeof(func_call_code), &r_r_sig);

  own<FuncType> func_type =
      FuncType::make(ownvec<ValType>::make(ValType::make(::wasm::ANYREF)),
                     ownvec<ValType>::make(ValType::make(::wasm::ANYREF)));
  own<Func> callback = Func::make(store(), func_type.get(), IdentityCallback);
  Extern* imports[] = {callback.get()};
  Instantiate(imports);

  Global* global = GetExportedGlobal(0);
  Table* table = GetExportedTable(1);
  const Func* global_set = GetExportedFunction(2);
  const Func* global_get = GetExportedFunction(3);
  const Func* table_set = GetExportedFunction(4);
  const Func* table_get = GetExportedFunction(5);
  const Func* func_call = GetExportedFunction(6);

  own<Foreign> host1 = Foreign::make(store());
  own<Foreign> host2 = Foreign::make(store());
  host1->set_host_info(reinterpret_cast<void*>(1));
  host2->set_host_info(reinterpret_cast<void*>(2));

  // Basic checks.
  EXPECT_TRUE(host1->copy()->same(host1.get()));
  EXPECT_TRUE(host2->copy()->same(host2.get()));
  Val val = Val::ref(host1->copy());
  EXPECT_TRUE(val.ref()->copy()->same(host1.get()));
  own<Ref> ref = val.release_ref();
  EXPECT_EQ(nullptr, val.ref());
  EXPECT_TRUE(ref->copy()->same(host1.get()));

  // Interact with the Global.
  Val args[2];
  Val results[1];
  own<Trap> trap = global_get->call(nullptr, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_EQ(nullptr, results[0].release_ref());
  args[0] = Val::ref(host1.get()->copy());
  trap = global_set->call(args, nullptr);
  EXPECT_EQ(nullptr, trap);
  trap = global_get->call(nullptr, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_TRUE(results[0].release_ref()->same(host1.get()));
  args[0] = Val::ref(host2.get()->copy());
  trap = global_set->call(args, nullptr);
  EXPECT_EQ(nullptr, trap);
  trap = global_get->call(nullptr, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_TRUE(results[0].release_ref()->same(host2.get()));
  args[0] = Val::ref(own<Ref>());
  trap = global_set->call(args, nullptr);
  EXPECT_EQ(nullptr, trap);
  trap = global_get->call(nullptr, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_EQ(nullptr, results[0].release_ref());

  EXPECT_EQ(nullptr, global->get().release_ref());
  global->set(Val(host2->copy()));
  trap = global_get->call(nullptr, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_TRUE(results[0].release_ref()->same(host2.get()));
  EXPECT_TRUE(global->get().release_ref()->same(host2.get()));

  // Interact with the Table.
  args[0] = Val::i32(0);
  trap = table_get->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_EQ(nullptr, results[0].release_ref());
  args[0] = Val::i32(1);
  trap = table_get->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_EQ(nullptr, results[0].release_ref());
  args[0] = Val::i32(0);
  args[1] = Val::ref(host1.get()->copy());
  trap = table_set->call(args, nullptr);
  EXPECT_EQ(nullptr, trap);
  args[0] = Val::i32(1);
  args[1] = Val::ref(host2.get()->copy());
  trap = table_set->call(args, nullptr);
  EXPECT_EQ(nullptr, trap);
  args[0] = Val::i32(0);
  trap = table_get->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_TRUE(results[0].release_ref()->same(host1.get()));
  args[0] = Val::i32(1);
  trap = table_get->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_TRUE(results[0].release_ref()->same(host2.get()));
  args[0] = Val::i32(0);
  args[1] = Val::ref(own<Ref>());
  trap = table_set->call(args, nullptr);
  EXPECT_EQ(nullptr, trap);
  trap = table_get->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_EQ(nullptr, results[0].release_ref());

  EXPECT_EQ(nullptr, table->get(2));
  table->set(2, host1.get());
  args[0] = Val::i32(2);
  trap = table_get->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_TRUE(results[0].release_ref()->same(host1.get()));
  EXPECT_TRUE(table->get(2)->same(host1.get()));

  // Interact with the Function.
  args[0] = Val::ref(own<Ref>());
  trap = func_call->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_EQ(nullptr, results[0].release_ref());
  args[0] = Val::ref(host1.get()->copy());
  trap = func_call->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_TRUE(results[0].release_ref()->same(host1.get()));
  args[0] = Val::ref(host2.get()->copy());
  trap = func_call->call(args, results);
  EXPECT_EQ(nullptr, trap);
  EXPECT_TRUE(results[0].release_ref()->same(host2.get()));
}

}  // namespace wasm
}  // namespace internal
}  // namespace v8