"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChildProcess = void 0;
const child_process = require("child_process");
const path_1 = require("path");
const events_1 = require("events");
const yok_1 = require("./yok");
class ChildProcess extends events_1.EventEmitter {
    constructor($logger, $errors) {
        super();
        this.$logger = $logger;
        this.$errors = $errors;
    }
    exec(command, options, execOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                const callback = (error, stdout, stderr) => {
                    this.$logger.trace("Exec %s \n stdout: %s \n stderr: %s", command, stdout.toString(), stderr.toString());
                    if (error) {
                        reject(error);
                    }
                    else {
                        const output = execOptions && execOptions.showStderr ? { stdout, stderr } : stdout;
                        resolve(output);
                    }
                };
                if (options) {
                    child_process.exec(command, options, callback);
                }
                else {
                    child_process.exec(command, callback);
                }
            });
        });
    }
    execFile(command, args) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.trace("execFile: %s %s", command, this.getArgumentsAsQuotedString(args));
            return new Promise((resolve, reject) => {
                child_process.execFile(command, args, (error, stdout) => {
                    if (error) {
                        reject(error);
                    }
                    else {
                        resolve(stdout);
                    }
                });
            });
        });
    }
    spawn(command, args, options) {
        var _a;
        if (command.charAt(0) === ".") {
            const cwd = (_a = options === null || options === void 0 ? void 0 : options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
            command = (0, path_1.resolve)(cwd, command);
        }
        this.$logger.trace("spawn: %s %s", command, this.getArgumentsAsQuotedString(args));
        return child_process.spawn(command, args, options);
    }
    fork(modulePath, args, options) {
        this.$logger.trace("fork: %s %s", modulePath, this.getArgumentsAsQuotedString(args));
        return child_process.fork(modulePath, args, options);
    }
    spawnFromEvent(command, args, event, options, spawnFromEventOptions) {
        return new Promise((resolve, reject) => {
            const childProcess = this.spawn(command, args, options);
            let isResolved = false;
            let capturedOut = "";
            let capturedErr = "";
            let killTimer = null;
            if (spawnFromEventOptions && spawnFromEventOptions.timeout) {
                this.$logger.trace(`Setting maximum time for execution of current child process to ${spawnFromEventOptions.timeout}`);
                killTimer = setTimeout(() => {
                    this.$logger.trace(`Sending SIGTERM to current child process as maximum time for execution ${spawnFromEventOptions.timeout} had passed.`);
                    childProcess.kill("SIGTERM");
                }, spawnFromEventOptions.timeout);
            }
            if (childProcess.stdout) {
                childProcess.stdout.on("data", (data) => {
                    if (spawnFromEventOptions &&
                        spawnFromEventOptions.emitOptions &&
                        spawnFromEventOptions.emitOptions.eventName) {
                        this.emit(spawnFromEventOptions.emitOptions.eventName, {
                            data,
                            pipe: "stdout",
                        });
                    }
                    capturedOut += data;
                });
            }
            if (childProcess.stderr) {
                childProcess.stderr.on("data", (data) => {
                    if (spawnFromEventOptions &&
                        spawnFromEventOptions.emitOptions &&
                        spawnFromEventOptions.emitOptions.eventName) {
                        this.emit(spawnFromEventOptions.emitOptions.eventName, {
                            data,
                            pipe: "stderr",
                        });
                    }
                    capturedErr += data;
                });
            }
            childProcess.on(event, (arg) => {
                const exitCode = typeof arg === "number" ? arg : arg && arg.code;
                const result = {
                    stdout: capturedOut,
                    stderr: capturedErr,
                    exitCode: exitCode,
                };
                const clearKillTimer = () => {
                    if (killTimer) {
                        clearTimeout(killTimer);
                    }
                };
                const resolveAction = () => {
                    isResolved = true;
                    resolve(result);
                    clearKillTimer();
                };
                if (spawnFromEventOptions &&
                    spawnFromEventOptions.throwError === false) {
                    if (!isResolved) {
                        this.$logger.trace("Result when throw error is false:");
                        this.$logger.trace(result);
                        resolveAction();
                    }
                }
                else {
                    if (exitCode === 0) {
                        resolveAction();
                    }
                    else {
                        let errorMessage = `Command ${command} failed with exit code ${exitCode}`;
                        if (capturedErr) {
                            errorMessage += ` Error output: \n ${capturedErr}`;
                        }
                        if (!isResolved) {
                            isResolved = true;
                            reject(new Error(errorMessage));
                            clearKillTimer();
                        }
                    }
                }
            });
            childProcess.once("error", (err) => {
                if (!isResolved) {
                    if (spawnFromEventOptions &&
                        spawnFromEventOptions.throwError === false) {
                        const result = {
                            stdout: capturedOut,
                            stderr: err.message,
                            exitCode: err.code,
                        };
                        isResolved = true;
                        resolve(result);
                    }
                    else {
                        isResolved = true;
                        reject(err);
                    }
                }
            });
        });
    }
    trySpawnFromCloseEvent(command, args, options, spawnFromEventOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const childProcessResult = yield this.spawnFromEvent(command, args, "close", options, spawnFromEventOptions);
                return childProcessResult;
            }
            catch (err) {
                this.$logger.trace(`Error from trySpawnFromCloseEvent method. More info: ${err}`);
                return Promise.resolve({
                    stderr: err && err.message ? err.message : err,
                    stdout: null,
                    exitCode: -1,
                });
            }
        });
    }
    tryExecuteApplication(command, args, event, errorMessage, condition) {
        return __awaiter(this, void 0, void 0, function* () {
            const childProcess = yield this.tryExecuteApplicationCore(command, args, event, errorMessage);
            if (condition && condition(childProcess)) {
                this.$errors.fail(errorMessage);
            }
        });
    }
    tryExecuteApplicationCore(command, args, event, errorMessage) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return this.spawnFromEvent(command, args, event, undefined, {
                    throwError: false,
                });
            }
            catch (e) {
                const message = e.code === "ENOENT" ? errorMessage : e.message;
                this.$errors.fail(message);
            }
        });
    }
    getArgumentsAsQuotedString(args) {
        return (args && args.length && args.map((argument) => `"${argument}"`).join(" "));
    }
}
exports.ChildProcess = ChildProcess;
yok_1.injector.register("childProcess", ChildProcess);
