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-2020 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
            DAStudio.warning('RTW:buildInfo:unableToFindMinimalIncludes',...
                             exc.message);
            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);
    
    % the root paths and defines are used in addition to any "local"
    % paths and defines in the linkObjs/ModelRefs.
    paths = h.getSourcePaths(true);
    paths = [paths  h.getIncludePaths(true)];    
    defines = locGetDefinesForEDG(h);

    for i=1:length(h.LinkObj)
        locRunEDGFrontEndAndAddIncludeFiles(h.LinkObj(i),paths,defines,args);
    end
    
    for i=1:length(h.ModelRefs)
        locRunEDGFrontEndAndAddIncludeFiles(h.ModelRefs(i),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();
    wordSizes = h.manageTargetInfo('getTargetWordsizes');
    tgt.CharNumBits = wordSizes.charNumBits;
    tgt.ShortNumBits = wordSizes.shortNumBits;
    tgt.IntNumBits = wordSizes.intNumBits;
    tgt.LongNumBits = wordSizes.longNumBits;
    
    % Following g1898213, it should no longer be necessary to specify the language here
    lang = 'c++';
    % c++ threads and Adaptive AUTOSAR require at least c++11.
    if h.Settings.CompilerRequirements.supportCPPThread || ...
            h.Settings.CompilerRequirements.supportAutosarAdaptive
        lang = [lang, '11'];
    end 
    cgfeOpts = internal.cxxfe.util.getFrontEndOptions('lang', lang, '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(:); {'RT'}; 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')
                continue;
            end
            
            if (msg.msgno < 0)
                if args.ignoreParseError
                    DAStudio.warning('RTW:buildInfo:ignoreFindIncludeParseError',...
                                     files{i}, msg.detail);
                else
                    DAStudio.error('RTW:buildInfo:findIncludeParseError',...
                                   files{i}, msg.detail);
                end
            else
                errMsg = sprintf('%s:%s: %s\n%s',...
                                 msg.file,...
                                 num2str(msg.line),...
                                 msg.desc,...
                                 msg.detail);
                if args.ignoreParseError
                    DAStudio.warning('RTW:buildInfo:ignoreFindIncludeParseError',...
                                     files{i}, errMsg);
                else 
                    DAStudio.error('RTW:buildInfo:findIncludeParseError',...
                                   files{i}, errMsg);
                end
            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,'[/\\]',...
                       regexptranslate('escape',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,'[/\\]',...
                                  regexptranslate('escape',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)
        h.addIncludeFiles(incNames,ifPaths,'Automatic');
    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
    
    for i=1:length(h.LinkObj)
        h.LinkObj(i).findIncludeFiles(exts);
    end
    
    for i=1:length(h.ModelRefs)
        h.ModelRefs(i).findIncludeFiles(exts);
    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
        biVals = strrep(biVals, '\"', '"');
        defines = strcat(biMacros,'=',biVals)';
    else
        defines = {};
    end
end
