classdef BuildInfoModules < RTW.BuildInfoClass & handle
%RTW.BuildInfoModules class
%   RTW.BuildInfoModules extends RTW.BuildInfoClass.
%
%    RTW.BuildInfoModules properties:
%       Settings - Property is of type 'handle'  
%       DisplayLabel - Property is of type 'ustring'  
%       Group - Property is of type 'ustring'  
%       Paths - Property is of type 'handle vector'  
%       Files - Property is of type 'handle vector'  

%   Copyright 2017-2020 The MathWorks, Inc.

    properties
        Paths = [];
        Files = [];
    end

    methods
        function h = BuildInfoModules(varargin)
        %  when the object is read in from a mat file, this is called with no args to
        %  load the class
            if (nargin == 0)
                h = [];
                return;
            end
            
            %check the args to make sure they all conform to the expected format
            [args_ok, DisplayLabel, Settings] = locCheckArgs(varargin{:});
            if ~(args_ok)
                DAStudio.error('RTW:buildInfo:badMethodArguments',...
                               'RTW:BuildInfo:BuildInfoModules');
            end
            
            % Instantiate class
            h.Settings = Settings;
            % override the base class display label. 
            h.DisplayLabel = DisplayLabel;
        end
    end

    methods
        function val = getChildren(h)
        %GETCHILDREN - returns non-hierarchical child properties for DA Studio Explorer
            val = [h.Paths;h.Files];
        end

        function dlgstruct = getDialogSchema(hSrc, ~)
        %GETDIALOGSCHEMA - returns the dialog struct for display in a DDG
            
            dlgstruct = [];
            
            %=======================================================================
            % The preprocessor defines are displayed in a table
            %=======================================================================
            widget = [];
            widget.Name = string(message('coder_compile:buildInfo:FilesLabel'));
            widget.Type = 'table';
            widget.Enabled = true;
            widget.Size = [length(hSrc.Files) 3];
            widget.Grid = true;
            widget.ColHeader = {string(message('coder_compile:buildInfo:GroupLabel')),...
                                string(message('coder_compile:buildInfo:FileLabel')),...
                                string(message('coder_compile:buildInfo:PathLabel'))};
            widget.HeaderVisibility = [0 1];
            
            if (length(hSrc.Files) > 1)
                widget.Data(:,1) = get(hSrc.Files,'Group');
                widget.Data(:,2) = get(hSrc.Files,'FileName');
                widget.Data(:,3) = get(hSrc.Files,'Path');
            else
                widget.Data(1,1) = {get(hSrc.Files,'Group')};
                widget.Data(1,2) = {get(hSrc.Files,'FileName')};
                widget.Data(1,3) = {get(hSrc.Files,'Path')};
            end
            
            widget.ToolTip = string(message('coder_compile:buildInfo:FileToolTip'));
            widget.Mode = 1;
            Files = widget;
            
            %=======================================================================
            % The Compile Flags are displayed in a table
            %=======================================================================
            widget = [];
            widget.Name = string(message('coder_compile:buildInfo:PathsLabel'));
            widget.Type = 'table';
            widget.Enabled = true;
            widget.Size = [length(hSrc.Paths) 2];
            widget.Grid = true;
            widget.ColHeader = {string(message('coder_compile:buildInfo:GroupLabel')),...
                                string(message('coder_compile:buildInfo:PathLabel'))};
            widget.HeaderVisibility = [0 1];
            
            if (length(hSrc.Paths) > 1)
                widget.Data(:,1) = get(hSrc.Paths,'Group');
                widget.Data(:,2) = get(hSrc.Paths,'Value');
            else
                widget.Data(1,1) = {get(hSrc.Paths,'Group')};
                widget.Data(1,2) = {get(hSrc.Paths,'Value')};
            end
            
            widget.ToolTip = string(message('coder_compile:buildInfo:PathToolTip'));
            widget.Mode = 1;
            Paths = widget;
            
            %=======================================================================
            % layout the widgets
            %=======================================================================
            Files.RowSpan = [1 1];
            Files.ColSpan = [1 3];
            Paths.RowSpan = [2 2];
            Paths.ColSpan = [1 3];
            
            group.Name = hSrc.getDisplayLabel;
            group.Type = 'group';
            group.LayoutGrid = [2 2];
            group.Items = {Files, Paths};
            
            dlgstruct.DialogTitle =  hSrc.getDisplayLabel;
            dlgstruct.Items = {group};
        end
        
        function Groups = getGroups(h)
        %GETGROUPS - returns a vector of Group labels used by this object
            
            fileGroups = get(h.Files,'Group');
            fileGroups = rtwprivate('convertToCell',fileGroups);
            
            dirGroups  = get(h.Paths,'Group');
            dirGroups  = rtwprivate('convertToCell',dirGroups);
            
            Groups = RTW.unique([rtwprivate('convertToCell',fileGroups);...
                                rtwprivate('convertToCell',dirGroups)]);
        end

        function val = getHierarchicalChildren(h)
        %GETHIERARCHICALCHILDREN - returns hierarchical child properties for DA Studio
        %explorer
            if strcmp(h.DisplayLabel, 'Includes')
                val = [];
            else
                val = h.Files;
            end
        end
        
        function val = getPreferredProperties(~)
        %GETPREFERREDPROPERTIES - Returns the preferred properties for the DA Studio
        %Explorer
            val = {};
        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'.
            if nargin > 1
                [varargin{:}] = convertStringsToChars(varargin{:});
            end

            if nargin < 2
                extList = {'.c' '.C'};
            else
                if iscell(varargin{1})
                    extList = varargin{1};
                else
                    extList = varargin(1);
                end
            end
            
            fnames = get(h.Files,'FileName');
            if isempty(fnames)
                fnames = {};
            else
                if ~iscell(fnames)
                    fnames = {fnames};
                end
            end
            
            fpaths = get(h.Files,'Path');
            if (isempty(fpaths) && ~ischar(fpaths))
                fpaths = {};
            else
                if ~iscell(fpaths)
                    fpaths = {fpaths};
                end
            end
            
            % this algorithm builds up a vector of filenames and paths, then sets them
            % all in one call.  In the event that a file name or path goes unresolved,
            % the original value should be retained. So the vectors are initialized to
            % the current values, and over-written when the file is found.
            finalNames = fnames;
            finalPaths = fpaths;
            
            paths = get(h.Paths,'Value');
            if isempty(paths)
                paths = {};
            else
                if ~iscell(paths)
                    paths = {paths};
                end
            end
            % for checking the existence of a file, or doing dirs, the paths need
            % $(MATLAB_ROOT) replaced with the hard-coded path.  For both path vectors,
            % a 'nomlr' version is needed.  When assigning the value, the original
            % version with $(MATLAB_ROOT) is used, but the file system checks use the
            % nomlr version.
            specialDirs = h.Settings.getSpecialDirsAndTokens('includeRegexpPats',...
                                                             true);
            sToks = regexptranslate('escape',specialDirs.toks); 
            nomlr_paths = regexprep(paths, sToks, specialDirs.regexpPats);
            nomlr_fpaths = regexprep(fpaths, sToks, specialDirs.regexpPats);
            
            % loop through the paths and check for the files
            for i=1:length(paths)
                
                dirFiles = dir(nomlr_paths{i});
                
                % save the dir listings for possible re-use below.  The biggest
                % performance hit is checking the external file system, so if
                % that can be minimized, significant gains are achieved.
                dirNames{i} = {dirFiles(:).name}; %#ok<AGROW>
                
                % for each extension do a dir *.EXT, and compare it to the filenames
                for j=1:length(extList)
                    % add the current extension to the filenames
                    fnamesExt = strcat(fnames,extList{j});
                    
                    [tf,fIdx] = intersect(fnamesExt,dirNames{i});
                    if ~isempty(tf)
                        for k=1:length(fIdx)
                            if isempty(fpaths{fIdx(k)}) || ...
                                    (strcmp(fpaths{fIdx(k)},paths{i})==1)
                                finalNames{fIdx(k)} = fnamesExt{fIdx(k)};
                                finalPaths{fIdx(k)} = paths{i};
                                % since the filename and extension have been updated, the
                                % entry in the fname list needs to be changed to something
                                % that won't resolve to a file name when the extension is
                                % appended, so that it will never be matched again.  Since
                                % '*' is an illegal starting char for a filename, it will
                                % never get returned from a dir listing.
                                fnames{fIdx(k)} = '*.';
                                fpaths{fIdx(k)} = '*/';
                            end
                        end
                    end
                end 
            end
            
            % the only files that don't have the extension updated are the ones that
            % had an explicit path that differs from the paths in the paths vector
            % specified when inserted.  For those a simple exist check can be done.
            for i=1:length(fnames)
                if isempty(strfind(fnames{i},'.'))
                    for j=1:length(extList)
                        fullname = [nomlr_fpaths{i} filesep fnames{i} extList{j}];
                        if exist(fullname,'file')
                            finalNames{i} = [fnames{i} extList{j}];
                            break;
                        end
                    end
                end
            end
            
            % finally, if the filename has an extension, but the path is still empty,
            % the path needs to be found walk through the paths and check for the file.
            
            emptyPathIdx = find(strcmp(fpaths,'')==1);
            if ~isempty(emptyPathIdx)
                sort(emptyPathIdx);
                for i=1:length(paths)
                    % the intersection contains the filenames that are in that path
                    emptyPathFnames = fnames(emptyPathIdx);
                    [tf,fIdx] = intersect(emptyPathFnames,dirNames{i});
                    if ~isempty(tf)
                        foundIdx = emptyPathIdx(fIdx);
                        finalPaths(foundIdx) = paths(i);
                        emptyPathIdx = setdiff(emptyPathIdx,foundIdx);
                    end
                end
            end
            
            % everything that can be resolved, has been resolved.  Set the vectors.
            set(h.Files,{'FileName'},finalNames);
            set(h.Files,{'Path'},finalPaths);
        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 ~any(strcmp(varargin{1}, {'/', '\'} ))
                    DAStudio.error('RTW:buildInfo:badFileSep');
                end
                
                % if the new filesep is the same as the old filesep, then don't update
                % the paths since it is a somewhat time-consuming operation.
                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
            
            % first update the files
            fpaths = get(h.Files, 'Path');
            if ~isempty(fpaths)
                if ~iscell(fpaths)
                    fpaths = {fpaths};
                end
                
                fpaths = regexprep(fpaths,'[\\/]',regexp_filesep);
                set(h.Files,{'Path'},fpaths);
            end
            
            % next update all the include/source paths
            spaths =  get(h.Paths, 'Value');
            if ~isempty(spaths)
                if ~iscell(spaths)
                    spaths = {spaths};
                end
                
                spaths = regexprep(spaths,'[\\/]',regexp_filesep);    
                set(h.Paths, {'Value'}, spaths);
            end
        end
    end

    methods (Hidden)
        function replacePathString(h, oldStr, newStr)
        %REPLACEPATHSTRING - undocumented internal function
            oldStr = regexptranslate('escape',oldStr);
            newStr = regexptranslate('escape',newStr);
            
            % first update the files
            fpaths = get(h.Files, 'Path');
            if ~isempty(fpaths)
                if ~iscell(fpaths)
                    fpaths = {fpaths};
                end
                
                fpaths = regexprep(fpaths,oldStr,newStr);
                set(h.Files,{'Path'},fpaths);
            end
            
            % next update all the include/source paths
            spaths =  get(h.Paths, 'Value');
            if ~isempty(spaths)
                if ~iscell(spaths)
                    spaths = {spaths};
                end
                
                spaths = regexprep(spaths,oldStr,newStr);
                set(h.Paths, {'Value'}, spaths);
            end
        end
    end
end

function [args_ok,DisplayLabel,Settings]= locCheckArgs(varargin)
    args_ok      = true;
    DisplayLabel = 'File & Path Info';
    Settings     = [];

    % First arg is the starting value
    if ~ischar(varargin{1})
        args_ok = false;
        return;
    end
    DisplayLabel = varargin{1};

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