% SLCOVERAGE.CODESELECTOR Code filter selector class
%   Selects Code for filtering by it properties.
%   so = slcoverage.CodeSelector(type, fileName, funName, expr, exprIdx, outcomeIdx, decOrCondIdx) - creates a selector object
%       type       - slcoverage.CodeSelectorType
%       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.CODESELECTOR methods:
%   slcoverage.CodeSelector.allSelectors(model) - all CodeSelectors
%                                                 for a model
% See also: SLCOVERAGE.CODESELECTORTYPE, SLCOVERAGE.FILTERRULE,
%           SLCOVERAGE.FILTER

% Copyright 2018-2020 The MathWorks, Inc.

classdef CodeSelector < slcoverage.Selector

    properties (SetAccess = private)
        FileName  = ''
        FunctionName = ''
        Expr = ''
        OutcomeIndex = []
        DecOrCondIndex = [];
    end

    properties (Hidden = true)
        ExprIndex = []
        CVMetricType = []
    end

    %% --------------------------------------------------------------------
    methods
        function this = CodeSelector(type, fileName, varargin)

            % Must have a valid Simulink Coverage license
            [status, msgId] = SlCov.CoverageAPI.checkCvLicense;
            if status == 0
                error(message(msgId));
            end

            % Check the input arguments
            narginchk(2, 7);
            validateattributes(type, {'slcoverage.CodeSelectorType'}, {'scalar'}, 'CodeSelector', 'type', 1);
            strArgValidFcn = @(arg, name, pos)validateattributes(arg, {'char', 'string'}, {'scalartext'}, 'CodeSelector', name, pos);
            strArgValidFcn(fileName, 'fileName', 2);
            fileName = convertStringsToChars(fileName);
            [varargin{:}] = convertStringsToChars(varargin{:});

            % Construct the object
            this@slcoverage.Selector('', type, '');
            this.FileName  = fileName;

            % Generate the construction command
            constructorCode = sprintf('slcoverage.CodeSelector(%s, ''%s''',...
                mat2str(type), fileName);

            argParser = inputParser;
            argParser.StructExpand = false;
            alwaysTrue = @(x)true;

            switch type
                case slcoverage.CodeSelectorType.Function
                    argParser.addRequired('funName', @(x)strArgValidFcn(x, 'funName', 3));
                    argParser.addOptional('expr', '', alwaysTrue);
                    argParser.addOptional('exprIdx', [], alwaysTrue);
                    argParser.addOptional('outcomeIdx', [], alwaysTrue);
                    argParser.addOptional('decOrCondIdx', [], alwaysTrue);
                    parse(argParser, varargin{:});
                    this.FunctionName = argParser.Results.funName;
                    constructorCode = [constructorCode, sprintf(', ''%s''', this.FunctionName)];

                case slcoverage.CodeSelectorType.File
                    argParser.addOptional('funName', '', alwaysTrue);
                    argParser.addOptional('expr', '', alwaysTrue);
                    argParser.addOptional('exprIdx', [], alwaysTrue);
                    argParser.addOptional('outcomeIdx', [], alwaysTrue);
                    argParser.addOptional('decOrCondIdx', [], alwaysTrue);
                    parse(argParser, varargin{:});

                otherwise
                    isOutcomeBased = ...
                        isequal(type, slcoverage.CodeSelectorType.DecisionOutcome) ||...
                        isequal(type, slcoverage.CodeSelectorType.ConditionOutcome) ||...
                        isequal(type, slcoverage.CodeSelectorType.MCDCOutcome) ||...
                        isequal(type, slcoverage.CodeSelectorType.RelationalBoundaryOutcome);
                    if isOutcomeBased
                        if ~SlCov.isCodeOutcomeFilterFeatureOn()
                            error(message('Slvnv:simcoverage:covFilterUnsupportedCodeSelector', ['slcoverage.CodeSelectorType.',char(type)]));
                        end
                        if isequal(type, slcoverage.CodeSelectorType.DecisionOutcome) || ...
                                isequal(type, slcoverage.CodeSelectorType.MCDCOutcome)
                            narginchk(2, 6);
                        else
                            narginchk(2, 7);
                        end
                    else
                        narginchk(2, 5);
                    end
                    exprIdxValidationFcn = @(x)validateattributes(x, {'numeric'}, {'positive', 'nonzero', 'scalar'}, 'CodeSelector', 'exprIdx', 5);
                    argParser.addRequired('funName', @(x)strArgValidFcn(x, 'funName', 3));
                    argParser.addRequired('expr', @(x)strArgValidFcn(x, 'expr', 4));
                    argParser.addRequired('exprIdx', exprIdxValidationFcn);
                    argParser.addOptional('outcomeIdx', [], alwaysTrue);
                    argParser.addOptional('decOrCondIdx', [], alwaysTrue);
                    parse(argParser, varargin{:});

                    % Check the expected arguments for outcome based selector
                    if isOutcomeBased
                        if isequal(type, slcoverage.CodeSelectorType.DecisionOutcome)
                            % TODO(eroy) would love to support 'T' and 'F'
                            % but it doesn't work for switch...
                        elseif isequal(type, slcoverage.CodeSelectorType.ConditionOutcome)
                            % TODO(eroy): allow 'T' and 'F' as outcome and convert to 1 and 2
                        elseif isequal(type, slcoverage.CodeSelectorType.RelationalBoundaryOutcome)
                            % TODO(eroy): allow '<', '>' and '==' as outcome and convert to 1, 2 and 3
                        end
                        validateattributes(argParser.Results.outcomeIdx, {'numeric'}, {'positive', 'nonzero', 'scalar'}, 'CodeSelector', 'outcomeIdx', 6);
                        if ~isempty(argParser.Results.decOrCondIdx)
                            validateattributes(argParser.Results.decOrCondIdx, {'numeric'}, {'positive', 'nonzero', 'scalar'}, 'CodeSelector', 'decOrCondIdx', 7);
                        end
                    end

                    this.FunctionName = argParser.Results.funName;
                    this.Expr = argParser.Results.expr;
                    this.ExprIndex = argParser.Results.exprIdx;
                    switch type
                        case {slcoverage.CodeSelectorType.Decision, slcoverage.CodeSelectorType.DecisionOutcome}
                            this.CVMetricType = 1; % to be compatible with old behavior (true was passed)
                        case {slcoverage.CodeSelectorType.Condition, slcoverage.CodeSelectorType.ConditionOutcome}
                            this.CVMetricType = 0; % to be compatible with old behavior (false was passed)
                        case slcoverage.CodeSelectorType.MCDCOutcome
                            this.CVMetricType = 2;
                        case slcoverage.CodeSelectorType.RelationalBoundaryOutcome
                            this.CVMetricType = 3;
                    end
                    if isOutcomeBased
                        this.OutcomeIndex = argParser.Results.outcomeIdx;
                        this.DecOrCondIndex = argParser.Results.decOrCondIdx;
                    end
                    constructorCode = [constructorCode,...
                        sprintf(', ''%s'', ''%s'', %s', 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(modelH, varargin)
            s = slcoverage.Selector.allSelectors(modelH,...
                'Type', 'slcoverage.CodeSelectorType',...
                varargin{:});
        end
    end
    
    %% --------------------------------------------------------------------
    methods (Hidden = true)
        function codeSelectors = convertToCodeSelectors(this, ~)
            codeSelectors = {this};
        end
    end
end
