"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerilatorDiagnostics = void 0;
const vscode_languageserver_1 = require("vscode-languageserver");
const genutils_1 = require("./genutils");
const child = require("child_process");
const path = require('path');
class VerilatorDiagnostics {
    constructor(indexer) {
        this._command = "";
        this._defines = [];
        this._optionsFile = "";
        this._alreadyRunning = new Map();
        this._fileWaiting = new Map();
        this._freeTmpFileNums = [];
        this._totalTmpFileNums = 0;
        this._indexer = indexer;
        this._tmpDir = genutils_1.getTmpDirSync();
    }
    setCommand(cmd) {
        this._command = cmd;
    }
    setOptionsFile(file) {
        this._optionsFile = file;
    }
    setDefines(defines) {
        this._defines = defines || [];
    }
    _getFreeTmpFileNum() {
        if (this._freeTmpFileNums.length <= 0) {
            this._freeTmpFileNums.push(this._totalTmpFileNums++);
        }
        return this._freeTmpFileNums.shift();
    }
    _lintImmediate(file, text) {
        let _kill = () => {
            let [proc, statusRef] = this._alreadyRunning.get(file);
            statusRef[0] = false;
            proc.kill();
        };
        if (this._alreadyRunning.has(file)) {
            //ConnectionLogger.log(`DEBUG: Killing already running command to start a new one`);
            _kill();
        }
        return new Promise((resolve) => {
            let actFile = text == undefined ? file : path.join(this._tmpDir.name, "sources", file);
            let optionsFile = this._optionsFile;
            let vcTmpFileNum;
            if (text != undefined) {
                genutils_1.fsWriteFileSync(actFile, text);
                // if file write takes too long and another process started in the interim
                if (this._alreadyRunning.has(file)) {
                    //ConnectionLogger.log(`DEBUG: Killing already running command to start a new one`);
                    _kill();
                }
                if (this._indexer.fileHasPkg(file)) {
                    let vcFileContent = this._indexer.getOptionsFileContent()
                        .map(line => { return (line == file) ? actFile : line; })
                        .join('\n');
                    vcTmpFileNum = this._getFreeTmpFileNum();
                    let tmpVcFile = path.join(this._tmpDir.name, "vcfiles", `lint${vcTmpFileNum}.vc`);
                    genutils_1.fsWriteFileSync(tmpVcFile, vcFileContent);
                    optionsFile = tmpVcFile;
                    // if file write takes too long and another process started in the interim
                    if (this._alreadyRunning.has(file)) {
                        //ConnectionLogger.log(`DEBUG: Killing already running command to start a new one`);
                        _kill();
                    }
                }
            }
            let definesArg = this._defines.length > 0 ? this._defines.map(d => ` +define+${d}`).join('') : "";
            let optionsFileArg = optionsFile ? ' -f ' + optionsFile : "";
            let actFileArg = (this._indexer.fileHasPkg(file)) ? "" : " " + actFile;
            let command = this._command + definesArg + optionsFileArg + actFileArg;
            let statusRef = [true];
            //ConnectionLogger.log(`DEBUG: verilator command ${command}`);
            this._alreadyRunning.set(file, [
                child.exec(command, (error, stdout, stderr) => {
                    if (optionsFile != this._optionsFile) {
                        this._freeTmpFileNums.push(vcTmpFileNum);
                    }
                    if (statusRef[0]) {
                        this._alreadyRunning.delete(file);
                        resolve(this._parseDiagnostics(error, stdout, stderr, actFile));
                    }
                    else {
                        resolve([]);
                    }
                }),
                statusRef
            ]);
        });
    }
    lint(file, text) {
        try {
            if (text == undefined) {
                return this._lintImmediate(file, text)
                    .catch(error => {
                    genutils_1.ConnectionLogger.error(error);
                    return [];
                });
            }
            else {
                if (this._fileWaiting.has(file)) {
                    let [waitTimer, resolver] = this._fileWaiting.get(file);
                    clearTimeout(waitTimer);
                    resolver(false);
                }
                return new Promise(resolve => {
                    this._fileWaiting.set(file, [setTimeout(resolve, 1000, true), resolve]);
                }).then((success) => {
                    if (!!success) {
                        this._fileWaiting.delete(file);
                        return this._lintImmediate(file, text);
                    }
                    return [];
                }).catch(error => {
                    genutils_1.ConnectionLogger.error(error);
                    return [];
                });
            }
        }
        catch (error) {
            genutils_1.ConnectionLogger.error(error);
            return Promise.resolve([]);
        }
    }
    _parseDiagnostics(error, stdout, stderr, file) {
        let diagnostics = [];
        let lines = stderr.split(/\r?\n/g);
        // RegExp expression for matching Verilator messages
        // Group 1: Severity
        // Group 2: Type (optional)
        // Group 3: Filename
        // Group 4: Line number
        // Group 5: Column number (optional)
        // Group 6: Message
        let regex = new RegExp(String.raw `%(Error|Warning)(-[A-Z0-9_]+)?: (` + file.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + String.raw `):(\d+):(?:(\d+):)? (.*)`, 'i');
        // Parse output lines
        lines.forEach((line, i) => {
            let terms = line.match(regex);
            if (terms != null) {
                let severity = this._getSeverity(terms[1]);
                let message = "";
                let lineNum = parseInt(terms[4]) - 1;
                let colNum = 0;
                if (terms[5]) {
                    colNum = parseInt(terms[5]) - 1;
                }
                message = terms[6];
                for (let whitelistedMessage of VerilatorDiagnostics._whitelistedMessages) {
                    if (message.search(whitelistedMessage) >= 0) {
                        return;
                    }
                }
                if ((lineNum != NaN) && (colNum != NaN)) {
                    diagnostics.push({
                        severity: severity,
                        range: vscode_languageserver_1.Range.create(lineNum, colNum, lineNum, Number.MAX_VALUE),
                        message: message,
                        code: 'verilator',
                        source: 'verilator'
                    });
                }
            }
        });
        return diagnostics;
    }
    _getSeverity(severityString) {
        let result = vscode_languageserver_1.DiagnosticSeverity.Information;
        if (severityString.startsWith('Error')) {
            result = vscode_languageserver_1.DiagnosticSeverity.Error;
        }
        else if (severityString.startsWith('Warning')) {
            result = vscode_languageserver_1.DiagnosticSeverity.Warning;
        }
        return result;
    }
    cleanupTmpFiles() {
        this._tmpDir.removeCallback();
    }
}
exports.VerilatorDiagnostics = VerilatorDiagnostics;
VerilatorDiagnostics._whitelistedMessages = [
    /Unsupported: Interfaced port on top level module/i
];
