classdef BuildInfoOptions < RTW.BuildInfoClass & matlab.mixin.SetGet & matlab.mixin.Copyable
%RTW.BuildInfoOptions class
%   RTW.BuildInfoOptions extends RTW.BuildInfoClass.
%
%    RTW.BuildInfoOptions properties:
%       Settings - Property is of type 'handle'  
%       DisplayLabel - Property is of type 'ustring'  
%       Group - Property is of type 'ustring'  
%       Defines - Property is of type 'handle vector'  
%       CompileFlags - Property is of type 'handle vector'  
%       ArchiveFlags - Property is of type 'handle vector'  
%       LinkFlags - Property is of type 'handle vector'  
%       MEXFlags - Property is of type 'handle vector'  

%   Copyright 2017-2020 The MathWorks, Inc.

    properties
        Defines = [];
        CompileFlags = [];
        ArchiveFlags = [];
        LinkFlags = [];
        MEXFlags = [];
    end

    methods
        function h = BuildInfoOptions(varargin)
            h.Settings = [];
            % if a valid settings object was passed in, set the Settings property to it.
            if (nargin > 0) &&...
                    ~isempty(varargin{1}) &&...
                    ~isa(varargin{1}, 'RTW.BuildInfoSettings')
                h.Settings = varargin{1};
            end
            % override the base class display label. 
            h.DisplayLabel = string(message('coder_compile:buildInfo:OptionsLabel'));
        end
    end

    methods
        function addOptions(h, type, varargin)
        %ADDOPTIONS - adds one or more options to the CompileFlags, Defines, or
        %LinkFlags vector.
        %
        % Call the function like this:
        % 
        % h.addOptions(type, options, groups)
        %
        % args  Optional? description
        % type    n   char array specifying option type.
        %             must be one of {'Defines', 'CompileFlags', 'LinkFlags'}
        % options n   char array or cell array of chars
        %             contains the option(s) to be added
        % groupd  y   char array, or cell array of char
        %             The Group label that should be applied to the option(s)
        %             if this arg is a char array, it is used for all the options
        %             if it is a cell array, length(groups) must equal length(options)
            
            [argsOK, Options, Groups] = locCheckArgsAddOptions(type, varargin{:});
            if ~(argsOK)
                DAStudio.error('RTW:buildInfo:badMethodArguments',...
                               'RTW.BuildInfoOptions.addOptions');
            end
            
            if isempty(Options)
                return;
            end
            
            if strcmp(type, 'Defines')
                [Keys,Vals] = h.resolveDefines(Options);
                
                cur_keys = get(h.Defines,'Key');
                
                if ~isempty(cur_keys)
                    % this returns the members and indices of Keys that are NOT in
                    % cur_keys. 
                    idx = ~ismember(Keys,cur_keys);
                    % reduce all of the input vectors to the subset of new entries.
                    Keys   = Keys(idx);
                    Vals   = Vals(idx);
                    Groups = Groups(idx);
                end
                cnt = length(Keys);
                
                for i=cnt:-1:1
                    % create a new string object to hold the Option, and add it to the
                    % list, the type is used for the display label.
                    newOpt(i) = RTW.BuildInfoKeyValuePair(Keys{i},Vals{i}, Groups{i},...
                                                          Keys{i}, h.Settings);
                end
                
            else
                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);

                for i=cnt:-1:1
                    % create a new string object to hold the Option, and add it to the
                    % list, the type is used for the display label.                   
                    newOpt(i) = RTW.BuildInfoString(Options{i},Groups{i},...
                                                    type, h.Settings);
                end
                
            end
            
            % if all the additions are duplicates, then the cnt will be 0.  We can
            % just return since there is nothing to add.
            if (cnt == 0)
                return;
            end

            % note that the newOpt must be transposed , because it is 1xN while
            % the option lists are Nx1
            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 deleteOptions(h, Type, varargin)
        %DELETEOPTIONS - Delete key-value pairs from one of the internal vectors

        % check the args to make sure they all conform to the expected format
            [args_ok, Keys] = locCheckArgsDeleteOptions(Type, varargin{:});
            if ~(args_ok)
                DAStudio.error('RTW:buildInfo:badMethodArguments',...
                               'RTW.BuildInfo.deleteOptions');
            end
            
            % if any of the Keys are already in the list, remove them.
            switch (Type)
              case 'Defines'
                cur_keys = get(h.Defines,'Key');
                if ~isempty(cur_keys) && ~isempty(Keys)
                    defNames = h.resolveDefines(Keys);
                    idx = ~ismember(cur_keys,defNames);
                    h.Defines = h.Defines(idx);
                end        
              otherwise
                assert(false, 'Unknown Type: %s', Type);
            end
        end

        function val = getChildren(h)
        %GETCHILDREN - returns non-hierarchical child properties for DA Studio Explorer
            val = [h.Defines' 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');
            cfGroups  = get(h.CompileFlags, 'Group');
            lfGroups  = get(h.LinkFlags, 'Group');
            Groups = RTW.unique([rtwprivate('convertToCell',defGroups);...
                                rtwprivate('convertToCell',cfGroups);...
                                rtwprivate('convertToCell',lfGroups)]);
        end
        
        function val = getHierarchicalChildren(~)
        %GETHIERARCHICALCHILDREN - returns hierarchical child properties for DA Studio
        %explorer
            val = [];
        end
        
        function [ret,Keys,Vals] = getOptions(h, type, varargin)
        %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
            
            type = convertStringsToChars(type);
            [varargin{:}] = convertStringsToChars(varargin{:});
  
            %check the args to make sure they all conform to the expected format
            [argsOK, incGroups, excGroups] = locCheckArgsGetOptions(type, varargin{:});
            if ~(argsOK)
                DAStudio.error('RTW:buildInfo:badMethodArguments',...
                               'RTW.BuildInfoBuildObj.getOptions');
            end
            
            ret = {};
            Keys = {};
            Vals = {};
            
            switch (type)
              case 'Defines'
                Keys = get(h.Defines,'Key');
                Vals = get(h.Defines,'Val');
                
                % if there are no defines, then just return
                if isempty(Keys)
                    return;
                end
                
                if ~iscell(Keys)
                    Keys = {Keys};
                end
                if~iscell(Vals)
                    Vals = {Vals};
                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));
                optGroups       = get(h.Defines,'Group');
              otherwise
                options   = get(h.(type),'Value');
                optGroups = get(h.(type),'Group');
            end
            
            % if there are no options, then return before filtering
            if isempty(options)
                Keys = {};
                Vals = {};
                return;
            end
            
            if ~iscell(options)
                options = {options};
            end
            
            if isempty(incGroups)
                includeIdx = true(length(options), 1);    
            else
                includeIdx = ismember(optGroups, incGroups);
            end

            if isempty(excGroups)
                excludeIdx = false(length(options), 1);    
            else
                excludeIdx = ismember(optGroups, excGroups);
            end

            includeIdx = includeIdx & (~excludeIdx);
   
            % if there are no options left, then return an empty cell array
            options = options(includeIdx);
            if isempty(options)
                Keys = {};
                Vals = {};
                return;
            end
            
            if ~isempty(Keys)
                Keys = Keys(includeIdx);
            end
            if ~isempty(Vals)
                Vals = Vals(includeIdx);
            end
            
            % transpose the output to get a 1xn vector to remain consistent with the other
            % get... API calls.
            ret = options';
        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] = resolveDefines(h, origDefs) %#ok<INUSL>
        %RESOLVEDEFINES - Parses a string or cell array of defines into name/value
            
        % first strip off any -d or -D at the beginning
            defs = regexprep(origDefs,'^-[dD]?(.*)','$1');
            
            % this call grabs anything up to but not including the first '='
            tmp = regexp(defs,'([^=]*)=?','tokens','once');
            defNames = [tmp{:}];
            
            % 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"'}
            %    
            tmp =  regexp(defs,'[^=]*=?(.*)','tokens','once');
            defValues = [tmp{:}];
        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;
            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
end

function [argsOK, Options, Groups] = locCheckArgsAddOptions(type, varargin)

    argsOK = true;
    Options = {};
    Groups  = {};

    % the first arg in varargin is the Flag, which is mandatory, the
    % rest are optional.  However, check for not exceeding the maximum
    % number of args.
    if (nargin < 2) || (nargin > 3)
        argsOK = false;
        return;
    end

    % make sure the type was specified correctly
    if ischar(type)
        tf = strcmp(type,{'Defines', 'CompileFlags', 'LinkFlags', 'ArchiveFlags','MEXFlags'});
        if ~any(tf)
            argsOK = false;
            return;
        end
    else
        argsOK = false;
        return;
    end

    % the first arg is the options list
    if ~iscellstr(varargin{1})
        if ~ischar(varargin{1})
            % this is an incompatible data type
            argsOK = false;
            return;
        end
        % if an empty string was the only insertion, just take the default Options
        % above '{}'.  The caller can handle an empty vector OK.
        if ~isempty(varargin{1})
            Options = varargin(1);
        end
    else
        Options  = varargin{1};
    end
    
    if (nargin >= 3)
        % if the argument is not a cell array (i.e. a single value) create a
        % cell array the same length as Options and fill it with that value.
        if iscellstr(varargin{2})
            Groups = varargin{2};
            % the Options and Groups cell array must have the same length
            if (length(Options) ~= length(Groups))
                argsOK = false;
                return;
            end
        else
            if ~ischar(varargin{2})
                argsOK = false;
                return;
            end
            Groups(1:length(Options)) = varargin(2);
        end
    else
        Groups(1:length(Options)) = {''};
    end
end

function [args_ok, Keys] = locCheckArgsDeleteOptions(Type, varargin)

    args_ok = false;
    Keys    = {};

    %only 2 arguments can be passed in.
    if (nargin ~= 2)
        return;
    end

    % the first arg is the type of Key Value pair, and affects which list
    % it will get added to.
    if ~ischar(Type) || ~strcmp(Type, 'Defines')
        return;
    end

    % the next arg is the Key or list of keys
    if ~iscellstr(varargin{1})
        if ~ischar(varargin{1})
            % this is an incompatible data type
            return;
        end
        Keys = varargin(1);
    else
        Keys = varargin{1};
    end

    % remove any duplicate entries if the list is longer than 1
    if (length(Keys) > 1)
        
        Keys = RTW.unique(Keys);
        
        % finally, check for empty Keys.  If the Token is empty, the
        % whole entry should be removed.
        idx = ~strcmp('', Keys);
        Keys   = Keys(idx);
    end

    % if we got here the args are OK
    args_ok  = true;
end

function [argsOK, incGroups, excGroups] = locCheckArgsGetOptions(type, varargin)

    argsOK = true;
    incGroups = {};
    excGroups = {};

    % make sure the type was specified correctly
    tf = strcmp(type,{'Defines', 'CompileFlags', 'LinkFlags', 'ArchiveFlags', 'MEXFlags'});
    if ~any(tf)
        argsOK = false;
        return;
    end
    
    nargs = length(varargin);
    % all of the args must be cell arrays for the caller.  If a char array
    % or scalar is sent int, just convert them to cell arrays
    
    % the first arg is the incGroups
    if (nargs >= 1)
        if ~iscellstr(varargin{1})
            if ~ischar(varargin{1})
                % this is an incompatible data type
                argsOK = false;
                return;
            end
            % Only use this arg if it is not ''.
            if ~isempty(varargin{1})
                incGroups = varargin(1);
            end
        else
            %get the unique list, just in case there are dups
            incGroups = unique(varargin{1});
        end
    end

    % the second arg is the excGroups
    if (nargs >= 2)
        if ~iscellstr(varargin{2})
            if ~ischar(varargin{2})
                % this is an incompatible data type
                argsOK = false;
                return;
            end
            % Only use this arg if it is not ''.
            if ~isempty(varargin{2})
                excGroups = varargin(2);
            end
        else
            %get the unique list, just in case there are dups
            excGroups = unique(varargin{2});
        end
    end
end
