"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SystemVerilogSignatureHelpProvider = void 0;
const svsymbol_1 = require("./svsymbol");
const svutils_1 = require("./svutils");
const genutils_1 = require("./genutils");
var ScopeType;
(function (ScopeType) {
    ScopeType[ScopeType["None"] = 0] = "None";
    ScopeType[ScopeType["InstanceParamsPortsNamed"] = 1] = "InstanceParamsPortsNamed";
    ScopeType[ScopeType["InstanceParamsOrdered"] = 2] = "InstanceParamsOrdered";
    ScopeType[ScopeType["InstancePortsOrdered"] = 3] = "InstancePortsOrdered";
    ScopeType[ScopeType["Macro"] = 4] = "Macro";
    ScopeType[ScopeType["RoutineNamed"] = 5] = "RoutineNamed";
    ScopeType[ScopeType["RoutineOrdered"] = 6] = "RoutineOrdered";
    ScopeType[ScopeType["SystemTask"] = 7] = "SystemTask";
})(ScopeType || (ScopeType = {}));
const systemTasksInfo = new Map([
    [/(?:display|write)[boh]?/, [[["format", "format string"], ["...", "arguments"]], 0]],
]);
class SystemVerilogSignatureHelpProvider {
    constructor(indexer) {
        this._indexer = indexer;
    }
    _getScopeType(tokenNum, svtokens) {
        let scopes = svtokens[tokenNum].scopes;
        if (scopes.length > 0) {
            let topScope = scopes[scopes.length - 1];
            if (topScope == "system.task.systemverilog") {
                return [ScopeType.SystemTask, tokenNum];
            }
            else if (topScope == "macro.call.systemverilog") {
                return [ScopeType.Macro, tokenNum];
            }
            for (let i = scopes.length - 1; i >= 0; i--) {
                if ((scopes[i] == "system.args.systemverilog") ||
                    (scopes[i] == "macro.args.systemverilog")) {
                    let startTokenNum = tokenNum - 1;
                    for (; startTokenNum >= 0; startTokenNum--) {
                        if ((svtokens[startTokenNum].scopes.length >= i) &&
                            ((svtokens[startTokenNum].scopes[i] == "system.task.systemverilog") ||
                                (svtokens[startTokenNum].scopes[i] == "macro.call.systemverilog"))) {
                            break;
                        }
                    }
                    if (startTokenNum >= 0) {
                        return [scopes[i] == "system.args.systemverilog" ? ScopeType.SystemTask : ScopeType.Macro, startTokenNum];
                    }
                    else {
                        return [ScopeType.None, undefined];
                    }
                }
                else if ((scopes[i] == "parantheses.begin.systemverilog") ||
                    (scopes[i] == "parantheses.block.systemverilog")) {
                    let startTokenNum;
                    if (scopes[i] == "parantheses.begin.systemverilog") {
                        startTokenNum = tokenNum;
                    }
                    else {
                        for (let j = tokenNum - 1; j >= 0; j--) {
                            if (svtokens[j].scopes.length <= i) {
                                break;
                            }
                            else if (svtokens[j].scopes[i] == "parantheses.begin.systemverilog") {
                                startTokenNum = j;
                                break;
                            }
                        }
                    }
                    if (startTokenNum != undefined) {
                        let hashOpenParenFound = false;
                        for (let j = startTokenNum - 1; j >= 0; j--) {
                            let scope = svtokens[j].scopes[svtokens[j].scopes.length - 1];
                            if (scope.startsWith("identifier.")) {
                                if (svutils_1.SystemVerilogUtils.keywordsList.has(svtokens[j].text)) {
                                    break;
                                }
                                else if ((svtokens[j].scopes.length < 2) ||
                                    (svtokens[j].scopes[svtokens[j].scopes.length - 2] == "container.header.systemverilog") ||
                                    (svtokens[j].scopes[svtokens[j].scopes.length - 2] == "routine.header.systemverilog")) {
                                    break;
                                }
                                else if (scope == "identifier.scoped.systemverilog") {
                                    return [ScopeType.RoutineOrdered, j];
                                }
                                else {
                                    let namedArg = false;
                                    for (let k = j - 1; k >= 0; k--) {
                                        if (svtokens[k].text == ".") {
                                            namedArg = true;
                                            break;
                                        }
                                        else if (!svtokens[k].scopes[svtokens[k].scopes.length - 1].startsWith("comment.") &&
                                            (svtokens[k].scopes[svtokens[k].scopes.length - 1] != "meta.whitespace.systemverilog")) {
                                            break;
                                        }
                                    }
                                    for (let k = 0; k < svtokens[j].scopes.length; k++) {
                                        if (!svtokens[j].scopes[k].startsWith("identifier.") &&
                                            (svtokens[j].scopes[k] != "parantheses.block.systemverilog") &&
                                            (svtokens[j].scopes[k] != "case.body.systemverilog") &&
                                            (svtokens[j].scopes[k] != "begin.block.systemverilog") &&
                                            (svtokens[j].scopes[k] != "generate.block.systemverilog") &&
                                            (svtokens[j].scopes[k] != "source.systemverilog")) {
                                            return [namedArg ? ScopeType.RoutineNamed : ScopeType.RoutineOrdered, j];
                                        }
                                    }
                                    if (namedArg) {
                                        return [ScopeType.InstanceParamsPortsNamed, j];
                                    }
                                    if (hashOpenParenFound) {
                                        return [ScopeType.InstanceParamsOrdered, j];
                                    }
                                    let scopeDepth = svtokens[j].scopes.length - 1;
                                    for (let k = j - 1; k >= 0; k--) {
                                        if (svtokens[k].scopes[scopeDepth].startsWith("identifier.")) {
                                            return [ScopeType.InstancePortsOrdered, k];
                                        }
                                    }
                                }
                            }
                            else if (!scope.startsWith("comment.") && (scope != "meta.whitespace.systemverilog")) {
                                if (!hashOpenParenFound && (svtokens[j].text == "#")) {
                                    hashOpenParenFound = true;
                                }
                                else {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        return [ScopeType.None, undefined];
    }
    _getArgNum(scopeType, startTokenNum, tokenNum, svtokens) {
        let scopeDepth = svtokens[startTokenNum].scopes.length;
        if (scopeType == ScopeType.InstanceParamsPortsNamed) {
            scopeDepth--;
        }
        let argNum = 0;
        for (let i = startTokenNum + 1; i <= tokenNum; i++) {
            if ((svtokens[i].scopes.length > scopeDepth) &&
                (svtokens[i].scopes[scopeDepth] == "operator.comma.systemverilog")) {
                argNum++;
            }
        }
        return argNum;
    }
    getSignatures(document, line, character) {
        let nullSignatureHelp = {
            signatures: [],
            activeSignature: null,
            activeParameter: null
        };
        try {
            const svtokens = this._indexer.getSystemVerilogCompletionTokens(document.uri);
            const tokenNum = this._indexer.getSystemVerilogCompletionTokenNumber(document, line, character)[1];
            const svtoken = (tokenNum > 0) && (tokenNum < svtokens.length) ? svtokens[tokenNum] : null;
            if (!svtoken) {
                return nullSignatureHelp;
            }
            let scopeType;
            let startTokenNum;
            [scopeType, startTokenNum] = this._getScopeType(tokenNum, svtokens);
            if (scopeType == ScopeType.None) {
                return nullSignatureHelp;
            }
            let argNum = this._getArgNum(scopeType, startTokenNum, tokenNum, svtokens);
            if (scopeType == ScopeType.SystemTask) {
                let taskInfo;
                for (let task of systemTasksInfo.keys()) {
                    let regEx = new RegExp(String.raw `^\$${task.source}\s*\($`);
                    if (regEx.exec(svtokens[startTokenNum].text)) {
                        taskInfo = systemTasksInfo.get(task);
                        break;
                    }
                }
                if (taskInfo != undefined) {
                    let params = [];
                    for (let i = 0; i < taskInfo[0].length; i++) {
                        let label = taskInfo[0][i][0];
                        if (i == taskInfo[1]) {
                            label = `[${label}`;
                        }
                        if ((i == (taskInfo[0].length - 1)) &&
                            (taskInfo[1] < taskInfo[0].length)) {
                            label = `${label}]`;
                        }
                        params.push({
                            label: label,
                            documentation: taskInfo[0][i][1]
                        });
                    }
                    let signLabel = svtokens[startTokenNum].text.concat(params.map(param => { return param.label; }).join(', ')).concat(')');
                    return {
                        signatures: [
                            {
                                label: signLabel,
                                documentation: "system task",
                                parameters: params
                            }
                        ],
                        activeSignature: 0,
                        activeParameter: argNum >= params.length ? params.length - 1 : argNum
                    };
                }
            }
            else if (scopeType == ScopeType.Macro) {
                let macroName = svtokens[startTokenNum].text.slice(1, -1).trim();
                let macroFilesInfo = this._indexer.getMacros(document.uri, svtokens[startTokenNum].text.slice(1).replace(/\s*\($/, ""));
                if (macroFilesInfo.length > 0) {
                    let macroDefinition = macroFilesInfo[0][1][0].getDefinition(macroFilesInfo[0][0]);
                    let macroArgsStartRegEx = new RegExp(String.raw `^\s*\`define\s+${macroName}\s*\(`);
                    let macroArgsStart = macroArgsStartRegEx.exec(macroDefinition);
                    if (!macroArgsStart) {
                        return nullSignatureHelp;
                    }
                    let params = [];
                    let paramLabel = "";
                    let nestDepth = 0;
                    let charIndex = macroArgsStart[0].length;
                    for (; charIndex < macroDefinition.length; charIndex++) {
                        if (((macroDefinition[charIndex] == ",") || (macroDefinition[charIndex] == ")")) && (nestDepth == 0)) {
                            params.push({ label: paramLabel.trim() });
                            if (macroDefinition[charIndex] == ")") {
                                charIndex++;
                                break;
                            }
                            else {
                                paramLabel = "";
                            }
                        }
                        else {
                            paramLabel = paramLabel.concat(macroDefinition[charIndex]);
                            if ((macroDefinition[charIndex] == "(") ||
                                (macroDefinition[charIndex] == "[") ||
                                (macroDefinition[charIndex] == "{")) {
                                nestDepth++;
                            }
                            else if ((macroDefinition[charIndex] == ")") ||
                                (macroDefinition[charIndex] == "]") ||
                                (macroDefinition[charIndex] == "}")) {
                                nestDepth--;
                            }
                        }
                    }
                    let macroLabel = `\`${macroName}(`.concat(params.map(param => { return param.label; }).join(', ')).concat(')');
                    let macroDoc = macroDefinition.slice(charIndex).trim();
                    return {
                        signatures: [
                            {
                                label: macroLabel,
                                documentation: macroDoc,
                                parameters: params
                            }
                        ],
                        activeSignature: 0,
                        activeParameter: argNum >= params.length ? params.length - 1 : argNum
                    };
                }
            }
            else if (scopeType == ScopeType.RoutineNamed) {
                let scopeDepth = svtokens[startTokenNum].scopes.length - 2;
                if (scopeDepth <= 0) {
                    return nullSignatureHelp;
                }
                let routineToken;
                for (let i = startTokenNum - 1; i >= 0; i--) {
                    if (svtokens[i].scopes[scopeDepth].startsWith("identifier.")) {
                        routineToken = i;
                        break;
                    }
                }
                if (routineToken == undefined) {
                    return nullSignatureHelp;
                }
                let routineFile;
                let routineSymbol;
                let containerSymbolsInfo;
                [routineFile, routineSymbol, containerSymbolsInfo] = this._indexer.getContainerInfo(document.uri, svtokens[routineToken].text);
                if ((routineFile == undefined) || (routineSymbol == undefined) || (containerSymbolsInfo == undefined) ||
                    (containerSymbolsInfo.symbolsInfo == undefined)) {
                    return nullSignatureHelp;
                }
                let routineArgSymbols = containerSymbolsInfo.symbolsInfo;
                let symbols = routineArgSymbols.filter(sym => { return sym.name == svtokens[startTokenNum].text; });
                if (symbols.length <= 0) {
                    return nullSignatureHelp;
                }
                let label = `${svtokens[routineToken].text}(${routineArgSymbols.map(sym => sym.name).join(', ')})`;
                let definition = symbols[0].getDefinition(routineFile);
                return {
                    signatures: [
                        {
                            label: definition,
                            documentation: routineSymbol.getDefinition(routineFile),
                            parameters: []
                        }
                    ],
                    activeSignature: 0,
                    activeParameter: null
                };
            }
            else if (scopeType == ScopeType.RoutineOrdered) {
                let routineFile;
                let routineSymbol;
                let containerSymbolsInfo;
                [routineFile, routineSymbol, containerSymbolsInfo] = this._indexer.getContainerInfo(document.uri, svtokens[startTokenNum].text);
                //DBG ConnectionLogger.log(`DEBUG: HERE with ${svtoken.text} and ${scopeType} and ${svtokens[startTokenNum].text} and ${routineFile} and ${routineSymbol.name} and ${containerSymbolsInfo == undefined}`);
                if ((routineFile == undefined) || (routineSymbol == undefined) || (containerSymbolsInfo == undefined)) {
                    return nullSignatureHelp;
                }
                if (containerSymbolsInfo.symbolsInfo == undefined) {
                    return {
                        signatures: [
                            {
                                label: `${svtokens[startTokenNum].text}()`,
                                documentation: routineSymbol.getDefinition(routineFile),
                                parameters: []
                            }
                        ],
                        activeSignature: 0,
                        activeParameter: undefined
                    };
                }
                let routineArgSymbols = containerSymbolsInfo.symbolsInfo;
                let label = `${svtokens[startTokenNum].text}(${routineArgSymbols.map(sym => sym.name).join(', ')})`;
                let definitions = svsymbol_1.SystemVerilogSymbol.getDefinitions(routineFile, routineArgSymbols);
                let params = [];
                for (let i = 0; i < routineArgSymbols.length; i++) {
                    params.push({
                        label: routineArgSymbols[i].name,
                        documentation: definitions[i]
                    });
                }
                return {
                    signatures: [
                        {
                            label: label,
                            documentation: routineSymbol.getDefinition(routineFile),
                            parameters: params
                        }
                    ],
                    activeSignature: 0,
                    activeParameter: argNum >= params.length ? params.length - 1 : argNum
                };
            }
            else if (scopeType == ScopeType.InstanceParamsPortsNamed) {
                // Find the instance type
                let scopeDepth = svtokens[startTokenNum].scopes.length - 2;
                if (scopeDepth <= 0) {
                    return nullSignatureHelp;
                }
                let instTypeToken;
                let openParenFound = false;
                let hashOpenParenFound = false;
                let portScope = false;
                for (let i = startTokenNum - 1; i >= 0; i--) {
                    if (svtokens[i].scopes[scopeDepth].startsWith("identifier.")) {
                        if (portScope || hashOpenParenFound) {
                            instTypeToken = i;
                            break;
                        }
                        else if (openParenFound) {
                            portScope = true;
                        }
                    }
                    else if (!svtokens[i].scopes[svtokens[i].scopes.length - 1].startsWith("comment.") &&
                        (svtokens[i].scopes[svtokens[i].scopes.length - 1] != "meta.whitespace.systemverilog")) {
                        if (hashOpenParenFound) {
                            openParenFound = false;
                            hashOpenParenFound = false;
                        }
                        else if (openParenFound) {
                            if (svtokens[i].text == "#") {
                                hashOpenParenFound = true;
                            }
                            openParenFound = false;
                        }
                        else if (svtokens[i].text == "(") {
                            openParenFound = true;
                        }
                    }
                }
                if ((instTypeToken == undefined) || (!openParenFound && !hashOpenParenFound)) {
                    return nullSignatureHelp;
                }
                let symbols = (portScope ? this._indexer.getInstPorts(svtokens[instTypeToken].text)
                    : this._indexer.getInstParams(svtokens[instTypeToken].text)).filter(sym => { return sym.name == svtokens[startTokenNum].text; });
                if (symbols.length <= 0) {
                    return nullSignatureHelp;
                }
                let instTypeSymbol = this._indexer.getContainerSymbol(svtokens[instTypeToken].text);
                let instFilePath = this._indexer.getInstFilePath(svtokens[instTypeToken].text);
                let definition = symbols[0].getDefinition(instFilePath);
                return {
                    signatures: [
                        {
                            label: definition,
                            documentation: instTypeSymbol.getDefinition(instFilePath),
                            parameters: []
                        }
                    ],
                    activeSignature: 0,
                    activeParameter: null
                };
            }
            else if ((scopeType == ScopeType.InstanceParamsOrdered) ||
                (scopeType == ScopeType.InstancePortsOrdered)) {
                let symbols = (scopeType == ScopeType.InstanceParamsOrdered) ? this._indexer.getInstParams(svtokens[startTokenNum].text) : this._indexer.getInstPorts(svtokens[startTokenNum].text);
                let label = `${svtokens[startTokenNum].text}(${symbols.map(sym => sym.name).join(', ')})`;
                let instTypeSymbol = this._indexer.getContainerSymbol(svtokens[startTokenNum].text);
                let instFilePath = this._indexer.getInstFilePath(svtokens[startTokenNum].text);
                let definitions = svsymbol_1.SystemVerilogSymbol.getDefinitions(instFilePath, symbols);
                let params = [];
                for (let i = 0; i < symbols.length; i++) {
                    params.push({
                        label: symbols[i].name,
                        documentation: definitions[i]
                    });
                }
                return {
                    signatures: [
                        {
                            label: label,
                            documentation: instTypeSymbol.getDefinition(instFilePath),
                            parameters: params
                        }
                    ],
                    activeSignature: 0,
                    activeParameter: argNum >= params.length ? params.length - 1 : argNum
                };
            }
            return nullSignatureHelp;
        }
        catch (error) {
            genutils_1.ConnectionLogger.error(error);
            return nullSignatureHelp;
        }
    }
}
exports.SystemVerilogSignatureHelpProvider = SystemVerilogSignatureHelpProvider;
