function ret = addLinkObjects(h, varargin)
% ADDLINKOBJECTS -The addLinkObjects function adds specified link objects
% to the model's build information. Real-Time Workshop stores the link
% objects in a vector in relative priority order. If multiple objects have
% the same priority or you do not specify priorities, the function adds the
% objects to the vector based on the order in which you specify them.
%
% Argument description
% buildinfo Build information handle returned by RTW.Buildinfo.
%
%
% linkobjs A character array or cell array of character arrays that
%
% specifies the filenames of linkable objects to be added to
% the build information. The function adds the filenames that
% you specify in the function call to a vector that stores the
% object filenames in priority order. If you specify multiple
% objects that have the same priority (see priority below), the
% function adds them to the vector based on the order in which
% you specify the object filenames in the cell array.
% The function removes duplicate link objects that:
% - Already exist in the linkable object filename vector
% - Have a path that matches the path of a matching linkable
% object filename
% A duplicate entry consists of an exact match of a path string
% and corresponding linkable object filename.
%
% paths A character array or cell array of character arrays that
% specifies paths to the linkable objects. If you specify a
% character array, the path string applies to all linkable objects.
%
% priority (optional) A numeric value or vector of numeric values that
% indicates the relative priority of each specified link
% object. Lower values have higher priority. The default
% priority is 1000.
%
% precompiled (optional) The logical value true or false or a vector of logical
% values that indicates whether each specified link object is
% precompiled.
%
% linkonly (optional) The logical value true or false or a vector of logical
% values that indicates whether each specified link object is
% to be only linked. If you set this argument to false, the
% function also adds a rule to the makefile for building the
% objects.
% groups (optional) A character array or cell array of character arrays
% that groups specified include files
%
% example 1:
% myModelBuildInfo = RTW.BuildInfo;
% addLinkObjects(myModelBuildInfo, {'libobj1' 'libobj2'},...
% {'/proj/lib/lib1' '/proj/lib/lib2'}, [26 10]);
%
% example 2:
% myModelBuildInfo = RTW.BuildInfo;
% addLinkObjects(myModelBuildInfo, {'libobj1' 'libobj2'},...
% {'/proj/lib/lib1' '/proj/lib/lib2'}, [26 10],...
% [false, true]);
%
% Please refer to the documentation for more information.
%

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

ret = [];
if (nargin == 2)
newlib = locCheckLibVector(h, varargin{1});
if ~isempty(newlib)
ret = locAddLibVector (h, newlib);
end
return;
end

%check the args to make sure they all conform to the expected format
[args_ok, mdlRefLibs, otherLibs] = locCheckArgs(h,varargin{:});

if ~(args_ok)
error(message('coder_compile:CoderCompile:badMethodArguments',...
'RTW.BuildInfo.addLinkObjects'));
end

% if any of the linkable objs are already in the list, remove them.
if (~isempty(h.LinkObjsDirect) && ~isempty(otherLibs.Names))
[~, libIdx] = intersect(otherLibs.Names, {h.LinkObjsDirect.Name}, 'stable');

% reduce all of the input vectors to the subset of new entries.
otherLibs.Names(libIdx) = [];
otherLibs.Paths(libIdx) = [];
otherLibs.LinkPri(libIdx) = [];
otherLibs.Precompiled(libIdx) = [];
otherLibs.LinkOnly(libIdx) = [];
otherLibs.Groups(libIdx) = [];
end

% if any of the modelrefs are already in the list, remove them.
if (~isempty(h.ModelRefsDirect) && ~isempty(mdlRefLibs.Names))
[~, mrIdx] = intersect(mdlRefLibs.Names, {h.ModelRefsDirect.Name}, 'stable');

% reduce all of the input vectors to the subset of new entries.
mdlRefLibs.Names(mrIdx) = [];
mdlRefLibs.Paths(mrIdx) = [];
mdlRefLibs.LinkPri(mrIdx) = [];
mdlRefLibs.Precompiled(mrIdx) = [];
mdlRefLibs.LinkOnly(mrIdx) = [];
mdlRefLibs.Groups(mrIdx) = [];
end

if ~isempty(otherLibs.Names)
cnt = length(otherLibs.Names);
for i=cnt:-1:1
% create a new linkable obj, and add it to the list
newOlib(i) = RTW.BuildInfoLinkObj(otherLibs.Names{i},...
otherLibs.Paths{i},...
otherLibs.LinkPri(i),...
otherLibs.Groups{i},...
h.Settings);

% set the items that are not args to the constructor
newOlib(i).Precompiled = otherLibs.Precompiled(i);
newOlib(i).LinkOnly = otherLibs.LinkOnly(i);
end
ret = locAddLibVector (h,newOlib);
end

if ~isempty(mdlRefLibs.Names)
cnt = length(mdlRefLibs.Names);
for i=cnt:-1:1
% create a new linkable obj, and add it to the list
newMlib(i) = RTW.BuildInfoLinkObj(mdlRefLibs.Names{i},...
mdlRefLibs.Paths{i},...
mdlRefLibs.LinkPri(i),...
mdlRefLibs.Groups{i},...
h.Settings);

% set the items that are not args to the constructor
newMlib(i).Precompiled = mdlRefLibs.Precompiled(i);
newMlib(i).LinkOnly = mdlRefLibs.LinkOnly(i);
end
ret = locAddLibVector (h,newMlib);
end

function ret = locAddLibVector(h, newlib)
% always return the last linkable obj inserted, which in this case is the
% last entry in the newlib list. Note that even though the list is built
% backwards, the order is maintained to be consistent with the order of
% Names passed in. From the caller's perspective, the last linkable obj
% inserted should be the last entry in the input vectors.
ret = newlib(end);

% separate the model ref libs from the other libs, since they must go in a
% different vector.
Groups = get(newlib,'Group');

if ~iscell(Groups)
Groups = {Groups};
end
isMdlRef = strcmp(Groups,'MDLREF_LIB');

mdlRefLinkObjs = newlib(isMdlRef);
otherLinkObjs = newlib(~isMdlRef);

if ~isempty(mdlRefLinkObjs)
% to sort the list based on link priorities we want to get the index
% ordering of the sorted list.
tmp_list = [h.ModelRefsDirect; mdlRefLinkObjs'];
priorities = get(tmp_list,'LinkPriority');
% if h.ModelRefsDirect is empty, and this is a single insertion, the get call
% returns a double array. otherwise it returns a cell array which must
% be converted.
if iscell(priorities)
priorities = cell2mat(priorities);
end
[~, idx] = sort(priorities);

% now that we have the sorted indices, just update the Linkable Obj vector
% with the sorted vector.
h.ModelRefsDirect = tmp_list(idx);
end
if ~isempty(otherLinkObjs)
% to sort the list based on link priorities we want to get the index
% ordering of the sorted list.
tmp_list = [h.LinkObjsDirect; otherLinkObjs'];
priorities = get(tmp_list,'LinkPriority');
% if h.LinkObjsDirect is empty, and this is a single insertion, the get call
% returns a double array. otherwise it returns a cell array which must
% be converted.
if iscell(priorities)
priorities = cell2mat(priorities);
end
[~, idx] = sort(priorities);

% now that we have the sorted indices, just update the Linkable Obj vector
% with the sorted vector.
h.LinkObjsDirect = tmp_list(idx);
end

function newlibs = locCheckLibVector(h, libs)

newlibs = [];
if isempty(libs)
return;
end

% check the vector for the correct object handle
for i=1:length(libs)
if ~isa(libs(i),'RTW.BuildInfoLinkObj')
error(message('coder_compile:CoderCompile:badMethodArguments',...
'RTW.BuildInfo.addLinkObjects'));
end
end

Names = get(libs,'Name');
Groups = get(libs,'Group');
if ~iscell(Names)
Names = {Names};
Groups = {Groups};
end

% check the input list for dups first
if (length(Names) > 1)
% remove any duplicate entries preserving the order.
[~,idx] = RTW.unique(Names);

Names = Names(idx);
Groups = Groups(idx);
libs = libs(idx);
end

isMdlRef = strcmp(Groups,'MDLREF_LIB');

mdlRefNames = Names(isMdlRef);
mdlRefLibs = libs(isMdlRef);
otherNames = Names(~isMdlRef);
otherLibs = libs(~isMdlRef);

if (~isempty(h.LinkObjsDirect) && ~isempty(otherNames))
[~, mrIdx] = intersect(otherNames, {h.LinkObjsDirect.Name}, 'stable');

% Skip link objects that are already added
otherLibs(mrIdx) = [];

end

if (~isempty(h.ModelRefsDirect) && ~isempty(mdlRefNames))
[~, mrIdx] = intersect(mdlRefNames, {h.ModelRefsDirect.Name}, 'stable');

% reduce all of the input vectors to the subset of new entries.
mdlRefLibs(mrIdx) = [];
end

newlibs = [otherLibs; mdlRefLibs]';

% make sure all of the Settings handles property are set to the same object
% as the main BuildInfo settings
for i=1:length(newlibs)

% get the old special dirs
oldSpecialDirs = newlibs(i).Settings.getSpecialDirsAndTokens();
% the first one is matlabroot, which can be skipped
oldSDirs = oldSpecialDirs.dirs(2:end);
oldSToks = oldSpecialDirs.toks(2:end);

% the special dirs in the current buildInfo must be put in place.
newSpecialDirs = h.Settings.getSpecialDirsAndTokens();
newSDirs = newSpecialDirs.dirs(2:end);
newSToks = newSpecialDirs.toks(2:end);

% first de-tokenize all the paths in the newlib, then re-tokenize them
% with the current buildInfo tokens
newlibs(i).replacePathString(oldSToks,oldSDirs);
newlibs(i).replacePathString(newSDirs,newSToks);

% propagate the unresolved paths and extension flags from the link object
% to the buildInfo
h.Settings.HasUnresolvedPaths = h.Settings.HasUnresolvedPaths || ...
newlibs(i).Settings.HasUnresolvedPaths;
h.Settings.HasUnresolvedExtensions = h.Settings.HasUnresolvedExtensions || ...
newlibs(i).Settings.HasUnresolvedExtensions;

% it is now safe to override the Settings object
newlibs(i).Settings = h.Settings;

% since the Settings object has changed, make sure the fileSeparator is
% updated.
newlibs(i).updateFileSeparator(h.Settings.FileSep);
end

[m,n] = size(newlibs);
if (m>n)
newlibs = newlibs';
end

function [args_ok, mdlRefLibs, otherLibs] = locCheckArgs(h,varargin)

args_ok = true;
mdlRefLibs = [];
otherLibs = [];

Paths = {};
LinkPri = [];
Precompiled = [];
LinkOnly = [];
Groups = {};

%currently 2-7 arguments can be passed in.
if ((nargin<2) || (nargin>7))
args_ok = false;
return;
end

% The arguments are information for creating new linkable obj objects. We
% need to format them in the correct way.

% the first arg is the file name
if ~iscellstr(varargin{1})
if ~ischar(varargin{1})
% this is an incompatible data type
args_ok = false;
return;
end
Names = varargin(1);
else
Names = varargin{1};
end

% For the remaining arguments, if the arg is defaulted or a scalar/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 >= 3)
% 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))
args_ok = false;
return;
end
else
if ~ischar(varargin{2})
args_ok = 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

% 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 = ~cellfun(@isempty,Paths);

if ispc
%only check the files that have a '~' in them.
altIdx = cellfun(@(x)contains(x,'~'),Names);
else
%only check the files that have a '\ ' in them on unix platforms.
altIdx = cellfun(@(x)contains(x,'\ '),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] = cellfun(@fileparts,fPaths(idx),...
'UniformOutput',false);

Names(idx) = strcat(fnames,fexts);

end

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

% the third (optional) vararg is the link priority
if (nargin >= 4) && ~isempty(varargin{3})
% if the argument is not a vector (i.e. a single value) create a
% vector the same length as LibName and fill it with that value.
if ~isscalar(varargin{3})
LinkPri = varargin{3};
% the Names and Link priorities cell array must have the same length
if (length(Names) ~= length(LinkPri))
args_ok = false;
return;
end
else
LinkPri(1:length(Names)) = varargin{3};
end
else
LinkPri(1:length(Names)) = RTW.BuildInfoLinkObj.DefaultLinkPriority;
end

% the fourth (optional) vararg is the Precompiled flag
if (nargin >= 5) && ~isempty(varargin{4})
% if the argument is not a vector (i.e. a single value) create a
% vector the same length as LibName and fill it with that value.
if ~isscalar(varargin{4})
Precompiled = varargin{4};
% the Names and Precompiled cell array must have the same length
if (length(Names) ~= length(Precompiled))
args_ok = false;
return;
end
else
Precompiled(1:length(Names)) = (varargin{4} == 1);
end
else
Precompiled(1:length(Names)) = false;
end

% the fifth (optional) vararg is the link only flag
if (nargin >= 6) && ~isempty(varargin{5})
% if the argument is not a vector (i.e. a single value) create a
% vector the same length as LibName and fill it with that value.
if ~isscalar(varargin{5})
LinkOnly = varargin{5};
% the Names and LinkOnly cell array must have the same length
if (length(Names) ~= length(LinkOnly))
args_ok = false;
return;
end
else
LinkOnly(1:length(Names)) = varargin{5};
end
else
LinkOnly(1:length(Names)) = false;
end

if (nargin >= 7) && ~isempty(varargin{6})
% 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{6})
Groups = varargin{6};
% the Names and Groups cell array must have the same length
if (length(Names) ~= length(Groups))
args_ok = false;
return;
end
else
if ~ischar(varargin{6})
args_ok = false;
return;
end
Groups(1:length(Names)) = varargin(6);
end
else
Groups(1:length(Names)) = {''};
end

% replace special dirs with the tokens. For example:
% h.Settings.Matlabroot with $(MATLAB_ROOT).
Paths = h.Settings.tokenizePaths(Paths);

if coder.make.internal.featureOn('BuildInfoRootFolders')
% Handle paths of the form $$(TOKEN)
Paths = coder.make.internal.processPathsForDoubleDollarSyntax(Paths);
end
% finally, split the input into the mdlref libs and other libs
isMdlRef = strcmp(Groups, coder.make.internal.BuildInfoGroup.ModelRefGroup);

mdlRefLibs.Names = Names(isMdlRef);
mdlRefLibs.Paths = Paths(isMdlRef);
mdlRefLibs.LinkPri = LinkPri(isMdlRef);
mdlRefLibs.Precompiled = Precompiled(isMdlRef);
mdlRefLibs.LinkOnly = LinkOnly(isMdlRef);
mdlRefLibs.Groups = Groups(isMdlRef);

% remove any duplicate entries if the list is longer than 1
if (length(mdlRefLibs.Names) > 1)
% remove any duplicate entries preserving the order.
[mdlRefLibs.Names,idx] = RTW.unique(mdlRefLibs.Names);

% reduce all of the input vectors to the subset of unique entries
% new_full_paths = new_full_paths(idx);
mdlRefLibs.Paths = mdlRefLibs.Paths(idx);
mdlRefLibs.LinkPri = mdlRefLibs.LinkPri(idx);
mdlRefLibs.Precompiled = mdlRefLibs.Precompiled(idx);
mdlRefLibs.LinkOnly = mdlRefLibs.LinkOnly(idx);
mdlRefLibs.Groups = mdlRefLibs.Groups(idx);
end

otherLibs.Names = Names(~isMdlRef);
otherLibs.Paths = Paths(~isMdlRef);
otherLibs.LinkPri = LinkPri(~isMdlRef);
otherLibs.Precompiled = Precompiled(~isMdlRef);
otherLibs.LinkOnly = LinkOnly(~isMdlRef);
otherLibs.Groups = Groups(~isMdlRef);

% remove any duplicate entries if the list is longer than 1
if (length(otherLibs.Names) > 1)
% remove any duplicate entries preserving the order.
[otherLibs.Names,idx] = RTW.unique(otherLibs.Names);

% reduce all of the input vectors to the subset of unique entries
% new_full_paths = new_full_paths(idx);
otherLibs.Paths = otherLibs.Paths(idx);
otherLibs.LinkPri = otherLibs.LinkPri(idx);
otherLibs.Precompiled = otherLibs.Precompiled(idx);
otherLibs.LinkOnly = otherLibs.LinkOnly(idx);
otherLibs.Groups = otherLibs.Groups(idx);
end