classdef BuildInfoSettings < RTW.BuildInfoClass
%RTW.BuildInfoSettings class
%   RTW.BuildInfoSettings extends RTW.BuildInfoClass.
%

%   Copyright 2017-2023 The MathWorks, Inc.

    properties (Transient=true)
        FileSep char = filesep
    end
    
    properties
        Matlabroot = '';        
        LocalAnchorDir = '';
        BuildInfoOutputFolder = '';
        TargetInfo = [];
        DisablePackNGo = false;
        DisableReportInPackNGo = false;
        TargetProvidesMain = false;
        HasUnresolvedExtensions = false;
        HasUnresolvedPaths = false;
    end

    properties (Hidden)
        % coder.make.Rootfolder objects used in this build
        MLRootFolder       coder.make.RootFolder    % MATLAB_ROOT
        StartDirRootFolder coder.make.RootFolder    % START_DIR
        RootFolders = coder.make.RootFolder.empty;  % Custom folders
    end

    properties (Access=private)    
       CompilerRequirements = coder.make.internal.CompilerRequirements;
    end

    methods
        function this = BuildInfoSettings()
            %BUILDINFOSETTINGS Class constructor (for RTW.BuildInfoSettings).
            % Instantiate class
            % override the base class display label.
            this.DisplayLabel = 'Settings';

            % default the FileSep property to whatever filesep for this platform is
            this.FileSep = filesep;

            % set the matlab root in the buildinfo to the current matlab install
            % dir
            this.updateMatlabRoot();

            this.LocalAnchorDir = '';
            this.BuildInfoOutputFolder = '';
            this.HasUnresolvedExtensions = false;    
            this.HasUnresolvedPaths      = false;
        end

        function val = getCompilerRequirements(this)
            val = this.CompilerRequirements;
        end

        function setCompilerRequirements(this, val)
            this.CompilerRequirements = val;
        end

        function set.Matlabroot(this,value)
            this.Matlabroot = value;
            this.MLRootFolder = coder.make.RootFolder('$(MATLAB_ROOT)', value); %#ok<MCSUP>
        end

        function set.LocalAnchorDir(this,val)
            this.LocalAnchorDir = val;
            if isempty(val)
                % START_DIR is not initialized, do not allow RootFolder to append pwd
                appendPWDToRelativePaths = false;
            else
                appendPWDToRelativePaths = true;
            end
            this.StartDirRootFolder = coder.make.RootFolder('$(START_DIR)',  val, appendPWDToRelativePaths); %#ok<MCSUP>            
        end

        function set.DisablePackNGo(this,value)
            value = logical(value); %  convert to logical
            this.DisablePackNGo = value;
        end

        function set.DisableReportInPackNGo(this,value)
            value = logical(value); %  convert to logical
            this.DisableReportInPackNGo = value;
        end

        function set.TargetProvidesMain(this,value)
            value = logical(value); %  convert to logical
            this.TargetProvidesMain = value;
        end

        function set.HasUnresolvedExtensions(this,value)
            value = logical(value); %  convert to logical
            this.HasUnresolvedExtensions = value;
        end

        function set.HasUnresolvedPaths(this,value)
            value = logical(value); %  convert to logical
            this.HasUnresolvedPaths = value;
        end
    end

    methods
        function updateFileSeparator(this, fileSeparator)
            this.FileSep = fileSeparator;
            this.Matlabroot = regexprep(this.Matlabroot, '[\\/]', this.FileSep);
            this.LocalAnchorDir = regexprep(this.LocalAnchorDir, '[\\/]', this.FileSep);
        end

        function val = areChildrenOrdered(~)
        %ARECHILDRENORDERED - returns true if children are returned in a specific order
        %from getChildren and getHierarchicalChildren.
            val = true;
        end
        
        function val = getChildren(~)
        %GETCHILDREN - returns non-hierarchical child properties for DA Studio Explorer
            val = [];
        end
        
        function dlgstruct = getDialogSchema(~, ~)
        %GETDIALOGSCHEMA - returns the dialog struct for display in a DDG
            dlgstruct = [];
        end
        
        function val = getHierarchicalChildren(~)
        %GETHIERARCHICALCHILDREN - returns hierarchical child properties for DA Studio
        %explorer
            val = [];
        end
        
        function val = getPreferredProperties(~)
        %GETPREFERREDPROPERTIES - Returns the preferred properties for the DA Studio
        %Explorer
            val = {'FileSep' 'Matlabroot' 'LocalAnchorDir'};
        end
        
        function val = isHierarchical(~)
        %ISHIERARCHICAL - returns true if this class has hierarchical child properties.
            val = false;
        end
     
        function addInstructionSetRequirements(this, value)
            if iscell(value) %a cell of string/chararray
                len = length(value);
                for i = 1:len
                    this.CompilerRequirements.InstructionSetRequirements{end+1} = value{i};
                end
            else %a string/char array 
                this.CompilerRequirements.InstructionSetRequirements{end+1} = value;
            end
            this.CompilerRequirements.InstructionSetRequirements = unique(this.CompilerRequirements.InstructionSetRequirements);
        end   
    end

    methods (Hidden)
        function addRootFoldersToBuildInfo(this, rootFolders)
            % adds or updates the values of the Root Folders stored in the BuildInfo object
            % update the subset of stored root folders corresponding to
            % the input argument
            currentRootFolders = this.RootFolders;
            keepFolders = ~ismember({currentRootFolders.Token}, { rootFolders.Token });
            this.RootFolders = [currentRootFolders(keepFolders), rootFolders];
        end

        function rootFolders = getRootFolders(this, processCustomFolders, ...
                requestedCustomTokens)
            % get the BuildInfo standard root folders and 
            % requested custom root folders
            arguments
                this
                processCustomFolders  (1,1) logical = false
                requestedCustomTokens (1,:) = {}
            end

            % get the requested custom root folders
            if processCustomFolders
                customRootFolders = getBuildInfoRootFolders(requestedCustomTokens);
                rootFolders = [this.MLRootFolder, this.StartDirRootFolder, customRootFolders];
            else
                rootFolders = [this.MLRootFolder, this.StartDirRootFolder];
            end
        end

        function [rc, matlabRoot, startDir] = getSpecialDirsAndTokens(this, opts)
            %GETSPECIALDIRSANDTOKENS - Undocumented internal function
            % TO BE RETIRED, see g3147795
            arguments
                this RTW.BuildInfoSettings
                opts.includeEmptyNames  logical = false
                opts.includeAltNames    logical = false
                opts.includeRegexpPats  logical = false
                opts.relativeAnchorOverride {mustBeText} = ''
            end

            % For certain directories, the paths are replaced with makefile tokens.
            % Note that the client and Local anchordirs map to the same token.
            % This is intentional so that parallel builds generate the same
            % makefile as sequential builds.

            if coder.make.internal.featureOn('BuildInfoRootFolders') && ...
                    ~isempty(this.RootFolders)
                % only process the root folders in use by this BuildInfo
                processCustomFolders = true;
                usedRootFolderTokens = {this.RootFolders.Token};
            else
                processCustomFolders = false;
                usedRootFolderTokens = [];
            end
            rootFolders = this.getRootFolders(processCustomFolders, usedRootFolderTokens);

            % NOTE: the matlabroot token must be the first in the list for proper
            % makefile generation (see genMakefileAndBuild usage of special dirs).           
            mr = this.MLRootFolder;
            sd = this.StartDirRootFolder;
            
            % If the relativeAnchorOverride is not an empty character array the
            % user is requesting that the START_DIR token be replaced by a
            % specified relative path. Ensure the tokens are mapped to that
            % relative path.
            if ~isempty(opts.relativeAnchorOverride)
                sd = coder.make.RootFolder(sd.Token, opts.relativeAnchorOverride, true, true);
                startDirIdx = 2;
                rootFolders(startDirIdx) = sd;
            end
            
            dirs = { rootFolders.Path };
            vars = { rootFolders.Variable };
            toks = { rootFolders.Token };
            tmfToks = { rootFolders.TMFToken };
                        
            if ~opts.includeEmptyNames
                % strip out any empty dirs.
                emptyIdx = strcmp(dirs,''); % Equivalent to ismember, but faster. Do not modify.
                dirs     = dirs(~emptyIdx);
                vars     = vars(~emptyIdx);
                toks     = toks(~emptyIdx);
                tmfToks  = tmfToks(~emptyIdx);
            end           

            rc.dirs    = dirs;
            rc.vars    = vars;
            rc.toks    = toks;
            rc.tmfToks = tmfToks;
            
            if opts.includeAltNames
                rc.altNames = coder.make.internal.transformPaths(rc.dirs, 'ignoreErrors',true);
            end
            
            if opts.includeRegexpPats
                rc.regexpPats = regexptranslate('escape',rc.dirs);
                if opts.includeAltNames
                    rc.regexpAltNamePats = regexptranslate('escape',rc.altNames);
                end
            end
            
            matlabRoot = locCopyStructByIndex(rc,strcmp(mr.Path, rc.dirs));
            startDir = locCopyStructByIndex(rc,strcmp(sd.Path, rc.dirs));
        end

        function updateMatlabRoot(this)
        % UPDATEMATLABROOT - updates the MATLAB root to the current install
        % directory
        % matlabroot may return the incorrect case, so we need to transform it to
        % the full name
            mlr = coder.make.internal.transformPaths(matlabroot,'pathType','full');
            this.Matlabroot = mlr;
        end

        function updateRootFolders(this, updatedRootFolders)
        % update the Root Folders stored in the BuildInfo object
            this.RootFolders = updatedRootFolders;
        end

        function tokenizedPaths = tokenizePaths(this, originalPaths)
            % TOKENIZEPATHS uses the root folder objects to return the tokenized form
            % of the paths provided as input.
            % When a root folder is used to tokenize a path, it is added to the list of
            % root folders currently in use

            if coder.make.internal.featureOn('BuildInfoRootFolders')
                processCustomFolders = true;
            else
                processCustomFolders = false;
            end

            % Query for the latest root folder values corresponding to the
            % root folder tokens required for the input paths
            usedRootFolderTokens = {};
            rootFolders = this.getRootFolders(processCustomFolders, usedRootFolderTokens);
            rootTokens = { rootFolders.Token };
            rPaths = { rootFolders.PathPattern };

            % Look ahead assertion to match file separator or end of input text
            endOfPathExpr = "(?=\\|\/|$)";

            % g2069863: if matlabroot is "/MATLAB", do not replace paths like
            % "/MATLAB workingFolder"
            rPathsExpr = string(rPaths)+endOfPathExpr;

            if ispc
                tokenizedPaths = regexprep(originalPaths, rPathsExpr, rootTokens, 'ignorecase');
            else
                tokenizedPaths = regexprep(originalPaths, rPathsExpr, rootTokens);
            end

            if coder.make.internal.featureOn('BuildInfoRootFolders')
                % Store the used Root Folders in the BuildInfo object
                rootFolders = rootFolders(3:end);  % Do not add MATLAB_ROOT or START_DIR => Start at 3.
                r = regexp(strjoin(tokenizedPaths), { rootFolders.TokenPattern });
                usedRootFoldersIdcs = ~cellfun(@isempty, r, 'UniformOutput', true);
                this.addRootFoldersToBuildInfo(rootFolders(usedRootFoldersIdcs));
            end
        end

        function expandedPaths = expandPaths(this, originalPaths)
            % EXPANDPATHS uses the root folder objects marked as in current use by
            % BuildInfo to expand the input paths into their absolute form.
            % When a root folder is used, its value (the associated path) is updated in
            % the list of root folders currently in use.

            usedRootFolderTokens = [];
            if coder.make.internal.featureOn('BuildInfoRootFolders')
                % From the root folder tokens currently being used by BuildInfo,
                % collect only those that are required to expand the input paths
                r = regexp(strjoin(originalPaths), { this.RootFolders.TokenPattern });
                usedRootFoldersIdcs = ~cellfun(@isempty, r, 'UniformOutput', true);
                usedRootFolderTokens = { this.RootFolders(usedRootFoldersIdcs).Token };
            end

            if ~isempty(usedRootFolderTokens)
                processCustomFolders = true;
            else
                processCustomFolders = false;
            end

            % Query for the latest root folder values corresponding to the
            % root folder tokens required for the input paths
            rootFolders = this.getRootFolders(processCustomFolders, usedRootFolderTokens);

            % Expand the input paths
            rTokens = { rootFolders.TokenPattern };
            rPaths = { rootFolders.PathPattern };
            expandedPaths = regexprep(originalPaths, rTokens, rPaths, 'ignorecase');

            if coder.make.internal.featureOn('BuildInfoRootFolders')
                % Update the values of the Root Folders stored in the BuildInfo object
                rootFolders = rootFolders(3:end); % Do not add MATLAB_ROOT or START_DIR
                this.addRootFoldersToBuildInfo(rootFolders(usedRootFoldersIdcs));
            end
        end
    end


    methods (Static, Access = public)
        function this = loadobj(s)
            % LOADOBJ
            %   Handle differences between objects serialized in earlier
            %   releases.
            if isstruct(s)
                this = RTW.BuildInfoSettings;
                fields = fieldnames(s);
                for k = 1:numel(fields)
                    if isprop(this, fields{k})
                        this.(fields{k}) = s.(fields{k});
                    end
                end
            else
                this = s;
            end
            
            if isstruct(this.CompilerRequirements)
               % If CompilerRequirements was previously a struct, then 
               % convert to MCOS.  
               
               % Cache the serialized value
               compilerRequirementsStruct = this.CompilerRequirements;
               
               % Construct a new coder.make.internal.CompilerRequirements object and copy
               % the fields that are present.
               this.CompilerRequirements = coder.make.internal.CompilerRequirements;
               oldFields = fieldnames(compilerRequirementsStruct);
               newFields = fieldnames(this.CompilerRequirements);
               removedFields = setdiff(oldFields, newFields);
               fields = setdiff(oldFields, removedFields);
               for currField = fields(:)'  % make sure is row
                   % Currently all permitted fields in the struct from
                   % R2018b/R2019a/R2019b are properties in the new MCOS
                   % object, so we do not currently need to validate that
                   % the property exists.
                   this.CompilerRequirements.(currField{:}) = compilerRequirementsStruct.(currField{:});
               end
               
               % Intel SIMD information has been converted from a serial
               % of boolean flags to an array in R2021b
               if isfield(compilerRequirementsStruct,'supportAVX512Intrinsic') && ...
                       compilerRequirementsStruct.supportAVX512Intrinsic
                   this.addInstructionSetRequirements('AVX512F');
               elseif isfield(compilerRequirementsStruct,'supportAVXIntrinsic') && ...
                       compilerRequirementsStruct.supportAVXIntrinsic
                   this.addInstructionSetRequirements('AVX2');
               elseif isfield(compilerRequirementsStruct,'supportSSE4Intrinsic') && ...
                   compilerRequirementsStruct.supportSSE4Intrinsic
                   this.addInstructionSetRequirements('SSE4.1');
               end

            end
        end
    end
end


function ss = locCopyStructByIndex(s, condition)
    if any(condition)
        idx = condition;
    else
        ss = {};
        return
    end

    fieldList = fieldnames(s);
    for i = 1:numel(fieldList)
        field = fieldList{i};
        ss.(field) = s.(field){idx};
    end
end

% LocalWords:  TOKENIZEPATHS ignorecase EXPANDPATHS Rootfolder
