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

%   Copyright 2017-2023 The MathWorks, Inc.

    properties
        Defines = RTW.BuildInfoKeyValuePair.empty;
        CompileFlags = RTW.BuildInfoString.empty;
        ArchiveFlags = RTW.BuildInfoString.empty;
        LinkFlags = RTW.BuildInfoString.empty;
    end

    properties(Hidden)
        Undefines = RTW.BuildInfoString.empty;
    end

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

        function set.Undefines(this, val)
            if isempty(val)
                val = RTW.BuildInfoString.empty;
            end
            mustBeA(val, 'RTW.BuildInfoString')
            this.Undefines = val;
        end
        
        function set.CompileFlags(this, val)
            if isempty(val)
                val = RTW.BuildInfoString.empty;
            end
            mustBeA(val, 'RTW.BuildInfoString')
            this.CompileFlags = val;
        end
        
        function set.ArchiveFlags(this, val)
            if isempty(val)
                val = RTW.BuildInfoString.empty;
            end
            mustBeA(val, 'RTW.BuildInfoString')
            this.ArchiveFlags = val;
        end
        
        function set.LinkFlags(this, val)
            if isempty(val)
                val = RTW.BuildInfoString.empty;
            end
            mustBeA(val, 'RTW.BuildInfoString')
            this.LinkFlags = val;
        end

        function h = BuildInfoOptions(varargin)
            % override the base class display label. 
            h.DisplayLabel = string(message('coder_compile:buildInfo:OptionsLabel'));
        end
    end

    methods
        function addOptions(h, type, options, groups, params)
            % addOptions - adds one or more options to the CompileFlags,
            % Defines, Undefines or LinkFlags vector.

            arguments
                h (1,1) RTW.BuildInfoOptions
                type (1, :) char ...
                    {mustBeMember(type, ...
                    {'Defines', 'Undefines', 'CompileFlags', 'LinkFlags', 'ArchiveFlags'})}
                options  (1, :) {mustBeText}
                groups (1, :) string ...
                    {mustBeText, i_mustBeCompatibleSize(options, groups)}
                params.SharedLibraryMerge (1, 1) logical = true
                params.SourceFileHandles (1, :) RTW.BuildInfoFile = RTW.BuildInfoFile.empty;
            end

            % If necessary, convert to cell array (preserve trailing
            % whitespace)
            if ischar(options)
                options = {options};
            end
            
            if isempty(groups)
                groups = "";
            end
            
            if numel(groups)==1 && numel(options) > 1
                groups(1:length(options)) = groups;
            end

            % Remove empty options
            emptyIdx = cellfun(@isempty, options);
            options = options(~emptyIdx);
            groups = groups(~emptyIdx);
    
            if isempty(options)
                return;
            end
            
            groups = cellstr(groups);
            
            if strcmp(type, 'Defines')
                
                % Filter out keys already present
                [keys, vals, groups] = h.resolveDefines(options, groups);
                cur_keys = get(h.Defines, 'Key');
                if ~isempty(cur_keys)
                    idx = ~ismember(keys, cur_keys);
                    keys = keys(idx);
                    vals = vals(idx);
                    groups = groups(idx);
                end

                cnt = length(keys);
                if cnt==0
                    return
                end

                newOpt = RTW.BuildInfoKeyValuePair.empty;
                for i=1:cnt
                    % Create a new key-value pair to hold the option, and
                    % add it to the list.
                    sourceFileHandles = params.SourceFileHandles;
                    numOptions = max(1, length(sourceFileHandles));
                    for kk = 1:numOptions
                        if isempty(sourceFileHandles)
                            handle = RTW.BuildInfoFile.empty;
                        else
                            handle = sourceFileHandles(kk);
                        end
                        newOpt(end+1) = ...
                            RTW.BuildInfoKeyValuePair(keys{i}, ...
                            vals{i}, ...
                            groups{i},...
                            keys{i}, ...
                            params.SharedLibraryMerge, ...
                            handle); %#ok<AGROW>
                    end
                end

            else
                % Filter out options already present
                cur_options = get(h.(type), 'Value');
                if ~isempty(cur_options)
                    idx = ~ismember(options,cur_options);
                    % reduce all of the input vectors to the subset of new entries.
                    options   = options(idx);
                    groups    = groups(idx);
                end

                cnt = length(options);
                if cnt==0
                    return
                end

                newOpt = RTW.BuildInfoString.empty;
                for i=1:cnt
                    % Create a new string object to hold the option, and
                    % add it to the list, the type is used for the display
                    % label.
                    sourceFileHandles = params.SourceFileHandles;
                    numOptions = max(1, length(sourceFileHandles));
                    for kk = 1:numOptions
                        if isempty(sourceFileHandles)
                            handle = RTW.BuildInfoFile.empty;
                        else
                            handle = sourceFileHandles(kk);
                        end
                        newOpt(end+1) = RTW.BuildInfoString(options{i}, ...
                            groups{i},...
                            type, ...
                            params.SharedLibraryMerge, ...
                            handle); %#ok<AGROW>
                    end
                end
            end

            h.(type) = [h.(type); newOpt(:)];
        end


        function val = areChildrenOrdered(~)
        %ARECHILDRENORDERED - returns true if children are returned in a specific order
        %from getChildren and getHierarchicalChildren.
            val = true;
        end
        
        function removeOptions(h, type, params)
        % removeOptions removes key-value pairs from Defines vector

            arguments
                h
                type (1, :) char ...
                    {mustBeMember(type, ...
                    {'Defines', 'Undefines', 'CompileFlags'})}
                params.Options (1, :) string = string.empty
                params.SourceFileHandles (1, :) RTW.BuildInfoFile = RTW.BuildInfoFile.empty;
            end
            
            options = unique(params.Options, 'stable');
            options = options(~(options==""));
            options = cellstr(options);

            foundOptionsToRemove = false;
            removeIdx = true(1, numel(h.(type)));

            if ~isempty(options)
                if strcmp(type, 'Defines')
                    cur_keys = {h.(type).Key};
                    if isempty(cur_keys)
                        return
                    end
                    % Matching options to be removed
                    defNames = h.resolveDefines(options);
                    optionsToRemoveIdx = ismember(cur_keys, defNames);
                elseif strcmp(type, 'Undefines')
                    optionsToRemoveIdx = ismember({h.Undefines.Value},options);
                else
                    cur_options = {h.(type).Value};

                    % Matching options to be removed
                    optionsToRemoveIdx = ismember(cur_options, options);

                end
                removeIdx = removeIdx & optionsToRemoveIdx;
                foundOptionsToRemove = true;
            end

            % Match against specified source file handle
            if ~isempty(params.SourceFileHandles)
                handleRemoveIdx = false(1, numel(h.(type)));
                for i=1:length(params.SourceFileHandles)
                    sourceFileHandle=params.SourceFileHandles(i);
                    sourceFileHandles = {h.(type).SourceFileHandle};
                    handleRemoveIdx_i = cellfun(@(x)isequal(x, sourceFileHandle), ...
                        sourceFileHandles);
                    handleRemoveIdx = handleRemoveIdx | handleRemoveIdx_i;
                end
                removeIdx = removeIdx & handleRemoveIdx;
                foundOptionsToRemove = true;
            end

            if foundOptionsToRemove && any(removeIdx)
                h.(type)(removeIdx) = [];
            end
        end

        function val = getChildren(h)
        %GETCHILDREN - returns non-hierarchical child properties for DA Studio Explorer
            val = [h.Defines' h.Undefines' h.CompileFlags' h.LinkFlags'];
        end
        
        function Groups = getGroups(h)
        %GETGROUPS - returns a vector of Group labels used by this object
            defGroups   = get(h.Defines,'Group');
            undefGroups = get(h.Undefines,'Group');
            cfGroups    = get(h.CompileFlags,'Group');
            lfGroups    = get(h.LinkFlags,'Group');
            Groups = RTW.unique([rtwprivate('convertToCell',defGroups);...
                                rtwprivate('convertToCell',undefGroups);...
                                rtwprivate('convertToCell',cfGroups);...
                                rtwprivate('convertToCell',lfGroups)]);
        end
        
        function val = getHierarchicalChildren(~)
        %GETHIERARCHICALCHILDREN - returns hierarchical child properties for DA Studio
        %explorer
            val = [];
        end
        
        function [options, keys, vals, optGroups] = getOptions(h, type, incGroups, excGroups, params)
        %GETOPTIONS - returns a cell array of options.  this function is called
        % like this:
        % 
        % options = getOptions(type, includeGroupIDs, excludeGroupIDs)
        %
        %    type               This argument controls which types(include or source
        %                       files) of files are returned. It must be one of:
        %                       {'include', 'source', 'all'}
        %
        %    includeGroupIDs    This is a cell array of Group IDs.  Only Options that
        %                       have GroupID in this set will be returned.  If this
        %                       is empty, the all Options are included
        %
        %    excludeGroupIDs    This is a cell array of Group IDs. Any Options that
        %                       have one of these Group ID's will be excluded.
        %
        % includeGroupIDs and excludeGroupIDs are optional

            arguments
                h
                type (1, :) char ...
                    {mustBeMember(type, ...
                                  {'Defines', 'Undefines', 'CompileFlags', 'LinkFlags', 'ArchiveFlags'})}
                incGroups (1, :) {mustBeText} = string.empty;
                excGroups (1, :) {mustBeText} = string.empty;
                params.OnlyForSharedLibraryMerge = false;
                params.SourceFileHandle RTW.BuildInfoFile {mustBeScalarOrEmpty} ...
                    = RTW.BuildInfoFile.empty;
            end

            [incGroups, excGroups] = ...
                coder.make.internal.buildInfoGroupArgs(incGroups, excGroups);

            options = {};
            keys = {};
            vals = {};
            optGroups = {};
            
            switch (type)
              case 'Defines'
                keys = {h.Defines.Key};
                vals = {h.Defines.Value};
                
                % if there are no defines, then just return
                if isempty(keys)
                    return;
                end
                
                % the options should automatically get a '-D' appended to the front
                options         = strcat('-D',keys);
                % if the val is non-empty, then it should be concatenated onto the end of
                % the key with an '=' at the beginning
                valIdx          = find(strcmp(vals,'')==0);
                options(valIdx) = strcat(options(valIdx),'=', vals(valIdx));
              otherwise
                options   = {h.(type).Value};
            end
            
            % if there are no options, then return before filtering
            if isempty(options)
                keys = {};
                vals = {};
                optGroups = {};
                return;
            end

            % Apply include/exclude group filters
            groupFilterIdx = coder.make.internal.buildInfoGroupsFilter...
                (h, type, incGroups, excGroups);

            % Apply shared library merge filter
            sharedLibMergeFilterIdx = i_sharedLibraryMergeFilter ...
                (h, type, params.OnlyForSharedLibraryMerge);

            % Apply source file filter
            sourceFileFilterIdx = i_sourceFileFilter(h, type, params.SourceFileHandle);

            keepIdx = groupFilterIdx & sharedLibMergeFilterIdx & ...
                sourceFileFilterIdx;

            % if there are no options left, then return an empty cell array
            options = options(keepIdx);
            optGroups = {h.(type).Group};
            optGroups = optGroups(keepIdx);
            if isempty(options)
                keys = {};
                vals = {};
                optGroups = {};
                return;
            end
            
            if ~isempty(keys)
                keys = keys(keepIdx);
            end
            if ~isempty(vals)
                vals = vals(keepIdx);
            end
            
            % Return column vectors for keys, vals (options, optGroups are
            % returned as row vectors)
            keys = keys(:);
            vals = vals(:);
        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 [defNames, defValues, groups] = resolveDefines(h, defsIn, groups) %#ok<INUSL>
        %RESOLVEDEFINES - Parses a string or cell array of defines into name/value
            
            % Split -D separated defines supplied in a single character vector 
            % See g2290243
            if nargin <= 2
                groups = repmat({''}, 1, length(defsIn));
            end
            [defs, groups] = locTokenize(defsIn, groups);
            
            % this call grabs anything up to but not including the first '='
            defNames = regexp(defs,'.*?(?=($|=))', 'match', 'once');
            
            % if there is an '=' in the define, this grabs everything after the
            % first '='.  Otherwise it will return an empty string.  So if the
            % following vector is passed in, the resulting vector of values is
            % shown:
            % 
            % defs = {'FOO' 'BAR=1' ABC="xx=123"'
            %
            % resulting values:
            % 
            % defValues = {'' '1' '"xx=123"'}
            %    
            defValues =  regexp(defs,'(?<==).*', 'match', 'once');
        end
    end

    methods (Hidden)
        function dlgstruct = getDialogSchema(hSrc, ~)
            dlgstruct = [];
            %=======================================================================
            % The preprocessor defines are displayed in a table
            %=======================================================================
            widget = [];
            widget.Name = string(message('coder_compile:buildInfo:PreprocDefinesLabel'));
            widget.Type = 'table';
            widget.Enabled = true;
            widget.Size = [length(hSrc.Defines) 3];
            widget.Grid = true;
            widget.ColHeader = {string(message('coder_compile:buildInfo:GroupLabel')),...
                                string(message('coder_compile:buildInfo:DefineLabel')),...
                                string(message('coder_compile:buildInfo:ValueLabel'))};
            widget.HeaderVisibility = [0 1];
            
            if (length(hSrc.Defines) > 1)
                widget.Data(:,1) = get(hSrc.Defines,'Group');
                widget.Data(:,2) = get(hSrc.Defines,'Key');
                widget.Data(:,3) = get(hSrc.Defines,'Value');
            else
                widget.Data(1,1) = {get(hSrc.Defines,'Group')};
                widget.Data(1,2) = {get(hSrc.Defines,'Key')};
                widget.Data(1,3) = {get(hSrc.Defines,'Value')};
            end
            
            widget.ToolTip = string(message('coder_compile:buildInfo:preprocDefinesToolTip'));
            widget.Mode = 1;
            defines = widget;
            widget = [];
            
            %=======================================================================
            % The Compile Flags are displayed in a table
            %=======================================================================
            widget.Name = string(message('coder_compile:buildInfo:CompileFlagsLabel'));
            widget.Type = 'table';
            widget.Enabled = true;
            widget.Size = [length(hSrc.CompileFlags) 2];
            widget.Grid = true;
            widget.ColHeader = {string(message('coder_compile:buildInfo:GroupLabel')),...
                                string(message('coder_compile:buildInfo:ValueLabel'))};
            widget.HeaderVisibility = [0 1];
            
            if (length(hSrc.CompileFlags) > 1)
                widget.Data(:,1) = get(hSrc.CompileFlags,'Group');
                widget.Data(:,2) = get(hSrc.CompileFlags,'Value');
            else
                widget.Data(1,1) = {get(hSrc.CompileFlags,'Group')};
                widget.Data(1,2) = {get(hSrc.CompileFlags,'Value')};
            end
            
            widget.ToolTip = string(message('coder_compile:buildInfo:CompileFlagsToolTip'));
            widget.Mode = 1;
            compileFlags = widget;
            widget = [];
            
            %=======================================================================
            % The Link Flags are displayed in a table
            %=======================================================================
            widget.Name = string(message('coder_compile:buildInfo:LinkFlagsLabel'));
            widget.Type = 'table';
            widget.Enabled = true;
            widget.Size = [length(hSrc.LinkFlags) 2];
            widget.Grid = true;
            widget.ColHeader = {string(message('coder_compile:buildInfo:GroupLabel')),...
                                string(message('coder_compile:buildInfo:ValueLabel'))};
            widget.HeaderVisibility = [0 1];
            
            if (length(hSrc.LinkFlags) > 1)
                widget.Data(:,1) = get(hSrc.LinkFlags,'Group');
                widget.Data(:,2) = get(hSrc.LinkFlags,'Value');
            else
                widget.Data(1,1) = {get(hSrc.LinkFlags,'Group')};
                widget.Data(1,2) = {get(hSrc.LinkFlags,'Value')};
            end
            
            widget.ToolTip = string(message('coder_compile:buildInfo:LinkFlagsToolTip'));
            widget.Mode = 1;
            linkFlags = widget;
            
            %=======================================================================
            % layout the widgets
            %=======================================================================
            defines.RowSpan = [1 1];
            defines.ColSpan = [1 3];
            compileFlags.RowSpan = [2 2];
            compileFlags.ColSpan = [1 3];
            linkFlags.RowSpan = [3 3];
            linkFlags.ColSpan = [1 3];
            
            group.Name = string(message('coder_compile:buildInfo:OptionsLabel'));
            group.Type = 'group';
            group.LayoutGrid = [3 3];
            group.Items = {defines, compileFlags, linkFlags};
            
            dlgstruct.DialogTitle = string(message('coder_compile:buildInfo:OptionsLabel'));
            dlgstruct.Items = {group};
        end
    end

    methods(Access = protected, Hidden=true)
        cpObj = copyElement(obj)
    end
end

function [defs, groups] = locTokenize(origDefs, origGroups)
% locTokenize splits macros using the -D delimiter 
% See g2290243

% Expand any strings that contain multiple defines into separate
% entries, for example, transform
%     
%     {'-DMACRO1 -DMACRO2'}
% 
% to 
% 
%     {'-DMACRO1' '-DMACRO2'}
defs = {};
groups = {};
for i=1:length(origDefs)
    defsExpanded = regexp(origDefs{i}, '\S.*?(?=(\s+-[dD])|$)', 'match');
    numDefs = length(defsExpanded);
    [defs{end+1:end+numDefs}] = defsExpanded{:};
    [groups(end+1:end+numDefs)] = origGroups(repmat(i, 1, numDefs));
end

% Replace any trailing white space with a single space character
defs = regexprep(defs, '\s*$', ' ');

% strip off the leading -D for each define
defs = regexprep(defs, '^\s*-[dD]', '');
end


function i_mustBeCompatibleSize(flags, groups)
% Test for equal size
if numel(groups)>1 && numel(groups)~=numel(string(flags))
    throw(MException(message('coder_compile:CoderCompile:badMethodArguments',...
        'RTW.BuildInfoOptions.addOptions')));
end
end


% Determine indices to keep after applying shared library filter
function keepIdx = i_sharedLibraryMergeFilter ...
    (h, type, onlyForSharedLibraryMerge)
if onlyForSharedLibraryMerge
    keepIdx = [h.(type).SharedLibraryMerge];
else
    keepIdx = true(size(h.(type)))';
end
end


% Determine indices to keep after applying source file filter
function keepIdx = i_sourceFileFilter(h, type, sourceFileHandle)

if isempty(sourceFileHandle)
    % Only keep options common to all files
    keepIdx = cellfun(@isempty, {h.(type).SourceFileHandle});
else
    % Identify options where sourceFileHandle is one of the specified handles
    sourceFileHandles = {h.(type).SourceFileHandle};
    matchingHandleidx = cellfun(@(x)isequal(x, sourceFileHandle), ...
                        sourceFileHandles);

    % Additionally keep the common options
    keepIdx = matchingHandleidx;
end
keepIdx = keepIdx(:)';
end
