"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const util = tslib_1.__importStar(require("util"));
const errors_1 = require("../errors");
const console_styles_1 = require("./console-styles");
const spinner_1 = tslib_1.__importDefault(require("./spinner"));
const stat_1 = require("./stat");
class DigitHistory {
    constructor(d = 0) {
        this.d = d;
    }
}
class ConsoleReporterTextSource {
    constructor() {
        this.running = false;
        this.message = "";
        this.cpuStat = new stat_1.CpuStats();
        this.memStat = new stat_1.MemStats();
        this.dCPU = new DigitHistory(3);
        this.dProg = new DigitHistory(2);
        this.dFin = new DigitHistory(0);
        this.dExec = new DigitHistory(0);
        this.showProgress = process.stderr.columns && process.stderr.columns >= 120;
    }
    chromaPad(value, dh, bright, grayOut) {
        const valStr = (value || 0).toFixed(0);
        if (valStr.length > dh.d)
            dh.d = valStr.length;
        let pad = "", padDigits = 0;
        while (valStr.length + padDigits < dh.d) {
            pad += grayOut("0");
            padDigits += 1;
        }
        return pad + bright(valStr);
    }
    progressBar(usage, size, bright, dark) {
        let bar = "";
        for (let s = 0; s < size; s++) {
            const thr = (s + 1 / 2) / size;
            bar += usage > thr ? bright("█") : dark("▒");
        }
        return bar;
    }
    percentage(usage, dh, bright, dark, grayOut, strict1) {
        const p = strict1 ? Math.floor(usage * 100) : Math.round(usage * 100);
        return this.chromaPad(p, dh, bright, grayOut) + dark("%");
    }
    resourceUsage(name, usage, dh) {
        return (chalk_1.default.greenBright(name) +
            chalk_1.default.gray(":") +
            this.percentage(usage, dh, chalk_1.default.greenBright, chalk_1.default.green, chalk_1.default.gray) +
            (this.showProgress
                ? " " + this.progressBar(usage, 10, chalk_1.default.greenBright, chalk_1.default.green)
                : ""));
    }
    resourceText() {
        return (" | " +
            this.resourceUsage("CPU", this.cpuStat.getTotalCpuUsage(), this.dCPU) +
            " - " +
            this.resourceUsage("RAM", this.memStat.getMemoryUsage(), this.dCPU));
    }
    standardProgress(finished, executing, awaiting) {
        const progress = finished / (finished + executing + awaiting) || 0;
        let r = chalk_1.default.cyan("Complete ") +
            this.chromaPad(finished, this.dFin, chalk_1.default.cyanBright, chalk_1.default.gray) +
            " - " +
            chalk_1.default.yellow("Executing ") +
            this.chromaPad(executing, this.dExec, chalk_1.default.yellowBright, chalk_1.default.gray) +
            " - " +
            chalk_1.default.cyan("Pending ") +
            this.chromaPad(awaiting, this.dFin, chalk_1.default.cyanBright, chalk_1.default.gray) +
            chalk_1.default.cyan(" (") +
            this.percentage(progress, this.dProg, chalk_1.default.cyanBright, chalk_1.default.cyan, chalk_1.default.gray, true) +
            chalk_1.default.cyan(")");
        if (this.showProgress) {
            r += " " + this.progressBar(progress, 20, chalk_1.default.cyanBright, chalk_1.default.cyan);
        }
        return r;
    }
    getText() {
        return this.message + (this.running ? this.resourceText() : "");
    }
}
class ConsoleReporter {
    constructor(verbosity, parent) {
        this.activeTargets = new Set();
        this.finishedTargets = new Set();
        this.reportedErrors = new Set();
        this.targetHalts = new Set();
        this.verbosity = verbosity;
        if (!parent) {
            this.spinner = new spinner_1.default();
            this.text = new ConsoleReporterTextSource();
            this.spinner.textSource = this.text;
        }
        else {
            this.spinner = parent.spinner;
            this.text = parent.text;
        }
        this.columns = (process.stdout.columns || 80) - 2;
    }
    start() {
        this.activeTargets = new Set();
        this.finishedTargets = new Set();
        this.reportedErrors = new Set();
        this.spinner.start();
        this.text.running = true;
    }
    beforeOutput() {
        this.spinner.pause();
    }
    afterOutput() {
        this.spinner.start();
    }
    end(wrong) {
        if (wrong) {
            this.text.message = chalk_1.default.red("Building process terminated.");
        }
        else {
            this.text.message = chalk_1.default.cyan(`Finished with ${this.finishedTargets.size} targets updated.`);
        }
        this.text.running = false;
        this.spinner.stop();
    }
    progressMessage() {
        const executing = Math.max(0, Math.min(this.activeTargets.size, this.activeTargets.size - this.targetHalts.size));
        const awaiting = this.activeTargets.size - executing;
        const finished = this.finishedTargets.size;
        this.text.message = this.text.standardProgress(finished, executing, awaiting);
    }
    targetStart(id) {
        if (this.activeTargets.has(id) || this.finishedTargets.has(id))
            return;
        this.info("Start building", id);
        this.activeTargets.add(id);
        this.progressMessage();
    }
    targetSkip(id) {
        this.finishedTargets.add(id);
        this.progressMessage();
    }
    targetEnd(id) {
        if (!this.activeTargets.has(id))
            return;
        this.info("Finish building", id);
        this.activeTargets.delete(id);
        this.targetHalts.delete(id);
        this.finishedTargets.add(id);
        this.progressMessage();
    }
    systemError(err) {
        if (this.reportedErrors.has(err))
            return;
        const ext = (0, errors_1.getExtErrorProps)(err);
        if (ext && ext.hide)
            return;
        if (ext && ext.system) {
            this.error(err.message);
        }
        else {
            this.error(util.inspect(err));
        }
        this.reportedErrors.add(err);
    }
    targetError(id, err) {
        if (this.reportedErrors.has(err))
            return;
        const ext = (0, errors_1.getExtErrorProps)(err);
        if (ext && ext.hide)
            return;
        if (ext && ext.system) {
            this.error(err.message);
        }
        else {
            this.error(`Unhandled exception when building "${id}":\n`, chalk_1.default.gray(util.inspect(err)));
        }
        this.reportedErrors.add(err);
    }
    targetHalt(name) {
        this.targetHalts.add(name);
        this.progressMessage();
    }
    targetUnHalt(name) {
        this.targetHalts.delete(name);
        this.progressMessage();
    }
    rawLog(...line) {
        this.beforeOutput();
        console.error(...line);
        this.afterOutput();
    }
    redirectStdout(line) {
        if (!line.length)
            return;
        this.beforeOutput();
        process.stdout.write(line);
        this.afterOutput();
    }
    redirectStderr(line) {
        if (!line.length)
            return;
        this.beforeOutput();
        process.stderr.write(line);
        this.afterOutput();
    }
    directive(color, symbol, word) {
        return chalk_1.default[color](symbol + (word ? " " + chalk_1.default.underline.bold(word) : ""));
    }
    extractFirstLine(lines, len, style) {
        let s = "";
        let lengthSofar = 0;
        for (let j = 0; j < lines.length; j++) {
            let line = lines[j];
            const joiner = style.joiner(j);
            let kShift = 0;
            if (joiner) {
                line = [joiner, ...line];
                kShift = 1;
            }
            for (let k = 0; k < line.length; k++) {
                const term = line[k];
                let segText = style.escape(term, j, k).replace(/[\r\n]+/g, " ");
                if (this.verbosity < 8 && lengthSofar + segText.length >= len) {
                    const remainingLength = len - lengthSofar - style.trail.length;
                    if (remainingLength > 0) {
                        const segText1 = segText.slice(0, remainingLength);
                        s +=
                            style.stylize(term, j, k - kShift, segText, segText1) +
                                style.styledTrail;
                    }
                    return s;
                }
                else {
                    s += style.stylize(term, j, k - kShift, segText, segText) + " ";
                    lengthSofar += segText.length + 1;
                }
            }
        }
        return s;
    }
    getStyle(s) {
        switch (s) {
            case "command":
                return console_styles_1.commandStylizer;
            case "jsCall":
                return console_styles_1.jsCallStyle;
            default:
                return console_styles_1.defaultStylizer;
        }
    }
    actions(commands, style) {
        if (this.verbosity < 6)
            return;
        this.rawLog(this.directive("blueBright", "♦", ""), this.extractFirstLine(commands, this.columns, this.getStyle(style || "")));
    }
    directiveLogging(directive, color, ...args) {
        const [prefix, postfix] = color
            ? chalk_1.default[color]("<<##BEGIN##>>").split("<<##BEGIN##>>")
            : ["", ""];
        this.beforeOutput();
        if (directive) {
            process.stderr.write(directive + " " + prefix);
        }
        else {
            process.stderr.write(prefix);
        }
        console.error(...args);
        process.stderr.write(postfix);
        this.afterOutput();
    }
    debug(...line) {
        if (this.verbosity < 8)
            return;
        this.directiveLogging(this.directive("gray", "·", "Debug"), "gray", ...line);
    }
    info(...line) {
        if (this.verbosity < 7)
            return;
        this.directiveLogging(this.directive("gray", "·", ""), "gray", ...line);
    }
    echo(...line) {
        if (this.verbosity < 5)
            return;
        this.directiveLogging("", "", ...line);
    }
    note(...line) {
        if (this.verbosity < 5)
            return;
        this.directiveLogging(this.directive("cyan", "●", "Note"), "", ...line);
    }
    warn(...line) {
        if (this.verbosity < 4)
            return;
        this.directiveLogging(this.directive("yellow", "!", "Warning"), "", ...line);
    }
    success(...line) {
        if (this.verbosity < 3)
            return;
        this.directiveLogging(this.directive("green", "√", "Success"), "", ...line);
    }
    fail(...line) {
        if (this.verbosity < 3)
            return;
        this.directiveLogging(this.directive("red", "×", "Fail"), "", ...line);
    }
    error(...line) {
        if (this.verbosity < 2)
            return;
        this.directiveLogging(this.directive("red", "×", "Error"), "", ...line);
    }
    fatal(...line) {
        if (this.verbosity < 1)
            return;
        this.directiveLogging(this.directive("red", "×", "Fatal"), "", ...line);
    }
}
exports.default = ConsoleReporter;
