#!/usr/bin/env node

var execSync = require('child_process').execSync;
var spawn = require('child_process').spawn;
var path = require('path');
var fs = require('fs');

var createSourceSet = require("../lib/converter").createSourceSet;
var Readable = require('stream').Readable;
var EventEmitter = require('events');

var printError = function (errorMessage) {
    if (typeof errorMessage !== "string") {
        console.error("ERROR:");
        console.error(errorMessage)
    } else {
        console.error("ERROR: " + errorMessage);
    }
};

function run(command, args) {
    var child = spawn(command, args);
    process.stdin.pipe(child.stdin);

    child.stdout.pipe(process.stdout);
    child.stderr.pipe(process.stderr);

    return child;
}

function printInvalidJavaHome() {
    printError("Your \"JAVA_HOME\" environment variable points to an invalid directory.");
    printError("As of now \"dukat\" requires Java Runtime Environment to be installed.");
    printError("Either set \"JAVA_HOME\" to a proper Java installation,");
    printError("or make sure \"java\" is callable from commandline and unset \"JAVA_HOME\".");
}

function printNoJava() {
    printError("\"java\" is not callable from commandline and no \"JAVA_HOME\" environment variable is set.");
    printError("As of now \"dukat\" requires Java Runtime Environment to be installed.");
    printError("Either set \"JAVA_HOME\" to a proper Java installation,");
    printError("or make sure \"java\" is callable from commandline.");
}

function findJavaExecutable() {
    var javaHome = process.env.JAVA_HOME
    var result = javaHome ? path.join(javaHome, "bin", "java") : "java"
    try {
        execSync('"' + result + "\" -version", {
            stdio: ['pipe', 'ignore', 'pipe']
        });
    } catch (error) {
        printError(error)
        if (javaHome) {
            printInvalidJavaHome();
        } else {
            printNoJava();
        }
        process.exit(1);
    }
    return result
}

function createReadable() {
    var readable = new Readable();
    readable._read = function(chunk) {};

    return readable
}

function getStdLib() {
    return path.resolve(__dirname, "..", "d.ts.libs/lib.es6.d.ts");
}

function processArgs(args) {
    var skip_2args = new Set(["-d", "-p", "-m", "-r"]);
    var ordinary_args = new Set(["--descriptors", "--descriptors-jar"]);
    var count = 0;

    var binaryOutput = null;
    var stdlib = getStdLib();
    var tsConfig = null;

    while (count < args.length) {
        var arg = args[count];
        if (arg == "-b") {
            binaryOutput = args[count + 1];
            count += 2;
        } else if (arg == "--ts-config") {
            tsConfig = args[count + 1];
            count += 2;
        } else if(arg == "-l") {
            stdlib = args[count + 1];
            count += 2;
        } else if (skip_2args.has(arg)) {
            count += 2;
        } else if (ordinary_args.has(arg)) {
            count++;
        } else {
            break;
        }
    }

    var files = [];
    if (count < args.length) {
        files = args.slice(count).map(function(arg) {
            return path.resolve(arg);
        });
    }

    return {
        stdlib: stdlib,
        binaryOutput: binaryOutput,
        files: files,
        tsConfig: tsConfig
    };
}

function endsWith(str, postfix) {
    return str.lastIndexOf(postfix) == (str.length - postfix.length);
}



function createBinary(tsConfig, stdlib, emitDiagnostics, failOnWarnings, files) {
    return createSourceSet(tsConfig, stdlib, emitDiagnostics, failOnWarnings, files);
}

function createReadableStream(binary, onData, onEnd) {
    var readable = createReadable();

    if (typeof onData == "function") {
        readable.on("data", onData);
    }

    if (typeof onEnd == "function") {
        readable.on("end", onEnd);
    }

    readable.push(binary.serializeBinary());
    readable.push(null);

    return readable;
}

function cliMode(args) {
    var packageDir = path.resolve(__dirname, "..");

    if (args[0] == "-v" || args[0] == "version") {
        var version = require(path.resolve(packageDir, "package.json")).version;
        console.log("dukat version " + version);
        return;
    }

    var runtimePath = path.resolve(packageDir, "build/runtime");
    var cliJar = path.resolve(runtimePath, "dukat-cli.jar");

    var java = findJavaExecutable();

    var argsProcessed = processArgs(args);

    var files = argsProcessed.files;
    var is_ts = files.every(function(file) { return endsWith(file, ".d.ts") || endsWith(file, ".ts")});
    var is_idl = files.every(function(file) { return endsWith(file, ".idl") || endsWith(file, ".webidl")});
    var is_js = files.every(function(file) { return endsWith(file, ".js")});

    var commandArgs = ["-jar", cliJar].concat(args);

    if (is_ts || is_js) {
        try {
            let bundle = createBinary(argsProcessed.tsConfig, argsProcessed.stdlib, false, false, files);

            var inputStream = createReadableStream(bundle);

            if (typeof argsProcessed.binaryOutput == "string") {
                inputStream.pipe(fs.createWriteStream(argsProcessed.binaryOutput));
                return null;
            }

            var dukatProcess = run(java, commandArgs);
            inputStream.pipe(dukatProcess.stdin);
            return dukatProcess;
        } catch (e) {
            if (e.hasOwnProperty("tsDiagnostic")) {
                console.log(`failed to parse tsconfig: ${argsProcessed.tsConfig}`);
            } else {
                console.log("unresolved exception");
            }
            console.log(e);
            process.exit(1);
        }

    } else if (is_idl) {
        return run(java, commandArgs);
    }

    process.exit(1);
}

var main = function (args) {
    var childProcess = cliMode(args);
    if (childProcess instanceof EventEmitter) {
        childProcess.on("exit", function() {
            process.exit();
        });
    }
};

if (require.main === module) {
    main(process.argv.slice(2));
}

exports.translate = main;
exports.createBinary = createBinary;
exports.createReadableStream = createReadableStream;
exports.getStdLib = getStdLib;