function sig = getSig(fcn, lang)
% GETSIG Retrieves the signature of a function/class.
% Returns a structure analogous to mcrFunctionSignature.

%   Copyright 2013-2023 The MathWorks, Inc.

    [folderPath, name] = fileparts(fcn);
    [searchableDir, packageName] = parseFolderPath( folderPath);
    if isempty(packageName)
        FQName = name;
    else
        FQName = [packageName '.' name];
    end

    % If CPPCodeGenerator class exists and the m-file is a MATLAB class, use
    % CPPCodeGenerator to get the class methods and generate the cpp header file.
    % TO DO: delete the first exist condition when CPPCodeGenerator is submitted.
    try
        if nargin == 2 && exist(FQName,'class') == 8
            if ~isempty(lang) && (strcmpi(lang, 'CPP') || strcmpi(lang, 'CSharp'))
                % Defer the warning to when the header file is generated since
                % the whole picture is not seen here with only one file.
                warnStruct = warning('OFF', "MATLAB:engine_codegen:GeneratorHasUnresolvedClassDependencies");
                ocWarn = onCleanup(@()warning(warnStruct));
                if strcmpi(lang, 'CPP')
                    g = matlab.engine.internal.codegen.cpp.CPPCodeGenerator();
                else
                    g = matlab.engine.internal.codegen.csharp.CSharpCodeGenerator();
                end
                g = g.read(Classes=string(FQName));
                sig = getClassSigs(g);
                clear ocWarn
            else
                % Not for CPP/CSharp strongly typed, so don't use code generator.
                % Later we will likely create a generic generator for all languages
                % and don't need to do this conditionally.
                sig = getClassSigsForNotTyped(FQName, name);
            end
            sig.hFuncname = FQName;
            sig.hClassname = FQName;
            return;
        end
    catch ME
        warning(ME.identifier, '%s', ME.message);
    end

    sig = struct('vhInputs', {}, 'vhOutputs', {}, 'bVarargin', {}, 'bVarargout', {}, 'bScript', {}, 'hFuncname', '', 'hClassname', '', 'pragma', {}, 'vhGlobals', {});

    % Naming convention from parser/interpreter: argument kind ('i' or 'o')
    % followed by a 0-based index, i.e. i0, i1... o0, o1...
    inStr = {'i', 'in'};
    outStr = {'o', 'out'};
    
    function [searchableDir, packageName] = parseFolderPath( folderPath)
        packageName='';
        searchableDir = '';
        if ~isempty(folderPath)
            dotSep = [];
            parts = flip(split(folderPath,filesep));
            if isempty(parts{1})
                %
                % folderPath contains trailing seperator.
                %
                parts = parts(2:end,1);
            end
            if numel(parts)>0
                % isempty test protects aginst folderpath==filesep
                if ~isempty(parts{1}) && parts{1}(1)=='@'
                    % Strip @folder from the last element of parts(e.g., aa/@bb -> aa).
                    parts = parts(2:end,1);
                end
            end
            inPackage = true;
            for indx=1:numel(parts)
                if isempty(parts{indx})
                    % Account for a leading filesep in folderPath.
                    searchableDir = [filesep,searchableDir];
                elseif parts{indx}(1)=='+' && inPackage
                    packageName = [parts{indx}(2:end),dotSep,packageName];
                    dotSep = '.';
                else
                    inPackage = false;
                    searchableDir = [parts{indx},filesep,searchableDir];
                end
            end

        end
    end
    function list = argList(kind, count)
        if count < 0
            list = cell(1, -count);
            for k = 1:numel(list)-1
                list{k} = sprintf('%s%d', char(kind(1)), k-1);
            end
            list{-count} = sprintf('vararg%s', char(kind(2)));
        else
            list = cell(1,count);
            for k = 1:numel(list)
                list{k} = sprintf('%s%d', char(kind(1)), k-1);
            end
        end
    end
    if(exist(name,'builtin') == 5 && exist(name, 'file') == 0) % built-in 
        nIn = nargin(name);
        nOut = nargout(name);
        sig(1).vhInputs = argList(inStr, nIn);
        sig(1).vhOutputs = argList(outStr, nOut);
        sig(1).bVarargin = nIn < 0; 
        sig(1).bVarargout = nOut < 0; 
        sig(1).bScript = false;
        sig(1).hFuncname = name;
        sig(1).hClassname = '';
        sig(1).pragma = 0;
        sig(1).vhGlobals = {};
        return;
    end
    savePath = path;
    if(~isempty(searchableDir) && searchableDir(1) ~= '+')
        path(searchableDir, path); % so that "which" can find it when it is not in the current folder
    end 
    whichFcn = which(fcn);
    if(~isempty(whichFcn))
        fcn = whichFcn;
    end
    existReturn = exist(fcn); %#ok<EXIST>
    
    switch existReturn
        case 3 % MEX-file 
            sig(1).vhInputs = {'varargin'};
            sig(1).vhOutputs = {'varargout'};
            sig(1).bVarargin = true;
            sig(1).bVarargout = true;
            sig(1).bScript = false;
            sig(1).hFuncname = name;       
            sig(1).hClassname = '';
            sig(1).pragma = 0;
            sig(1).vhGlobals = {};
            path(savePath);
            return;
         case 6 % P-file 
            isScript = false;
            try           
                nIn = nargin(name);
            catch
                isScript = true;
            end
            if(isScript)
                sig(1).vhInputs = {};
                sig(1).vhOutputs = {};
                sig(1).bVarargin = false;
                sig(1).bVarargout = false;
                sig(1).bScript = true;
                sig(1).hFuncname = name;
                sig(1).hClassname = '';
                sig(1).pragma = 0;
                sig(1).vhGlobals = {};
            else
                nOut = nargout(name);
       
                sig(1).vhInputs = argList(inStr, nIn);
                sig(1).vhOutputs = argList(outStr, nOut);
                sig(1).bVarargin = nIn < 0;
                sig(1).bVarargout = nOut < 0;
                sig(1).bScript = false;
                sig(1).hFuncname = name;
                sig(1).hClassname = '';
                sig(1).pragma = 0;
                sig(1).vhGlobals = {};
            end
            path(savePath);
            return;
    end
   
    mt = mtree(fcn, '-file');

    switch mt.FileType
        case mtree.Type.ClassDefinitionFile
            sig(1).vhInputs = {'varargin'};
            sig(1).vhOutputs = {'varargout'};
            sig(1).bVarargin = true;
            sig(1).bVarargout = true;
            sig(1).bScript = false;
            if(strcmp(mt.root.Cexpr.kind, 'LT')) 
                % class derivation
                sig(1).hFuncname = mt.root.Cexpr.Left.string;
            else
                sig(1).hFuncname = mt.root.Cexpr.string;
            end
  %          sig(1).hFuncname = name;
            sig(1).hClassname = sig(1).hFuncname;
            sig(1).pragma = 0;
            sig(1).vhGlobals = {};
         case mtree.Type.FunctionFile
            mt = mtree(fcn, '-file', '-com');

            % top level functions
            fncs = indices(mtfind(List(root(mt)), 'Kind', 'FUNCTION'));
            num_fncs = numel(fncs);
            fncs_idx = 1;

            comments = indices(mtfind(List(mt), 'Kind', 'COMMENT'));

            events = [];
            externals = [];
            
            % Find pragmas.  Since the only pragmas searched for are now unsupported, only
            % the first such pragma will be found.  Search terminates at this point since
            % the callers that use this information will only use it to generate an error.
            %
            % The above means that for files containing multiple functions, correct pragma
            % information will only be provided for a single function.
            for i = comments
                if (i < fncs(fncs_idx)) 
                    continue;
                elseif ((fncs_idx < num_fncs) && (i >= fncs(fncs_idx + 1)))
                    fncs_idx = fncs_idx + 1;
                end
                currFunc = fncs(fncs_idx);
                   
                n = mt.select(i);
                if (strcmp(n.string, '%#event') && ~ismember(currFunc, externals))
                    % Abort pragma search at first occurrence.
                    events(end + 1) = currFunc; %#ok<AGROW>
                    break;
                end
                if (strcmp(n.string, '%#external') && ~ismember(currFunc, events))
                    % Abort pragma search at first occurrence.
                    externals(end + 1) = currFunc; %#ok<AGROW>
                    break;
                end
            end
         
            for fncInd = fncs
                if(ismember(fncInd, externals))
                    p = 1;
                elseif(ismember(fncInd, events))
                    p = 2;
                else
                    p = 0;
                end
                if(fncInd == fncs(1)) % main function
                    temp = makeFuncSig(mt.select(fncInd), p);
                    temp.hFuncname = name;
                    sig(end + 1) = temp; %#ok<AGROW>
                elseif(p > 0)
                    sig(end + 1) = makeFuncSig(mt.select(fncInd), p);  %#ok<AGROW>
                end
            end
    
        case mtree.Type.ScriptFile 
            % script
            sig(1).vhInputs = {};
            sig(1).vhOutputs = {};
            sig(1).bVarargin = false;
            sig(1).bVarargout = false;
            sig(1).bScript = true;
            sig(1).hFuncname = name;
            sig(1).hClassname = '';
            sig(1).pragma = 0;
            sig(1).vhGlobals = listGlobals(mt);
            
        otherwise
            error(message('Compiler:compiler:COM_ERROR_UNKNOWN_FILE_TYPE', ...
                char(mt.FileType), fcn))
            
    end
    if ~isempty(packageName)
        sig(1).hFuncname = [packageName '.' sig(1).hFuncname];
    end
    path(savePath);
end

function funcSig = makeFuncSig(node, pragma)
    if(~strcmp(node.kind, 'FUNCTION'))
        funcSig = [];
        return;
    end
    funcSig.vhInputs = listIns(node);
    funcSig.vhOutputs = strings(List(node.Outs));
    funcSig.bVarargin = ~isempty(funcSig.vhInputs) && strcmp(funcSig.vhInputs(end), 'varargin');
    funcSig.bVarargout = ~isempty(funcSig.vhOutputs) && strcmp(funcSig.vhOutputs(end), 'varargout');
    funcSig.bScript = false;
    funcSig.hFuncname = node.Fname.string;
    funcSig.hClassname = '';
    
    funcSig.pragma = pragma;
   
     funcSig.vhGlobals = listGlobals(node.Body);
end
function ins = listIns(node)
    ins = {};
    curr = node.Ins;
    tildeCount = 0;
    tildeBase = 75; % hex2dec('4b');
    while ~curr.isnull
        if(strcmp(kind(curr), 'NOT')) % tilde
            ins{end + 1} = sprintf('%%U%x', tildeBase + tildeCount); %#ok<AGROW>
            tildeCount = tildeCount + 1;
        else
            ins{end + 1} = curr.string; %#ok<AGROW>
        end
        curr = curr.Next;
    end
end
function globals = listGlobals(node)
    l = List(node);
    
    globals = {};

    g = mtfind(l.Tree, 'Kind', 'GLOBAL');

    for i = g.indices
        n = g.select(i);
        a = n.Arg;
        globals = [globals strings(a.List)]; %#ok<AGROW>
    end
end
function classSigs = getClassSigs(g)
    classSigs = classSigsInCommon();
    clsMethods = {};
    num_methods = numel(g.ClassList(1).Methods);
    if (num_methods > 0)
        clsMethods = cell(1, num_methods);
        for index = 1:num_methods
            clsMethods{index} = char(g.ClassList(1).Methods(index).SectionName);
        end
    end
    classSigs.methods = clsMethods;
end

function classSigs = getClassSigsForNotTyped(FQName, name)
    classSigs = classSigsInCommon();
    classobj = meta.class.fromName(FQName);
    clsMethods = {};
    if ~classobj.Enumeration
        num_methods = numel(classobj.MethodList);
        next = 1;
        if (num_methods > 0)
            for index = 1:num_methods
                % skip non-public methods, constructor, 'empty' and
                % those not defined in the current class
                if strcmp(classobj.MethodList(index).Access, 'public') && ...\
                        ~strcmp(classobj.MethodList(index).Name, 'empty') && ...\
                        contains(classobj.MethodList(index).DefiningClass.Name, FQName) && ...\
                        ~strcmp(name, classobj.MethodList(index).Name)
                    if classobj.MethodList(index).Static
                        clsMethods{next} = [FQName '.' classobj.MethodList(index).Name];
                    else
                        clsMethods{next} = classobj.MethodList(index).Name;
                    end
                    next = next + 1;
                end
            end
        end
    end
    classSigs.methods = clsMethods;
end

function classSigs = classSigsInCommon()
    classSigs.vhInputs = {'varargin'};
    classSigs.vhOutputs = {'varargout'};
    classSigs.bVarargin = true;
    classSigs.bVarargout = true;
    classSigs.bScript = false;
    classSigs.pragma = 0;
    classSigs.vhGlobals = {};
end
