classdef BuildInfoModules < RTW.BuildInfoClass
%

%   Copyright 2017-2023 The MathWorks, Inc.

    properties
        Paths = RTW.BuildInfoString.empty;
        Files = RTW.BuildInfoFile.empty;
    end

    methods
        function set.Paths(this, val)
            if isempty(val)
                val = RTW.BuildInfoString.empty;
            end
            mustBeA(val, 'RTW.BuildInfoString');
            this.Paths = val;
        end

        function set.Files(this, val)
            if isempty(val)
                val = RTW.BuildInfoFile.empty;
            end
            mustBeA(val, 'RTW.BuildInfoFile');
            this.Files = val;
        end

        function h = BuildInfoModules(label)
            arguments
                label {mustBeText} = 'File & Path Info';
            end

            % override the base class display label.
            h.DisplayLabel = label;
        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;
            itemFiles = 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;
            itemPaths = widget;
            
            %=======================================================================
            % layout the widgets
            %=======================================================================
            itemFiles.RowSpan = [1 1];
            itemFiles.ColSpan = [1 3];
            itemPaths.RowSpan = [2 2];
            itemPaths.ColSpan = [1 3];
            
            group.Name = hSrc.getDisplayLabel;
            group.Type = 'group';
            group.LayoutGrid = [2 2];
            group.Items = {itemFiles, itemPaths};
            
            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, settings, extList)
        %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.
            arguments
                h RTW.BuildInfoModules
                settings RTW.BuildInfoSettings
                extList = {'.c' '.C'};
            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.
            nomlr_paths = settings.expandPaths(paths);
            nomlr_fpaths = settings.expandPaths(fpaths);
            
            % 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'}, settings.tokenizePaths(finalPaths));
        end
        
        function updateFileSeparator(h, fsep)
        %UPDATEFILESEPARATOR - this function updates the file separator on all paths in
        % the Build Info Object to the value specified.  This includes all source and
        % include paths, as well as file paths.  
         
            if ~any(strcmp(fsep, {'/', '\'} ))
                error(message('RTW:buildInfo:badFileSep'));
            end

            % first update the files
            fpaths = get(h.Files, 'Path');
            if ~isempty(fpaths)
                if ~iscell(fpaths)
                    fpaths = {fpaths};
                end
                
                switch fsep
                    case '/'
                        fpaths = strrep(fpaths, '\', '/');
                    case '\'
                        fpaths = strrep(fpaths, '/', '\');
                end
                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
                
                switch fsep
                    case '/'
                        spaths = strrep(spaths, '\', '/');
                    case '\'
                        spaths = strrep(spaths, '/', '\');
                end
                set(h.Paths, {'Value'}, spaths);
            end
        end

    end

    methods (Hidden)
        function replacePathString(h, oldStr, newStr)
        %REPLACEPATHSTRING - undocumented internal function
            
            % first update the files
            fpaths = {h.Files.Path}'; % turn into column vector
            if ~isempty(fpaths)
                fpaths = coder.make.internal.replacePathString(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 = coder.make.internal.replacePathString(spaths, oldStr, newStr);
                set(h.Paths, {'Value'}, spaths);
            end
        end
    end
end



