function [ret, varargout] = addFiles(h, type, varargin)
%ADDFILES - add one or more files to either the Src.Files or Inc.Files
%list.  Duplicate entries (both filename and paths must match to qualify)
%are removed from the input, in addition to entries already in the
%Source/Include files vector.
%
% call the method like this:
%
% h.addFiles(type, files, groups)
%
% args  Optional? description
% type    n   char array specifying option type.
%             must be one of {'include', 'source'}
% files   n   char array or cell array of chars
%             contains the file(s) to be added
% groups  y   char array, or cell array of char
%             The Group label that should be applied to the files(s)
%             if this arg is a char array, it is used for all the files
%             if it is a cell array, length(groups) must equal length(files)

% Copyright 2005-2023 The MathWorks, Inc.

%check the args to make sure they all conform to the expected format
[argsOK, names, paths, groups, compNames, compPaths] = locCheckArgs(h, type, varargin{:});
if ~(argsOK)
    error(message('coder_compile:CoderCompile:badMethodArguments',...
                   'RTW.BuildInfoBuildObj.addFiles'));
end

% if there are no entries currently in the vector, we can skip the code to
% check for duplicate entries
isPreinc = false;
switch (type)
  case 'include'
    fVec = h.Inc;
  case 'preinclude'
    fVec = h.Inc;
    isPreinc = true;
  case 'source'
    fVec = h.Src;
  case 'other'
    fVec = h.Other;
  case 'syslib'
    fVec = h.SysLib;
    varargout{1} = {}; % assume nothing will be added for now
end

if ~isempty(fVec.Files)
    % if any of the files are already in the list, remove them.
    % However, they are only considered different if the paths
    % are different as well.
    cur_names = locGetFilePropForCompare(fVec.Files, 'FileName');
    isNameMatch = ismember(cur_names, compNames);
    curFileSubset = fVec.Files(isNameMatch);
    if ~isempty(curFileSubset)
        preincIdxToSet = [];
        cur_names = cur_names(isNameMatch);
        cur_paths = locGetFilePropForCompare(curFileSubset, 'Path');
        
        % Exact filename matches including full path
        [newFPMatch, curFPIdx] = locGetExactMatches(compNames, compPaths, cur_names, cur_paths);
        % capture indices of existing include files that we need to
        % mark as preincludes.
        preincIdxToSet = locCacheIndicesForUpdateIsPreinclude(isPreinc, preincIdxToSet, curFPIdx);

        % remove entries that match current entries exactly
        if ~isempty(curFPIdx)
            names     = names(~newFPMatch);
            paths     = paths(~newFPMatch);
            compNames = compNames(~newFPMatch);
            compPaths = compPaths(~newFPMatch);
            groups    = groups(~newFPMatch);
        end
        
        % Filename matches with empty paths
        %
        % next remove new entries with empty paths whose names match
        % existing entries.
        if ~isempty(names)
            [newEPMatch, curEPIdx] = locGetNameMatchWithEmptyNewPath(compNames, compPaths, cur_names);
            if ~isempty(curEPIdx)
                locValidatePreincludeWithEmptyPath(isPreinc, compNames, names, cur_names, newEPMatch);
                preincIdxToSet = locCacheIndicesForUpdateIsPreinclude(isPreinc, preincIdxToSet, curEPIdx);
                names     = names(~newEPMatch);
                paths     = paths(~newEPMatch);
                compNames = compNames(~newEPMatch);
                compPaths = compPaths(~newEPMatch); %#ok<NASGU>
                groups    = groups(~newEPMatch);
            end
        end
        
        % finally, any entries with paths that match existing
        % entries with no paths should set the paths
        % on the existing entries
        if ~isempty(names)
            [newMatchCurEP, curEPIdx] = locGetNameMatchWithEmptyCurrentPath(compNames, cur_names, cur_paths);
            if ~isempty(curEPIdx)
                % set the paths on the existing entries
                set(curFileSubset(curEPIdx),{'Path'},paths(newMatchCurEP)');

                % set preinclude on existing entries
                preincIdxToSet = locCacheIndicesForUpdateIsPreinclude(isPreinc, preincIdxToSet, curEPIdx);

                % remove these entries from the new entries
                names     = names(~newMatchCurEP);
                paths     = paths(~newMatchCurEP);
                groups    = groups(~newMatchCurEP);
            end
        end
        updateIsPreinclude(curFileSubset(preincIdxToSet), isPreinc);
    end             
end

% if all the insertions are duplicates, just return
if isempty(names)
    ret = [];
    return;
end

cnt = length(names);

for i=cnt:-1:1
    if ~strcmp(type, 'syslib')
        % check for unresolved paths and extensions
        if ~h.Settings.HasUnresolvedPaths
            h.Settings.HasUnresolvedPaths = isempty(paths{i});
        end
        if ~h.Settings.HasUnresolvedExtensions
            [~,~,ext] = fileparts(names{i});
            h.Settings.HasUnresolvedExtensions = isempty(ext);
        end
    end
    % Handle paths of the form $$(TOKEN)
    if coder.make.internal.featureOn('BuildInfoRootFolders')
        paths = coder.make.internal.processPathsForDoubleDollarSyntax(paths);
    end
    % create a new path object and add it to the list
    newFile(i) = RTW.BuildInfoFile(names{i},  paths{i}, ...
                                   groups{i}, names{i}, ...
                                   isPreinc);
end

% note that the newSrcFile must be transposed , because it is 1xN while
% fVec.Files is Nx1
fVec.Files = [fVec.Files;newFile'];

% return a handle to the file(s)
ret = newFile(cnt);

% if syslibs are being added, then return the path list
if strcmp(type, 'syslib')
    varargout{1} = paths;
end



%------------------------------------------------------------------------------
function [argsOK, names, paths, groups, compNames, compPaths] = locCheckArgs(h,type,varargin)

argsOK  = true;
names = {};
paths = {};
groups = {};
compNames = {};
compPaths = {};

% the first arg in varargin is the Filename, which is mandatory, the
% rest are optional.  However, check for not exceeding the maximum
% number of args.
if (nargin < 2) || (nargin > 5)
    argsOK = false;
    return;
end

% make sure the type was specified correctly
if ischar(type)
    tf = strcmp(type,{'include', 'preinclude', 'source', 'other', 'syslib'});
    if ~any(tf)
        argsOK = false;
        return;
    end
else
    argsOK = false;
    return;
end

% all of the remaining args must be cell arrays for the caller.  If a char
% array or scalar is sent int, just convert them to cell arrays

% the first arg is the file name
if ~iscellstr(varargin{1})
    if ~ischar(varargin{1})
        % this is an incompatible data type
        argsOK = false;
        return;
    end
    % If this is a *.ext insertion, the args get processed differently
    if ~isempty(regexp(varargin{1},'^\*\.[*\w]+$','ONCE'))
        if  (nargin < 4)
            argsOK = false;
            return;
        end
        names = locWildCardInsertion(varargin{1},varargin{2});
    else
        names = varargin(1);
    end
else
    names  = varargin{1};
end

% For the remaining arguments, if the arg is defaulted or a scaler/single
% char array, it should be converted to a cell array, repeating the entry
% for the length of the 'names' vector.

% second arg is the path(s)
if (nargin >= 4)
    % if the arg is already a cell array, use it as is.  otherwise create a
    % vector filling each entry with the arg.
    if iscellstr(varargin{2})
        paths = varargin{2};
        % the names and paths cell array must have the same length
        if (length(names) ~= length(paths))
            argsOK = false;
            return;
        end
    else
        if ~ischar(varargin{2})
            argsOK = false;
            return;
        end
        paths(1:length(names)) = varargin(2);
    end
    % get the full name, if it exists
    paths = coder.make.internal.transformPaths(paths,...
                                    'pathType','full',...
                                    'ignoreErrors',true);
    paths = regexprep(paths,'[\\/]$','');
else
    % no arg, just create a default cell array
    paths(1:length(names)) = {''};
end

% Strip filenames if supplied with a path
if strcmp(type, 'syslib')
    [filePaths,names,exts] = cellfun(@loc_CMakeNamespaceSafeFileparts, names, 'UniformOutput', false);
else
    [filePaths,names,exts] = fileparts(names);
end
names = strcat(names, exts);

% Error out if a path was included with the filename argument
% as well as  via the file path argument
idcsEmptyPaths = strcmp(paths, '');
idcsEmptyFilePaths = strcmp(filePaths, '');

if any( ~idcsEmptyPaths & ~idcsEmptyFilePaths )
    error(message('coder_compile:CoderCompile:inconsistentPaths',...
                   'RTW.BuildInfoBuildObj.addFiles'));
end

% Only use the path included with the filename argument if the path argument was 
% not supplied explicitly, or was empty
paths(idcsEmptyPaths) = filePaths(idcsEmptyPaths);

% the orientation of the matrices must all be 1xn if they are nx1 they
% must be transposed.
s = size(names);
if(s(1) > s(2))
    names = names';
end
s = size(paths);
if(s(1) > s(2))
    paths = paths';
end

% The long version of the file names are needed.  however, only the
% non-empty paths should be checked for full versions for both filenames and
% paths since an actual file system check is done to get the full filename.
idx = ~strcmp(paths, '');
if ispc
    %only check the files that have a '~' in them.
    altIdx = contains(names,'~');
else
    %only check the files that have a '\ ' in them on unix platforms.
    altIdx = contains(names,'\ ');
end

idx = and(idx,altIdx);
% if all paths are empty, no check is done.
if any(idx)
    % concat the full path with the filename and get the full name for the
    % file.
    fPaths = strcat(paths,filesep,names);
    fPaths(idx) = coder.make.internal.transformPaths(fPaths(idx),...
                                     'pathType','full',...
                                     'ignoreErrors',true);

    [paths(idx),fnames,fexts] = fileparts(fPaths(idx));
    names(idx) = strcat(fnames,fexts);
    
end

% make all of the fileseps the 'correct' one
paths = regexprep(paths,'[\\/]',h.Settings.FileSep);

if (nargin >= 5)
    % if the argument is not a cell array (i.e. a single value) create a
    % cell array the same length as names and fill it with that value.
    if iscellstr(varargin{3})
        groups = varargin{3};
        % the names and groups cell array must have the same length
        if (length(names) ~= length(groups))
            argsOK = false;
            return;
        end
    else
        if ~ischar(varargin{3})
            argsOK = false;
            return;
        end
        groups(1:length(names)) = varargin(3);
    end
else
    groups(1:length(names)) = {''};
end


% 
% check for empty names.  If the filename is empty, the
% whole entry should be removed.
idx    = ~strcmp(names, '');
names  = names(idx);
paths  = paths(idx);
groups = groups(idx);

% reduce relative path now in case file are added individually instead of
% in a bundle of cell array
paths = RTW.reduceRelativePath(paths);

% remove any duplicate entries if the list is longer than 1
if (~isscalar(names))
    s = size(groups);
    if(s(1) > s(2))
        groups = groups';
    end
    
    % remove any duplicate entries.  This is done by first concatenating
    % the paths and the names (which is done with vectors here), then
    % finding the unique set of entries.
    %
    % NOTE: even though the path doesn't have a filesep at the end, for the
    % purposes of comparing the path/filename combo, just strcating them
    % together is sufficient to get only unique pairings.  the strcat is
    % discarded afterward.
    new_full_paths = strcat(paths,names);
    [~,idx] = unique(new_full_paths, 'stable');
    
    % reduce all of the input vectors to the subset of unique entries
    % new_full_paths = new_full_paths(idx);
    names  = names(idx);
    paths  = paths(idx);
    groups = groups(idx);
    
    % next find any files with empty paths and the same name as a file with a
    % path
    idx    = strcmp(paths, '');
    
    % ef are the names with no path, f are the names
    % with a path in the corresponding paths vector.
    ef = names(idx);
    f = names(~idx);
    
    % get the files that are provided both with and without paths
    tf = intersect(f,ef);
    if ~isempty(tf)
        % get the logical indices from the original list for files that match
        % these names
        fidx = ismember(names, tf);
        
        % the final indices for removal are the ones that are empty and
        % have another matching entry with a path.  Negating this
        % value gives the indices of the items we want to keep.
        idx = ~and(idx, fidx);
        
        names  = names(idx);
        paths  = paths(idx);
        groups = groups(idx);
    end
end

% the final thing needed is to replace special dirs with the tokens.  For
% example: h.Settings.Matlabroot with $(MATLAB_ROOT).
paths = h.Settings.tokenizePaths(paths);

if ispc
    % for faster comparisons on the PC, get the
    % 'lowered' versions of the Names and Paths
    compNames = lower(names);
    compPaths = lower(paths);
else
    % preserve case for other platforms
    compNames = names;
    compPaths = paths;
end

function Names = locWildCardInsertion(dirPat, dirPath)
nameStructs = dir(fullfile(dirPath,dirPat));
Names =  {nameStructs(:).name};
% don't include the 'directory files'
Names = setdiff(Names,{'.','..'},'legacy');


function [filepath, filename, fileext] = loc_CMakeNamespaceSafeFileparts(name)

isCMakeLib = coder.make.internal.hasCMakeNamespace(name);
if isCMakeLib
    filepath = '';
    filename = name;
    fileext = '';
else
    [filepath, filename, fileext] = fileparts(name);
end



function [newFPMatch, curFPIdx] = locGetExactMatches(compNames, compPaths, cur_names, cur_paths)
    % The full path is used for removed exact matches.  The fact
    % that no '/' is added in between the name and
    % path is irrelevant, since both vectors have
    % the same format.
    curFP = strcat(cur_names, cur_paths);
    newFP = strcat(compNames, compPaths);
    [newFPMatch, curIdx] = ismember(newFP, curFP);
    curFPIdx = curIdx(newFPMatch);

function [newEPMatch, curEPIdx] = locGetNameMatchWithEmptyNewPath(compNames, compPaths, cur_names)
    newEPMatch = false(1, numel(compPaths));
    emptyPath = strcmp(compPaths, '');
    [match, curIdx] = ismember(compNames(emptyPath), cur_names);
    newEPMatch(emptyPath) = match;
    curEPIdx = curIdx(match);

function [newMatchCurEP, curEPIdx] = locGetNameMatchWithEmptyCurrentPath(compNames, cur_names, cur_paths)
    % Current empty paths
    curPathEmptyIdx = find(strcmp(cur_paths, ''));
    
    % New names that match current names
    [newMatchCurEP, curNameIdx] = ismember(compNames, cur_names(curPathEmptyIdx));
    curEPIdx = curPathEmptyIdx(curNameIdx(newMatchCurEP));

function p = locGetFilePropForCompare(buildInfoFiles, field)
    if ispc
        % PC is case insensitive
        p = lower(get(buildInfoFiles, field));
    else
        p = get(buildInfoFiles, field);
    end
    % if there is only 1 value it will be a char array, instead of a cell
    % array.  it needs to be converted to a cell array
    p = cellstr(p);

function locValidatePreincludeWithEmptyPath(isPreinc, compNames, names, cur_names, newEPMatch)
    % For preincludes, we need to do some additional
    % validation that we can unambiguously identify the
    % current include file that we need to update.
    if isPreinc
        % For preincludes, we need to do some additional
        % validation that we can unambiguously identify the
        % current include file that we need to update.
        newCompNames = compNames(newEPMatch);        
        newCompNameMatches = cellfun(@(x)strcmp(x, cur_names), newCompNames, 'UniformOutput', false);
        newNameIsUnambiguous = cellfun(@(x)sum(x)==1, newCompNameMatches);
        if ~all(newNameIsUnambiguous)
            % Throw an error - there may be more than one current
            % match (with different paths).
            newNames = names(newEPMatch);
            error(message('coder_compile:CoderCompile:NonUniquePreincludes',...
                strjoin(newNames(~newNameIsUnambiguous), ', ')));
        end
    end

function preincIdxToSet = locCacheIndicesForUpdateIsPreinclude(isPreinc, preincIdxToSet, newIdx)
    if isPreinc
        preincIdxToSet = [preincIdxToSet; newIdx];
    end

function updateIsPreinclude(buildInfoFiles, isPreinc)
    if isPreinc
        set(buildInfoFiles, 'IsPreinclude', true);
    end


% LocalWords:  dotexceptnewline
