package com.maxmind.jmeter.planner;

import com.maxmind.jmeter.generators.Generator;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Random;
import java.util.Scanner;
import java.util.stream.Collectors;

/**
 * Run script code and return result.
 */
public class ScriptRunner<T> {

private static final String COMMON_JS = "/common.js";

private final String       scriptSource;
private final ScriptEngine engine;

public ScriptRunner(int seed, String scriptSource) {
    this(new Random(seed), scriptSource);
}

public ScriptRunner(Random random, String scriptSource) {
    this.scriptSource = scriptSource;
    ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
    engine = scriptEngineManager.getEngineByName("nashorn");
    loadCommon(random);
}

public static <U> ScriptRunner<U> fromFile(int seed, String userScriptName) {
    return new ScriptRunner<U>(seed, readFile(userScriptName));
}

public static <U> ScriptRunner<U> fromFile(
    Random random, String userScriptName) {
    return new ScriptRunner<U>(random, readFile(userScriptName));
}


@SuppressWarnings("unchecked")
public Generator<T> eval_js() {
    try {
        return (Generator<T>) evalScript(scriptSource);
    } catch (Exception e) {
        throw new RuntimeException("Cannot run script ", e);
    }
}

@SuppressWarnings("unchecked")
private void loadCommon(Random random) {
    InputStream inputStream = ScriptRunner.class.getResourceAsStream(COMMON_JS);
    String common_js = new Scanner(inputStream, "UTF-8").
        useDelimiter("\\A").next();

    evalScript(common_js);
    Invocable invocable = (Invocable) engine;

    try {
        invocable.invokeFunction("_mm_initRandom", random);
    } catch (ScriptException | NoSuchMethodException e) {
        throw new RuntimeException("Cannot run common ", e);
    }
}

private Object evalScript(String script) {
    try {
        return engine.eval(script);
    } catch (ScriptException e) {
        throw new RuntimeException("Cannot run script ", e);
    }
}

private static String readFile(String name) {
    Path scriptPath = Paths.get(name);
    try {
        return Files.readAllLines(
            scriptPath,
            StandardCharsets.UTF_8).
            stream().
            collect(Collectors.joining("\n"));
    } catch (IOException e) {
        throw new RuntimeException("Cannot load script ", e);
    }
}

}
