classdef BuildInfoLinkObj < RTW.BuildInfoBuildObj & matlab.mixin.SetGet & matlab.mixin.Copyable
%RTW.BuildInfoLinkObj class
%   RTW.BuildInfoLinkObj extends RTW.BuildInfoBuildObj.
%
%    RTW.BuildInfoLinkObj properties:
%       Settings - Property is of type 'handle'  
%       DisplayLabel - Property is of type 'ustring'  
%       Group - Property is of type 'ustring'  
%       Options - Property is of type 'handle'  
%       Inc - Property is of type 'handle'  
%       Src - Property is of type 'handle'  
%       Other - Property is of type 'handle'  
%       Name - Property is of type 'ustring'  
%       Path - Property is of type 'ustring'  
%       Precompiled - Property is of type 'bool'  
%       LinkOnly - Property is of type 'bool'  
%       LinkPriority - Property is of type 'int32'  
%       BuildInPlace - Property is of type 'bool'  

%   Copyright 2017-2020 The MathWorks, Inc.
    
    properties (Constant=true)
        DefaultLinkPriority = 1000;
    end

    properties (Hidden=true, Transient=true)
        BuildInfoHandle = [];
        BuildOptsHandle = [];
        BuildInfoDirty = false;
    end
    
    properties (SetObservable)
        Name = 'Library';
        Precompiled = false;
        LinkOnly = false;
        LinkPriority = 1000;
        BuildInPlace = false;
    end

    methods
        function h = BuildInfoLinkObj(varargin)
        %  when the object is read in from a mat file, this is called with no args to
        %  load the class
            if (nargin == 0)
                return;
            end
            %check the args to make sure they all conform to the expected format
            [args_ok,args] = locCheckArgs(varargin{:});
            if ~(args_ok)
                DAStudio.error('RTW:buildInfo:badMethodArguments',...
                               'RTW:BuildInfo:BuildInfoLinkObj');
            end
            
            % Instantiate class
            if isempty(args.Settings)
                h.Settings = RTW.BuildInfoSettings;
            else
                h.Settings = args.Settings;
            end
            
            h.Options      = RTW.BuildInfoOptions(args.Settings);
            h.Inc          = RTW.BuildInfoModules('Includes', args.Settings);
            h.Src          = RTW.BuildInfoModules('Sources', args.Settings);
            h.Other        = RTW.BuildInfoModules('Other', args.Settings);
            h.Name         = args.Name;
            h.LinkPriority = args.LinkPriority;
            h.Path         = args.Path;
            h.Group        = args.Group;
            % although this field is not displayed, we want to set it for
            % debugging purposes
            h.DisplayLabel = 'Linkable Object';
        end
    end

    methods 
        function set.Precompiled(obj,value)
            value = logical(value); %  convert to logical
            obj.Precompiled = value;
        end

        function set.LinkOnly(obj,value)
            value = logical(value); %  convert to logical
            obj.LinkOnly = value;
        end

        function set.BuildInPlace(obj,value)
            value = logical(value); %  convert to logical
            obj.BuildInPlace = value;
        end
    end

    methods
        function val = areChildrenOrdered(~)
        %ARECHILDRENORDERED - returns true if children are returned in a specific order
        %from getChildren and getHierarchicalChildren.
            val = true;
        end
        
        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.
        %
            if nargin > 1
                [varargin{:}] = convertStringsToChars(varargin{:});
                % 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.
                exts = varargin{1}(~cellfun(@isempty,...
                                            regexp(varargin{1},'^\*\.[*\w]+$','ONCE')));
            else
                exts = {'*.h'};
            end
            
            if isempty(exts)
                exts = {'*.h'};
            end
            
            % first get the root include files
            paths = h.getSourcePaths(true);
            paths = [paths  h.getIncludePaths(true)];
            
            % 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
        end
        
        function val = getChildren(h)
        %GETCHILDREN - returns non-hierarchical child properties for DA Studio Explorer
            val = h.getHierarchicalChildren;
        end
        
        function val = getDisplayLabel(h)
        %GETDISPLAYLABEL - returns the display label for the class in the DAStudio
        %explorer.  The value is generally overridden by the derived classes.
            val = char(h.Name);
        end
        
        function Groups = getGroups(h)
        %GETGROUPS - returns a vector of Group labels used by this object
            srcGroups = h.Src.getGroups();
            incGroups = h.Inc.getGroups();
            Groups = RTW.unique([rtwprivate('convertToCell',incGroups);...
                                rtwprivate('convertToCell',srcGroups)]);
        end
        
        function val = getHierarchicalChildren(h)
        %GETHIERARCHICALCHILDREN - returns hierarchical child properties for DA Studio
        %explorer
            val = [h.Options h.Inc h.Src h.Other];
        end
        
        function varRoot = getMakeVarRoot(h)
        %GETMAKEVARROOT - internal use only
        % use the group label if it exists.  Strip off '_SRC_LIB' to get the actual
        % root.
            varRoot = upper(regexprep(h.Group,'_SRC_LIB$',''));
            if isempty(varRoot)
                varRoot = upper(regexprep(h.Name,'\..*$',''));
            end
            
        end
        
        function val = getPreferredProperties(h)
        %GETPREFERREDPROPERTIES - Returns the preferred properties for the DA Studio
        %Explorer
            
        % the base class has properties which should be in the
        % preferred properties list.  Since there is no way to call a
        % base class method from a derived class, call a surrogate
        % getPreferredProperties from the base class.
            base_props = h.getBaseBuildInfoClassProperties();
            additional_props = {'Path', 'LinkPriority', 'Precompiled', 'LinkOnly'};
            val = [base_props additional_props];
        end
        
        function val = isHierarchical(~)
        %ISHIERARCHICAL - returns true if this class has hierarchical child properties.
            val = true;
        end
        
        function updateFilePathsAndExtensions(h,varargin)
        %UPDATEFILEPATHSANDEXTENSIONS This function updates any file with a missing
        % path and/or extension.  By default it assumes the extension is '.c',
        % however a cell array of extensions to search for can be passed in.  The
        % extensions are tested for in the order they appear in the cell array.
            
        % set up the extension list to search.  If none is provided, then default
        % it to '.c'.
            extList = {'.c' '.C' '.cpp' '.CPP' '.s' '.S'};
            incExtList = {'.h' '.H'};
            
            if nargin > 1
                if iscell(varargin{1})
                    extList = varargin{1};
                else
                    extList = varargin(1);
                end
            end
            if nargin > 2
                if iscell(varargin{2})
                    incExtList = varargin{2};
                else
                    incExtList = varargin(2);
                end
            end
            
            h.Src.updateFilePathsAndExtensions(extList);
            h.Inc.updateFilePathsAndExtensions(incExtList);
        end
        
        function updateFileSeparator(h,varargin)
        %UPDATEFILESEPARATOR - this function updates the file separator on all paths in
        % the Build Info Object to the value specified in the Settings object.  This
        % includes all source and include paths, as well as file paths.  
            if (nargin > 1)
                if ~ismember(varargin{1},{'/','\'})
                    DAStudio.error('RTW:buildInfo:badFileSep');
                end
                
                if ~strcmp(varargin{1},h.Settings.FileSep)
                    h.Settings.FileSep = varargin{1};
                end
            end
            
            % because this is used as a regexp expression, the '\' must be
            % converted to '\\' because '\' is the regexp escape char.
            if strcmp(h.Settings.FileSep, '\')
                regexp_filesep = '\\';
            else
                regexp_filesep = h.Settings.FileSep;
            end
            
            % Matlabroot and other special dirs are in the settings object.  these
            % needs to be updated for proper makefile generation.
            h.Settings.Matlabroot = regexprep(h.Settings.Matlabroot,...
                                              '[\\/]',regexp_filesep);            
            h.Settings.LocalAnchorDir = regexprep(h.Settings.LocalAnchorDir,...
                                                  '[\\/]',regexp_filesep);
            
            h.Path = regexprep(h.Path,'[\\/]',regexp_filesep);
            
            % update the include and source modules for this link object
            h.Src.updateFileSeparator();
            h.Inc.updateFileSeparator();
            h.Other.updateFileSeparator();
        end
    end

    methods (Hidden)
        function mergeFilesAndPaths(h, linkObj)
        % MERGEFILESANDPATHS - internal use only
            
            if ~isa(linkObj,'RTW.BuildInfoLinkObj')
                DAStudio.error('RTW:buildInfo:badMethodArguments',...
                               'RTW.BuildInfoLinkObj.mergeFilesAndPaths');
            end
            
            [srcFiles, srcGroups] = linkObj.getSourceFiles(true,true);
            [srcNames, srcPaths] = locSplitFiles(srcFiles);
            h.addSourceFiles(srcNames,srcPaths,srcGroups);
            [srcPaths, srcPathGroups] = linkObj.getSourcePaths(true);
            h.addSourcePaths(srcPaths,srcPathGroups);
            
            [incFiles, incGroups] = linkObj.getIncludeFiles(true,true);
            [incNames, incPaths] = locSplitFiles(incFiles);
            h.addIncludeFiles(incNames,incPaths,incGroups);
            [incPaths, incPathGroups] = linkObj.getIncludePaths(true);
            h.addIncludePaths(incPaths,incPathGroups);
            
            [nbFiles, nbGroups] = linkObj.getNonBuildFiles(true,true);
            [nbNames, nbPaths] = locSplitFiles(nbFiles);
            h.addNonBuildFiles(nbNames,nbPaths,nbGroups);
        end
        
        function replacePathString(h, oldStr, newStr)
        %REPLACEPATHSTRING - undocumented internal function

        % update the include and source modules for this link object
            h.Src.replacePathString(oldStr,newStr);
            h.Inc.replacePathString(oldStr,newStr);
            h.Other.replacePathString(oldStr,newStr);
            
            oldStr = regexptranslate('escape',oldStr);
            newStr = regexptranslate('escape',newStr);
            h.Path = regexprep(h.Path,oldStr,newStr);
        end
    end
end

function [args_ok,args] = locCheckArgs(varargin)

    args_ok           = true;
    args.Name         = '';
    args.Path         = '';
    args.LinkPriority = 1000;
    args.Group        = '';
    args.Settings     = [];

    % if a Name is provided, then a path must also be provided.
    if (nargin < 2)
        args_ok = false;
        return;
    end

    % First arg is the Name
    if ~ischar(varargin{1})
        args_ok = false;
        return;
    end
    args.Name = varargin{1};

    % second arg is the Path
    if ~ischar(varargin{2})
        args_ok = false;
        return;
    end
    args.Path = varargin{2};

    % third arg is the Link Priority
    if (nargin >= 3)
        if ~isnumeric(varargin{3})
            args_ok = false;
            return;
        end
        args.LinkPriority = varargin{3};
    end

    % Fourth arg is the group
    if (nargin >= 4)
        if ~ischar(varargin{4})
            args_ok = false;
            return;
        end
        args.Group = varargin{4};
    end

    % final arg is a handle to a settings object, or [].
    if (nargin >= 5)
        if ~isempty(varargin{5}) && ~isa(varargin{5}, 'RTW.BuildInfoSettings')
            args_ok = false;
            return;
        end
        args.Settings = varargin{5};
    end
end

function [fnames, paths] = locSplitFiles(files)
    [paths, names, exts] = cellfun(@fileparts, files, 'UniformOutput', false);
    fnames = strcat(names, exts);
end
