//
// Copyright 2021 Vulcanize, Inc.
//

import path from 'path';
import { expect } from 'chai';
import { utils } from 'ethers';

import { BaseProvider } from '@ethersproject/providers';

import { instantiate } from './loader';
import { getDummyGraphData, getTestDatabase, getTestIndexer, getTestProvider } from '../test/utils';
import { Database } from './database';
import { Indexer } from '../test/utils/indexer';

const WASM_FILE_PATH = '../build/debug.wasm';

describe('wasm loader tests', () => {
  let exports: any;
  let db: Database;
  let indexer: Indexer;
  let provider: BaseProvider;
  let module: WebAssembly.Module;
  let dummyGraphData: any;

  before(async () => {
    db = getTestDatabase();
    indexer = getTestIndexer();
    provider = getTestProvider();
    dummyGraphData = getDummyGraphData();

    const filePath = path.resolve(__dirname, WASM_FILE_PATH);

    const instance = await instantiate(
      db,
      indexer,
      provider,
      {},
      filePath,
      dummyGraphData
    );

    exports = instance.exports;
    module = instance.module;
  });

  it('should execute exported function', async () => {
    const { callGraphAPI } = exports;
    callGraphAPI();
  });

  it('should execute async function', async () => {
    const { callAsyncMethod } = exports;
    await callAsyncMethod();
  });

  it('should use a class/instance created in wasm from JS', async () => {
    const { Foo, __getString, __pin, __unpin } = exports;

    const fooPtr = await __pin(await Foo.getFoo());
    const foo = Foo.wrap(fooPtr);
    const strPtr = await foo.getString();
    expect(__getString(strPtr)).to.equal('hello world!');
    await __unpin(fooPtr);
  });

  it('should instantiate a class in wasm from JS', async () => {
    const { Foo, FooID, Bar, __getString, __new, __pin, __unpin, __newString } = exports;

    const fooPtr = await __pin(await __new(FooID));
    const foo = Foo.wrap(fooPtr);
    const strPtr = await foo.getString();
    expect(__getString(strPtr)).to.equal('hello world!');
    __unpin(fooPtr);

    const bar = await Bar.__new(await __newString('test'));
    expect(__getString(await bar.prop)).to.equal('test');
  });

  it('should log messages', async () => {
    const { testLog } = exports;

    // Should print all log messages for different levels.
    await testLog();
  });

  it('should throw out of memory error', async () => {
    // Maximum memory is set to 10 pages (640KB) when compiling using asc maximumMemory option.
    // https://www.assemblyscript.org/compiler.html#command-line-options

    const { testMemory, __newString, memory } = exports;

    try {
      // Continue loop until memory size reaches max size 640KB
      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/buffer
      while (memory.buffer.byteLength <= 1024 * 640) {
        // Create long string of 100KB.
        const longString = utils.hexValue(utils.randomBytes(1024 * 100 / 2));

        const stringPtr = await __newString(longString);
        await testMemory(stringPtr);
      }

      expect.fail('wasm code should throw error');
    } catch (error) {
      expect(error).to.be.instanceof(WebAssembly.RuntimeError);
      expect(error.message).to.equal('unreachable');
    }
  });

  it('should reinstantiate wasm', async () => {
    const instance = await instantiate(
      db,
      indexer,
      provider,
      {},
      module,
      dummyGraphData
    );

    exports = instance.exports;
    const { callGraphAPI } = exports;
    await callGraphAPI();
  });
});
