% SLCOVERAGE.SFCNSELECTOR S-Function filter selector class
%   Selects S-Function block for filtering by it properties.
%   so = slcoverage.SFcnSelector(type, blockH, fileName, funName, expr, exprIdx, outcomeIdx, decOrCondIdx) - creates a selector object
%       type       - slcoverage.SFcnSelectorType
%       blockH     - block handle, Simulink ID or property name
%       fileName   - c++/c file name
%       funName    - c++/c function name
%       expr       - expression to filter
%       exprIdx    - expression index
%       outcomeIdx - outcome index (optional)
%     decOrCondIdx - index of the owning condition or decision (optional)
%
% SLCOVERAGE.SFCNSELECTOR methods:
%   slcoverage.SFcnSelector.allSelectors(blockH) - all SFcnSelectors
%                                                  for a block
% See also: SLCOVERAGE.SFCNSELECTORTYPE, SLCOVERAGE.FILTERRULE,
%           SLCOVERAGE.FILTER

% Copyright 2017-2020 The MathWorks, Inc.

classdef SFcnSelector < slcoverage.Selector 
properties (SetAccess = private)
    FileName  = '';
    FunctionName = '';
    Expr = '';
    OutcomeIndex = [];
    DecOrCondIndex = [];
end    
properties (Hidden = true)
    ExprIndex = [];
    CVMetricType = [];
end
methods

    function  this = SFcnSelector (type, name, varargin)
        narginchk(2, 8);
        
        [status, msgId] = SlCov.CoverageAPI.checkCvLicense;
        if status == 0
            error(message(msgId));
        end
        
        [varargin{:}] = convertStringsToChars(varargin{:});
        
        p = inputParser;
        typeValidationFcn = @(x)validateattributes(x, {'slcoverage.SFcnSelectorType'}, {'scalar'}, 'SFcnSelector', 'type', 1);
        p.addRequired('type', typeValidationFcn);
        parse(p, type);
        type = p.Results.type;
        constructorCode = 'slcoverage.SFcnSelector(';
        sid = '';
        id = '';
        try 
            name = Simulink.ID.getSID(name);
        catch
        end

        if contains(name, ':') || Simulink.ID.isValid(name)
            sidValidationFcn = @(x)validateattributes(x, {'char'}, {'vector'}, 'SFcnSelector', 'sid', 1);
            p1 = inputParser;
            p1.addRequired('sid', sidValidationFcn);
            parse(p1, name);
            sid = p1.Results.sid;
            id = sid; 
            constructorCode = [constructorCode sprintf('%s, ''%s'', ', mat2str(type), id)];
        elseif isequal(type, slcoverage.SFcnSelectorType.SFcnName)
             sfcnNameValidationFcn = @(x)validateattributes(x, {'char'}, {'vector'}, 'SFcnName', 'SFcnName', 1);                        
             p1 = inputParser;
             p1.addRequired('SFcnName', sfcnNameValidationFcn );
             parse(p1, convertStringsToChars(name));
             id = p1.Results.SFcnName;
             constructorCode = [constructorCode sprintf('%s, ''%s''', mat2str(type), id)];
        end
        this@slcoverage.Selector(sid, type, id);
        p2 = inputParser;
        p2.StructExpand = false;        
        alwaysTrue = @(x)true;

        switch type
            case slcoverage.SFcnSelectorType.SFcnInstanceCppFunction
                    fileNameValidationFcn = @(x)validateattributes(x, {'char'}, {'vector'}, 'SFcnSelector', 'FileName', 1);        
                    funNameValidationFcn = @(x)validateattributes(x, {'char'}, {'vector'}, 'SFcnSelector', 'FunName', 2);        
                    p2.addRequired('FileName', fileNameValidationFcn);
                    p2.addRequired('FunName', funNameValidationFcn);
                    p2.addOptional('Expr', '', alwaysTrue);  
                    p2.addOptional('ExprIdx', [], alwaysTrue);
                    p2.addOptional('OutcomeIdx', [], alwaysTrue);
                    p2.addOptional('DecOrCondIdx', [], alwaysTrue);
                    parse(p2, varargin{:});
                    this.FileName  = p2.Results.FileName;
                    this.FunctionName = p2.Results.FunName;
                    constructorCode = [constructorCode sprintf('''%s'', ''%s''', this.FileName, this.FunctionName)];

            case slcoverage.SFcnSelectorType.SFcnInstanceCppFileName
                    fileNameValidationFcn = @(x)validateattributes(x, {'char'}, {'vector'}, 'SFcnSelector', 'FileName', 1);        
                    p2.addRequired('FileName', fileNameValidationFcn);
                    p2.addOptional('FunName', '', alwaysTrue);
                    p2.addOptional('Expr', '', alwaysTrue);  
                    p2.addOptional('ExprIdx', [], alwaysTrue);
                    p2.addOptional('OutcomeIdx', [], alwaysTrue);
                    p2.addOptional('DecOrCondIdx', [], alwaysTrue);
                    parse(p2, varargin{:});
                    this.FileName  = p2.Results.FileName;
                    constructorCode = [constructorCode sprintf('''%s''', this.FileName)];

            case {slcoverage.SFcnSelectorType.SFcnInstanceCppDecision, slcoverage.SFcnSelectorType.SFcnInstanceCppCondition,...
                    slcoverage.SFcnSelectorType.SFcnInstanceCppDecisionOutcome, slcoverage.SFcnSelectorType.SFcnInstanceCppConditionOutcome,...
                    slcoverage.SFcnSelectorType.SFcnInstanceCppMCDCOutcome, slcoverage.SFcnSelectorType.SFcnInstanceCppRelationalBoundaryOutcome}

                isOutcomeBased = ...
                    isequal(type, slcoverage.SFcnSelectorType.SFcnInstanceCppDecisionOutcome) ||...
                    isequal(type, slcoverage.SFcnSelectorType.SFcnInstanceCppConditionOutcome) ||...
                    isequal(type, slcoverage.SFcnSelectorType.SFcnInstanceCppMCDCOutcome) ||...
                    isequal(type, slcoverage.SFcnSelectorType.SFcnInstanceCppRelationalBoundaryOutcome);

                    if isOutcomeBased
                        if ~SlCov.isCodeOutcomeFilterFeatureOn()
                            error(message('Slvnv:simcoverage:covFilterUnsupportedCodeSelector', ['slcoverage.SFcnSelectorType.',char(type)]));
                        end
                        if type == slcoverage.SFcnSelectorType.SFcnInstanceCppDecisionOutcome || ...
                                type == slcoverage.SFcnSelectorType.SFcnInstanceCppMCDCOutcome
                            narginchk(2, 7);
                        else
                            narginchk(2, 8);
                        end
                    else
                        narginchk(2, 6);
                    end

                    fileNameValidationFcn = @(x)validateattributes(x, {'char'}, {'vector'}, 'SFcnSelector', 'FileName', 1);
                    funNameValidationFcn = @(x)validateattributes(x, {'char'}, {'vector'}, 'SFcnSelector', 'FunName', 2);
                    exprValidationFcn = @(x)validateattributes(x, {'char'}, {'vector'}, 'SFcnSelector', 'Expr', 3);
                    exprIdxValidationFcn = @(x)validateattributes(x, {'numeric'}, {'positive', 'nonzero','scalar'}, 'SFcnSelector', 'ExprIdx', 4);
                    p2.addRequired('FileName', fileNameValidationFcn);
                    p2.addRequired('FunName', funNameValidationFcn);
                    p2.addRequired('Expr', exprValidationFcn);
                    p2.addRequired('ExprIdx', exprIdxValidationFcn);
                    p2.addOptional('OutcomeIdx', [], alwaysTrue);
                    p2.addOptional('DecOrCondIdx', [], alwaysTrue);
                    parse(p2, varargin{:});

                    % Check the expected arguments for outcome based selector
                    if isOutcomeBased
                        if isequal(type, slcoverage.SFcnSelectorType.SFcnInstanceCppDecisionOutcome)
                            % TODO(eroy) would love to support 'T' and 'F'
                            % but it doesn't work for switch...
                        elseif isequal(type, slcoverage.SFcnSelectorType.SFcnInstanceCppConditionOutcome)
                            % TODO(eroy): allow 'T' and 'F' as outcome and convert to 1 and 2
                        elseif isequal(type, slcoverage.SFcnSelectorType.SFcnInstanceCppRelationalBoundaryOutcome)
                            % TODO(eroy): allow '<', '>' and '==' as outcome and convert to 1, 2 and 3
                        end
                        validateattributes(p2.Results.OutcomeIdx, {'numeric'}, {'positive', 'nonzero', 'scalar'}, 'SFcnSelector', 'OutcomeIdx', 5);
                        if ~isempty(p2.Results.DecOrCondIdx)
                            validateattributes(p2.Results.DecOrCondIdx, {'numeric'}, {'positive', 'nonzero', 'scalar'}, 'SFcnSelector', 'DecOrCondIdx', 6);
                        end
                    end

                    this.FileName  = p2.Results.FileName;
                    this.FunctionName = p2.Results.FunName;
                    this.Expr = p2.Results.Expr;
                    this.ExprIndex = p2.Results.ExprIdx;
                    switch type
                        case {slcoverage.SFcnSelectorType.SFcnInstanceCppDecision, slcoverage.SFcnSelectorType.SFcnInstanceCppDecisionOutcome}
                            this.CVMetricType = 1;
                        case {slcoverage.SFcnSelectorType.SFcnInstanceCppCondition, slcoverage.SFcnSelectorType.SFcnInstanceCppConditionOutcome}
                            this.CVMetricType = 0;
                        case slcoverage.SFcnSelectorType.SFcnInstanceCppMCDCOutcome
                            this.CVMetricType = 2;
                        case slcoverage.SFcnSelectorType.SFcnInstanceCppRelationalBoundaryOutcome
                            this.CVMetricType = 3;
                    end
                    if isOutcomeBased
                        this.OutcomeIndex = p2.Results.OutcomeIdx;
                        this.DecOrCondIndex = p2.Results.DecOrCondIdx;
                    end
                    constructorCode = [constructorCode sprintf('''%s'', ''%s'', ''%s'', %s', this.FileName, this.FunctionName, this.Expr, mat2str(this.ExprIndex))];
                    if ~isempty(this.OutcomeIndex)
                        constructorCode = [constructorCode, sprintf(', %s', mat2str(this.OutcomeIndex))];
                        if ~isempty(this.DecOrCondIndex)
                            constructorCode = [constructorCode, sprintf(', %s', mat2str(this.DecOrCondIndex))];
                        end
                    end
        end

        constructorCode = [constructorCode ')'];
    	this.setConstructorCode(constructorCode );
    end
end
%===================
methods  (Static = true)    
    function s = allSelectors(blockH, varargin)
        s = slcoverage.Selector.allSelectors(blockH, ...
            'Type', 'slcoverage.SFcnSelectorType', ...
            varargin{:});
    end
end


end
