"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerilogDiagnostics = void 0;
const node_1 = require("vscode-languageserver/node");
const genutils_1 = require("./genutils");
const path = require('path');
function getVerilatorSeverity(severityString) {
    let result = node_1.DiagnosticSeverity.Information;
    if (severityString.startsWith('Error')) {
        result = node_1.DiagnosticSeverity.Error;
    }
    else if (severityString.startsWith('Warning')) {
        result = node_1.DiagnosticSeverity.Warning;
    }
    return result;
}
function parseVerilatorDiagnostics(stdout, stderr, file, whitelistedMessages = []) {
    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, '\\$&')}):(\d+):(?:(\d+):)? (.*)`, 'i');
    // Parse output lines
    for (let i = 0; i < lines.length; ++i) {
        const line = lines[i];
        let terms = line.match(regex);
        if (terms != null) {
            let severity = getVerilatorSeverity(terms[1]);
            let message = "";
            let lineNum = parseInt(terms[4]) - 1;
            let colNum = 0;
            let colNumEnd = 0;
            if (terms[5]) {
                colNum = parseInt(terms[5]) - 1;
            }
            message = terms[6];
            let messageWhiteListed = false;
            for (let whitelistedMessage of whitelistedMessages) {
                if (message.search(whitelistedMessage) >= 0) {
                    messageWhiteListed = true;
                    break;
                }
            }
            if (messageWhiteListed) {
                continue;
            }
            // Match the ^~~~~~~ under the error message
            if (/\s*\^~+/.exec(lines[i + 2])) {
                let startColNum = lines[i + 2].indexOf('|') + 2;
                colNum = lines[i + 2].indexOf('^');
                colNum = colNum > startColNum ? colNum - startColNum : colNum;
                colNumEnd = lines[i + 2].lastIndexOf('~');
                colNumEnd = colNumEnd > startColNum ? colNumEnd - startColNum : colNumEnd;
                i += 2;
            }
            if ((lineNum != NaN) && (colNum != NaN)) {
                diagnostics.push({
                    severity: severity,
                    range: node_1.Range.create(lineNum, colNum, lineNum, colNumEnd < colNum ? colNum : colNumEnd),
                    message: message,
                    code: 'verilator',
                    source: 'verilator'
                });
            }
        }
    }
    return diagnostics;
}
function getIcarusSeverity(severityString, message) {
    let result = node_1.DiagnosticSeverity.Information;
    if (severityString === 'error' || message === 'syntax error') {
        result = node_1.DiagnosticSeverity.Error;
    }
    else if (severityString === 'warning') {
        result = node_1.DiagnosticSeverity.Warning;
    }
    return result;
}
function parseIcarusDiagnostics(stdout, stderr, file, whitelistedMessages = []) {
    let diagnostics = [];
    let lines = stderr.split(/\r?\n/g);
    // RegExp expression for matching Icarus messages
    // Group 1: Filename
    // Group 2: Line number
    // Group 3: Severity (optional)
    // Group 4: Message
    let regex = new RegExp(String.raw `(${file.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}):(\d+):(?: (error|warning):)? (.*)`, 'i');
    // Parse output lines
    for (let i = 0; i < lines.length; ++i) {
        const line = lines[i];
        let terms = line.match(regex);
        if (terms != null) {
            let message = terms[4];
            let severity = getIcarusSeverity(terms[3], message);
            let lineNum = parseInt(terms[2]) - 1;
            let messageWhiteListed = false;
            for (let whitelistedMessage of whitelistedMessages) {
                if (message.search(whitelistedMessage) >= 0) {
                    messageWhiteListed = true;
                    break;
                }
            }
            if (messageWhiteListed) {
                continue;
            }
            if (lineNum != NaN) {
                diagnostics.push({
                    severity: severity,
                    range: node_1.Range.create(lineNum, 0, lineNum, 0),
                    message: message,
                    code: 'iverilog',
                    source: 'iverilog'
                });
            }
        }
    }
    return diagnostics;
}
class VerilogDiagnostics {
    constructor(indexer) {
        this._linter = 'icarus';
        this._command = "";
        this._defines = [];
        this._optionsFile = "";
        this._childProcMngr = new genutils_1.ChildProcManager();
        this._delayedCaller = new genutils_1.DelayedCaller();
        this._whitelistedMessages = [];
        this._indexer = indexer;
    }
    setCommand(cmd) {
        this._command = cmd;
    }
    setLinter(linter) {
        this._linter = linter;
    }
    setOptionsFile(file) {
        this._optionsFile = file;
    }
    setWhitelistedMessages(msgs) {
        this._whitelistedMessages = msgs.map(m => new RegExp(m));
    }
    setDefines(defines) {
        this._defines = defines || [];
    }
    _lintImmediate(file, text) {
        this._childProcMngr.kill(file);
        return new Promise((resolve, reject) => {
            let fileWithoutRoot = file.slice(path.parse(file).root.length);
            let actFile = text == undefined ? file : genutils_1.tmpFileManager.getTmpFilePath("sources", fileWithoutRoot);
            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
                this._childProcMngr.kill(file);
                if (this._indexer.fileHasPkg(file)) {
                    let vcFileContent = this._indexer.getOptionsFileContent()
                        .map(line => { return (line.endsWith(file)) ? line.slice(0, line.length - file.length) + actFile : line; })
                        .join('\n');
                    vcTmpFileNum = genutils_1.tmpFileManager.getFreeTmpFileNum("vcfiles");
                    let tmpVcFile = genutils_1.tmpFileManager.getTmpFilePath("vcfiles", `lint${vcTmpFileNum}.vc`);
                    genutils_1.fsWriteFileSync(tmpVcFile, vcFileContent);
                    optionsFile = tmpVcFile;
                    // if file write takes too long and another process started in the interim
                    this._childProcMngr.kill(file);
                }
            }
            let definesArg = this._defines.length > 0 ? this._defines.map(d => ` +define+${d}`).join('') : "";
            let optionsFileArg = optionsFile ? ' -f ' + optionsFile : "";
            let actFileArg = this._indexer.isMustSrcFile(file) ? "" : " " + actFile;
            let command = this._command + definesArg + optionsFileArg + actFileArg;
            //ConnectionLogger.log(`DEBUG: verilator command ${command}`);
            this._childProcMngr.run(file, command, (status, error, stdout, stderr) => {
                if (optionsFile != this._optionsFile) {
                    genutils_1.tmpFileManager.returnFreeTmpFileNum("vcfiles", vcTmpFileNum);
                }
                if (status) {
                    switch (this._linter) {
                        case "icarus":
                            resolve(parseIcarusDiagnostics(stdout, stderr, actFile, this._whitelistedMessages));
                            break;
                        case "verilator":
                            resolve(parseVerilatorDiagnostics(stdout, stderr, actFile, this._whitelistedMessages));
                            break;
                        default:
                            reject(new Error(`Unknown linter ${this._linter}`));
                            break;
                    }
                }
                else {
                    resolve([]);
                }
            });
        });
    }
    lint(file, text) {
        try {
            if (text == undefined) {
                return this._lintImmediate(file, text)
                    .catch(error => {
                    genutils_1.ConnectionLogger.error(error);
                    return [];
                });
            }
            else {
                return this._delayedCaller.run(file, (success) => {
                    if (!!success) {
                        return this._lintImmediate(file, text);
                    }
                    return [];
                }, (error) => {
                    genutils_1.ConnectionLogger.error(error);
                    return [];
                });
            }
        }
        catch (error) {
            genutils_1.ConnectionLogger.error(error);
            return Promise.resolve([]);
        }
    }
    cleanupTmpFiles() {
        genutils_1.tmpFileManager.cleanupTmpFiles();
    }
}
exports.VerilogDiagnostics = VerilogDiagnostics;
