function findIncludeFiles(h,varargin)
% FINDINCLUDEFILES - The findIncludeFiles function searches for include
% files in all source and include paths in the build information object and
% adds the files found, along with their full paths, to the build
% information object
%
% Argument    description
% buildinfo   Build information handle returned by RTW.Buildinfo.
%
% extPatterns (optional) A cell array of file patterns (char arrays) that
%             conform to a specific format.  Each pattern must start with
%             '*.' and may have an combination of '*' and alphanumeric or
%             underscore characters.  The following are all valid patterns:
%                *.h
%                *.hpp
%                *.x*
%             if no pattern is specified, then a default pattern of '*.h' is
%             used
%
% example 1:   
%   myModelBuildInfo = RTW.BuildInfo;
%   addSourcePaths(myModelBuildInfo,...
%                  {'/etcproj/etclib' '/etcproj/etc/etc_build'}, 'etc');
%   findIncludeFiles(buildInfo);
%
% example 2:
%   myModelBuildInfo = RTW.BuildInfo;
%   addSourcePaths(myModelBuildInfo,...
%                  {'/etcproj/etclib' '/etcproj/etc/etc_build'}, 'etc');
%   findIncludeFiles(buildInfo,{'*.h' '*.hpp' '*.zzz');
%
% Please refer to the documentation for more information.
%

%   Copyright 2006-2023 The MathWorks, Inc.
%     
    [varargin{:}] = convertStringsToChars(varargin{:});

    p = inputParser;
    p.StructExpand  = true;
    p.CaseSensitive = false; 

    default_exts = {'*.h' '*.H' '*.hpp' '*.HPP'};
    p.addParameter('minimalHeaders',        true,         @islogical);
    p.addParameter('extensions',            default_exts, @iscellstr);
    p.addParameter('ignoreParseError',      false,        @islogical);
    p.addParameter('ignoreMissingIncludes', true,         @islogical);
     
    if nargin > 2
        p.parse(varargin{:});
        args = p.Results;
    else
        % parse with an empty argument to get the default parameter values
        p.parse();
        args = p.Results;
        if nargin == 2
            % this is for backwards compatibility to when the function only
            % allowed for a single input arg.
            
            % this will reduce the extensions to only those that conform to
            % '*.ABC' and *.A*' type rules where ABC and A can be any
            % alphanumeric or underscore char.
            idx = ~cellfun(@isempty, regexp(varargin{1},'^\*\.[*\w]+$','ONCE'));
            args.extensions = varargin{1}(idx);
        end
    end

    % first get the root include files
    paths = h.getSourcePaths(true);
    paths = [paths  h.getIncludePaths(true)];


    if args.minimalHeaders
        try
            locFindMinimalIncludeFiles(h,args);
        catch exc
            
            % In this special case, g2317671, need DAStudio.warning -> MSLDiagnostic wrapper
            MSLDiagnostic('RTW:buildInfo:unableToFindMinimalIncludes',...
                             exc.message).reportAsWarning;
            locFindAllIncludeFiles(h, paths, args.extensions);
        end
    else
        locFindAllIncludeFiles(h, paths, args.extensions);
    end

end

function locFindMinimalIncludeFiles(h,args)
    
% get the root headers first
    locRunEDGFrontEndAndAddIncludeFiles(h,{},{},args);
    
    linkObjs = h.Linkables(:);
    for i=1:length(linkObjs)
        if ~isempty(linkObjs(i).BuildInfoHandle)
            % If the link object is backed by a buildInfo.mat, collect the
            % headers directly from the backing buildInfo
            linkObj = linkObjs(i).BuildInfoHandle;
            paths = {};
            defines = {};
        else
            linkObj = linkObjs(i);
            % Root paths and defines from the top component are needed because
            % the linkable object does not have its own buildInfo
            sourcePaths = h.getSourcePaths(true);
            paths = [sourcePaths  h.getIncludePaths(true)];    
            defines = locGetDefinesForEDG(h);
        end
        locRunEDGFrontEndAndAddIncludeFiles(linkObj,paths,defines,args);
    end
end

function locRunEDGFrontEndAndAddIncludeFiles(h,paths,defines,args)

% for the EDG front end, all source files must be fully resolved.
% Run updateFilepathsAndExtensions to do this.
    h.updateFilePathsAndExtensions();
    
    % append the paths and defines in the BuildInfoBuildObj (passed
    % in via h) to the paths and defines passed in.
    paths = [paths h.getSourcePaths(true)];
    paths = [paths  h.getIncludePaths(true)];    
    defines = [defines locGetDefinesForEDG(h)]; 
        
    % Get the EDG front-end options
    tgt = internal.cxxfe.util.TargetOptions();
    if isempty(h.Settings.TargetInfo)
        % Target word size information was not specified therefore use 
        % current platform default values
        % See g2517666
        wordSizes = locGetHostWordSizes();
    else
        wordSizes = h.manageTargetInfo('getTargetWordsizes');
    end
    tgt.CharNumBits = wordSizes.charNumBits;
    tgt.ShortNumBits = wordSizes.shortNumBits;
    tgt.IntNumBits = wordSizes.intNumBits;
    tgt.LongNumBits = wordSizes.longNumBits;
    
    cgfeOpts = internal.cxxfe.util.getFrontEndOptions('lang', 'c++14', 'targetCpu', tgt);

    % Add system include for OpenMP and windows stub headers, omp.h and windows.h, that MATLAB Coder may use
    cgfeOpts.Preprocessor.SystemIncludeDirs{end+1} = fullfile(matlabroot,'toolbox','rtw','rtw','+rtw','+codemetrics','@C_CodeMetrics','omp_include');
    cgfeOpts.Preprocessor.SystemIncludeDirs{end+1} = fullfile(matlabroot,'toolbox','rtw','rtw','+rtw','+codemetrics','@C_CodeMetrics','stub_include');

    % the drive letter for matlabroot may be lower case, however buildInfo as
    % well as other utilities such as the parser normalizes the drive to
    % uppercase.  Normalize the SystemIncludeDirs to upper case drive letters.
    cgfeOpts.Preprocessor.SystemIncludeDirs =...
        coder.make.internal.transformPaths(cgfeOpts.Preprocessor.SystemIncludeDirs,...
                           'pathType','full');
    
    cgfeOpts.Preprocessor.IncludeDirs = paths;
    cgfeOpts.Preprocessor.Defines = [cgfeOpts.Preprocessor.Defines(:); defines(:)];
    cgfeOpts.Language.LanguageExtra(end+1) = {'--dependencies'};
    if args.ignoreMissingIncludes
        cgfeOpts.Language.LanguageExtra(end+1) = {'--ignore_missing_includes'};
    end
    
    files = h.getSourceFiles(true,true);
    
    incFiles = {};
    
    for i=1:length(files)        
        [msgs, iFiles] = internal.cxxfe.util.GlobalSymbolParser.parseFile(files{i}, cgfeOpts, -1);
        
        for j=1:length(msgs)
            msg = msgs(j);

            % ignore non-errors since they are not pertinent to finding the
            % header files.
            if ~strcmpi(msg.kind,{'error', 'fatal'})
                continue;
            end

            msgText = locGetFormattedMessage(msg);
            
            if args.ignoreParseError
                warning(message('RTW:buildInfo:ignoreFindIncludeParseError',...
                                files{i}, msgText));
            else
                error(message('RTW:buildInfo:findIncludeParseError',...
                              files{i}, msgText));
            end
        end
        
        % the original source file is always in the list of files.
        % Remove this from the list before processing.
        % iFiles might be empty when a parse error occurs and is ignored.
        % iFiles might be a 1x1 empty cell when ignore parse error. 
        if ~isempty(iFiles) && ~isempty(iFiles{1})
            incFiles = RTW.unique([incFiles iFiles{:}]);
        end

    end
    incFiles = regexprep(incFiles,'[/\\]',h.Settings.FileSep);
    incFiles = coder.make.internal.transformPaths(incFiles,...
                                'pathType','full',...
                                'ignoreErrors',true);
    incFiles = setdiff(incFiles,files);    
    % filter out any system includes.  Note that these folders must be
    % transformed to match the transformation done on the file list.
    % otherwise there can be subtle mismatches such as UNC vs mapped drive
    % paths.
    % the     
    SystemIncludeDirs = cgfeOpts.Preprocessor.SystemIncludeDirs;
    SystemIncludeDirs = regexprep(SystemIncludeDirs,'[/\\]',h.Settings.FileSep);
    SystemIncludeDirs = coder.make.internal.transformPaths(SystemIncludeDirs,...
                                           'pathType','full',...
                                           'ignoreErrors',true);
    for i=1:length(SystemIncludeDirs)
        idx = cellfun(@isempty,strfind(incFiles,SystemIncludeDirs{i}));
        incFiles = incFiles(idx);
    end
    
    incFiles = RTW.unique(incFiles);
    
    % break the filenames up into paths and filenames
    incNames(1:length(incFiles)) = {''};
    ifPaths(1:length(incFiles)) = {''};
    for i=1:length(incFiles)
        [ifPaths{i},fnames,exts] = fileparts(incFiles{i});
        incNames{i} = strcat(fnames,exts);
    end
    
    % Add them to BuildInfo
    if ~isempty(incNames)
        if isa(h, 'RTW.BuildInfoLinkObj')
            % TODO(should not be necessary to add include files to a link object)
            h.addLinkObjIncludeFiles(incNames,ifPaths,'Automatic');
        else
            h.addIncludeFiles(incNames,ifPaths,'Automatic');
        end
    end
end

function locFindAllIncludeFiles(h, paths, exts)

% some paths may start with a '.' indicating a relative path.  convert them
% to a fully qualified path so that the files added have a correct
% absolute path.
    idx = ~cellfun(@isempty,regexp(paths,'^\.','once'));
    for i=1:length(idx)
        if idx(i)
            paths(i) = RTW.reduceRelativePath(fullfile(pwd,paths(i)));
        end
    end
    
    % loop through the paths and search for header files
    for i=1:length(paths)
        for j = 1:length(exts)
            incFiles = dir(fullfile(paths{i},exts{j}));    
            
            % we need the names separated out
            incNames = {incFiles(:).name};
            
            % add them to the object
            h.addIncludeFiles(incNames,paths{i},'Automatic');
        end
    end
    
    linkObjs = h.Linkables(:);
    for i=1:length(linkObjs)
        % If this is a lightweight link object that is backed by a buildInfo.mat
        % we collect the headers directly from the backing buildInfo 
        if ~isempty(linkObjs(i).BuildInfoHandle)
            % We run this function when 'minimalHeaders' is false or EDG has failed
            % Hence the recursive call uses the value 'false' for 'minimalHeaders'
            linkObjs(i).BuildInfoHandle.findIncludeFiles('extensions', exts, 'minimalHeaders', false);
        else
            % findIncludeFiles for BuildInfoLinkObj objects does not support the 'minimalHeaders' name-value pair
            % It collects all the headers by default and never calls EDG
            linkObjs(i).findIncludeFiles(exts);
        end
    end
end

function defines = locGetDefinesForEDG(h)
    % get defines from BuildInfo and format
    % them as inputs for EDG
    [~,biMacros,biVals] = h.getDefines();
    if ~isempty(biMacros)
        % g2005835: remove escaping of "'s
        unEscapeFunction = coder.make.internal.unEscapeDoubleQuotesFunction;
        biVals = unEscapeFunction(biVals);
        defines = strcat(biMacros,'=',biVals)';
    else
        defines = {};
    end
end

function wordSizes = locGetHostWordSizes()
% Format the host wordsizes to be used when the BuildInfo.Settings field 
% TargetInfo is empty
    hostWordSizes = rtwhostwordlengths;

    wordSizes.charNumBits  = hostWordSizes.CharNumBits ;
    wordSizes.shortNumBits = hostWordSizes.ShortNumBits;
    wordSizes.intNumBits   = hostWordSizes.IntNumBits  ;
    wordSizes.longNumBits  = hostWordSizes.LongNumBits ;
end

function msgText = locGetFormattedMessage(msg)
    % Format the message as line:<n>:<kind> <desc> <newline> <detail>
    if msg.line > 0
        msgText = [msg.file ':' num2str(msg.line) ':'];
    else
        msgText = '';
    end

    msgText = [msgText, char(msg.kind), ':']; %#ok<*AGROW>

    if ~isempty(msg.desc)
        msgText = [msgText, ' ', msg.desc];
    end

    if ~isempty(msg.detail)
        msgText = [msgText, newline, msg.detail];
    end
end
