#!/usr/bin/env python3

import argparse
import sys
import os
import glob
import subprocess
import shutil
import multiprocessing as mp

SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
INTERPRETER_DIR = os.path.join(SCRIPT_DIR, '..', 'interpreter')
WASM_EXEC = os.path.join(INTERPRETER_DIR, 'wasm')

WAST_TESTS_DIR = os.path.join(SCRIPT_DIR, 'core')
HARNESS_DIR = os.path.join(SCRIPT_DIR, 'harness')

HARNESS_FILES = ['testharness.js', 'testharnessreport.js', 'testharness.css']
WPT_URL_PREFIX = '/resources'

# Helpers.
def run(*cmd):
    return subprocess.run(cmd,
                          stdout=subprocess.PIPE,
                          stderr=subprocess.STDOUT,
                          universal_newlines=True)
def call(*cmd):
    return subprocess.call(cmd,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           universal_newlines=True)

# Preconditions.
def ensure_remove_dir(path):
    if os.path.exists(path):
        shutil.rmtree(path)

def ensure_empty_dir(path):
    ensure_remove_dir(path)
    os.mkdir(path)

def compile_wasm_interpreter():
    print("Recompiling the wasm interpreter...")
    result = call('make', '-C', INTERPRETER_DIR, 'clean', 'default')
    if result != 0:
        print("Couldn't recompile wasm spec interpreter")
        sys.exit(1)
    print("Done!")

def ensure_wasm_executable(path_to_wasm):
    """
    Ensure we have built the wasm spec interpreter.
    """
    result = call(path_to_wasm, '-v', '-e', '')
    if result != 0:
        print('Unable to run the wasm executable')
        sys.exit(1)

# JS harness.
def convert_one_wast_file(inputs):
    wast_file, js_file = inputs
    print('Compiling {} to JS...'.format(wast_file))
    return run(WASM_EXEC, wast_file, '-h', '-o', js_file)

def convert_wast_to_js(out_js_dir):
    """Compile all the wast files to JS and store the results in the JS dir."""

    inputs = []

    for wast_file in glob.glob(os.path.join(WAST_TESTS_DIR, '*.wast')):
        # Don't try to compile tests that are supposed to fail.
        if '.fail.' in wast_file:
            continue

        js_filename = os.path.basename(wast_file) + '.js'
        js_file = os.path.join(out_js_dir, js_filename)
        inputs.append((wast_file, js_file))

    pool = mp.Pool(processes=8)
    for result in pool.imap_unordered(convert_one_wast_file, inputs):
        if result.returncode != 0:
            print('Error when compiling to JS:')
            print(result.args)
            if result.stdout:
                # stderr is piped to stdout via `run`, so we only need to
                # worry about stdout
                print(result.stdout)
    return [js_file for (wast_file, js_file) in inputs]

def copy_harness_files(out_js_dir, include_harness):
    harness_dir = os.path.join(out_js_dir, 'harness')
    ensure_empty_dir(harness_dir)

    print('Copying JS test harness to the JS out dir...')
    for js_file in glob.glob(os.path.join(HARNESS_DIR, '*')):
        if os.path.basename(js_file) in HARNESS_FILES and not include_harness:
            continue
        shutil.copy(js_file, harness_dir)

def build_js(out_js_dir):
    print('Building JS...')
    convert_wast_to_js(out_js_dir)
    copy_harness_files(out_js_dir, False)
    print('Done building JS.')

# HTML harness.
HTML_HEADER = """<!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>WebAssembly Web Platform Test</title>
    </head>
    <body>

        <script src={WPT_PREFIX}/testharness.js></script>
        <script src={WPT_PREFIX}/testharnessreport.js></script>
        <script src={PREFIX}/{JS_HARNESS}></script>

        <div id=log></div>
"""

HTML_BOTTOM = """
    </body>
</html>
"""

def wrap_single_test(js_file):
    test_func_name = os.path.basename(js_file).replace('.', '_').replace('-', '_')

    content = "(function {}() {{\n".format(test_func_name)
    with open(js_file, 'r') as f:
        content += f.read()
    content += "reinitializeRegistry();\n})();\n"

    with open(js_file, 'w') as f:
        f.write(content)

def build_html_js(out_dir):
    ensure_empty_dir(out_dir)
    copy_harness_files(out_dir, True)

    tests = convert_wast_to_js(out_dir)
    for js_file in tests:
        wrap_single_test(js_file)
    return tests

def build_html_from_js(tests, html_dir, use_sync):
    for js_file in tests:
        js_filename = os.path.basename(js_file)
        html_filename = js_filename + '.html'
        html_file = os.path.join(html_dir, html_filename)
        js_harness = "sync_index.js" if use_sync else "async_index.js"
        with open(html_file, 'w+') as f:
            content = HTML_HEADER.replace('{PREFIX}', './js/harness') \
                                 .replace('{WPT_PREFIX}', './js/harness') \
                                 .replace('{JS_HARNESS}', js_harness)
            content += "        <script src=./js/{SCRIPT}></script>".replace('{SCRIPT}', js_filename)
            content += HTML_BOTTOM
            f.write(content)

def build_html(html_dir, js_dir, use_sync):
    print("Building HTML tests...")

    js_html_dir = os.path.join(html_dir, 'js')

    tests = build_html_js(js_html_dir)

    print('Building WPT tests from JS tests...')
    build_html_from_js(tests, html_dir, use_sync)

    print("Done building HTML tests.")


# Front page harness.
def build_front_page(out_dir, js_dir, use_sync):
    print('Building front page containing all the HTML tests...')

    js_out_dir = os.path.join(out_dir, 'js')

    tests = build_html_js(js_out_dir)

    front_page = os.path.join(out_dir, 'index.html')
    js_harness = "sync_index.js" if use_sync else "async_index.js"
    with open(front_page, 'w+') as f:
        content = HTML_HEADER.replace('{PREFIX}', './js/harness') \
                             .replace('{WPT_PREFIX}', './js/harness')\
                             .replace('{JS_HARNESS}', js_harness)
        for js_file in tests:
            filename = os.path.basename(js_file)
            content += "        <script src=./js/{SCRIPT}></script>\n".replace('{SCRIPT}', filename)
        content += HTML_BOTTOM
        f.write(content)

    print('Done building front page!')

# Main program.
def process_args():
    parser = argparse.ArgumentParser(description="Helper tool to build the\
            multi-stage cross-browser test suite for WebAssembly.")

    parser.add_argument('--js',
                        dest="js_dir",
                        help="Relative path to the output directory for the pure JS tests.",
                        type=str)

    parser.add_argument('--html',
                        dest="html_dir",
                        help="Relative path to the output directory for the Web Platform tests.",
                        type=str)

    parser.add_argument('--front',
                        dest="front_dir",
                        help="Relative path to the output directory for the front page.",
                        type=str)

    parser.add_argument('--dont-recompile',
                        action="store_const",
                        dest="compile",
                        help="Don't recompile the wasm spec interpreter (by default, it is)",
                        const=False,
                        default=True)

    parser.add_argument('--use-sync',
                        action="store_const",
                        dest="use_sync",
                        help="Let the tests use the synchronous JS API (by default, it does not)",
                        const=True,
                        default=False)

    return parser.parse_args(), parser

if __name__ == '__main__':
    args, parser = process_args()

    js_dir = args.js_dir
    html_dir = args.html_dir
    front_dir = args.front_dir

    if front_dir is None and js_dir is None and html_dir is None:
        print('At least one mode must be selected.\n')
        parser.print_help()
        sys.exit(1)

    if args.compile:
        compile_wasm_interpreter()

    ensure_wasm_executable(WASM_EXEC)

    if js_dir is not None:
        ensure_empty_dir(js_dir)
        build_js(js_dir)

    if html_dir is not None:
        ensure_empty_dir(html_dir)
        build_html(html_dir, js_dir, args.use_sync)

    if front_dir is not None:
        ensure_empty_dir(front_dir)
        build_front_page(front_dir, js_dir, args.use_sync)

    print('Done!')
