import atexit
import collections
import os
import shutil
import subprocess
import tempfile
import time

TESTS_DIR = os.path.dirname(os.path.dirname(__file__))
BATAVIA_DIR = os.path.dirname(TESTS_DIR)

SuiteConfiguration = collections.namedtuple(
    'SuiteConfiguration',
    [
        # A state variable to determine if the test environment has been configured.
        'configured',
        # Temporary directory containing all files generated by this test process
        # Set during setUpSuite(), emptied after each test
        'output_dir',
        # Set during setUpSuite(), emptied after the suite
        'suite_output_dir',
        # Location of compiled batavia.js file
        'batavia_js_dir',
        # JS execution server and port
        'js_harness_port'
    ],
)

_suite_configuration = SuiteConfiguration(
    configured=False,
    output_dir='',
    suite_output_dir='',
    batavia_js_dir='',
    js_harness_port='',
)


def setUpSuite():
    """Configure the entire test suite.

    This only needs to be run once, prior to the first test.
    """
    global _suite_configuration
    if _suite_configuration.configured:
        return _suite_configuration

    suite_output_dir = create_output_dir()
    output_dir = create_output_dir()

    precompile = os.environ.get('PRECOMPILE', 'true').lower() == 'true'

    if precompile:
        batavia_js_dir = suite_output_dir
    else:
        batavia_js_dir = os.path.join(BATAVIA_DIR, 'dist')

    launch_js_harness(output_dir, batavia_js_dir)

    if precompile:
        build_batavia_js(suite_output_dir)
    else:
        print("Not precompiling 'batavia.js' as part of test run")

    js_harness_port = read_js_harness_port(output_dir)

    _suite_configuration = SuiteConfiguration(
        configured=True,
        output_dir=output_dir,
        suite_output_dir=suite_output_dir,
        batavia_js_dir=batavia_js_dir,
        js_harness_port=js_harness_port,
    )

    return _suite_configuration


def read_js_harness_port(_output_dir):
    while 'server.port' not in os.listdir(_output_dir):
        time.sleep(1)
    with open(os.path.join(_output_dir, 'server.port')) as f:
        return f.readline()


def create_output_dir():
    output_dir = tempfile.mkdtemp(dir=TESTS_DIR)

    def remove_output_dir():
        if output_dir != '':
            try:
                shutil.rmtree(output_dir)
            except FileNotFoundError:
                pass

    atexit.register(remove_output_dir)

    return output_dir


def build_batavia_js(suite_output_dir):
    print("building 'batavia.js' for development")
    proc = subprocess.Popen(
        ' '.join(
            [os.path.join(BATAVIA_DIR, "node_modules", ".bin", "webpack"),
             "--bail", "-d", "--output-path=\"%s\"" % suite_output_dir]),
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        shell=True,
    )
    try:
        out, err = proc.communicate(timeout=60)
        print(out.decode('utf-8'))
    except subprocess.TimeoutExpired:
        proc.kill()
        out, err = proc.communicate()
        raise
    if proc.returncode != 0:
        raise Exception(
            "Error compiling batavia sources: " + out.decode('ascii'))


def launch_js_harness(output_dir, batavia_js_dir):
    js_harness = subprocess.Popen(
        ['node',
         os.path.join(TESTS_DIR, 'js_harness.js'),
         os.path.join(output_dir, 'server.port'),
         os.path.join(batavia_js_dir, 'batavia.js'),
         ],
    )
    atexit.register(lambda: js_harness and js_harness.kill())
