
%

%   Copyright 2012-21 The MathWorks, Inc.

classdef Test < handle
    %TEST elementary abstract class that all rtiostream tests should extend
    %   Extend this class to implement your own test for use with the
    %   rtiostreamtest suite. The only function you should worry about is
    %   the RUNTEST. You should put your test performing logic inside which
    %   should use the provided server and parameters.
    %   Test also provides facilities for parsing and setting parameters in
    %   a cell array of parameters. It also provides a progress bar that
    %   tests could use and other tools.

    properties (GetAccess = 'protected', SetAccess = 'protected')
        % For progress bar
        lastCharCount = -1;
        
        % Default name of a test
        name = 'Test';
        
        % Whether or not to skip this Test
        skip = false;
    end
    
    properties  (Constant, GetAccess = 'private')
    % Constants for drawing progress bar
        percBarSizeInChars = 9;    % Width of the progress bar in chars
        perActionChar = '.';        % Char for a ready char of the bar
        perOverflowChar = 'X';      % Char if percentage is > 100% (error)
    end
    
    methods (Abstract)
        
        %RUNTEST performs a test using a connection with custom parameters
        %   Override this function to implement your own test logic. This
        %   function is used by the test engine to invoke your test. This
        %   function can return an arbitrary number of arguments (including
        %   none). Make sure that the caller knows how many arguments to
        %   expect.
        %
        %   CONNECTION - the connection you should use to send commands and
        %                data to so that you could perform your test
        %   PARAMETERS - A list of parameters that you could use alongside
        %                with GETSETTINGS, SETSETTING and SETSETTINGS in
        %                order to parse/set different options for your
        %                test.
        runTest(this, connection, parameters);
    end
    
    methods
        
        function this = Test(name)
            %TEST(NAME) where NAME is optional
            %   Only call the super constructor TEST(NAME) from your class
            %   if you want to set up a name of the test you are
            %   implementing. Otherwise explicit call to this constructor
            %   is not necessary.
            
            if nargin == 1, this.name = name; end;
        end
        
        function name = toString(this)
            %TOSTRING get the name of the current test
            name = this.name;
        end
    end
    
    methods (Access = protected)
        
        function progress(this, value)
        % PROGRESS Draws the progress bar
        %   VALUE - a fraction between 0 and 1 representing the progress
        %           that you would require to be shown on the screen.
        
        
            done = false;
            if strcmp(value, 'done')
                done = true;
                value = 1;
            end
            
            char = this.perActionChar;
            if value>1
                value = 1;
                char = this.perOverflowChar;
            end
            charCount = round(value * this.percBarSizeInChars);
            
            % If no need to redraw
            if this.lastCharCount == charCount && value ~= 1
                return
            end
            
            % If no need to delete
            if this.lastCharCount ~= -1
                fprintf(repmat(char,1,charCount - this.lastCharCount));
            end
            
            if done, fprintf(' '); this.print('PassLabel'); end;
            
            % Store last progress
            this.lastCharCount = charCount;
        end
        
        function resetProgress(this)
        %RESETPROGRESS call this each time before your fist PROGRESS call
        %   This function resets the progress bar so that the layout does
        %   not break        
            this.lastCharCount = -1;
        end
    end
    
    methods (Static)
        
        function value = getSetting(parameters, name, default)
            %GETSETTING Gets a value from a parameter cell array
            %   PARAMETERS -
            %       Is in format { ... 'nameX', valueX, {'nameY', valueY} ... }
            %   NAME -
            %       The name of the parameter which value you want to obtain
            %   DEFAULT -
            %       If a parameter with the required name is not found in the
            %       parameters list, this value is returned instead. If such a
            %       value is not specified, error will be thrown in case a
            %       parameter with the required name is not found.
            %
            %   Usage: if PARAMETERS = {'a', [1 2 3], 'b', 'test'}
            %   then GETSETTING(PARAMETERS, 'a') will return [1 2 3]
            %   and  GETSETTING(PARAMETERS, 'b') will return 'test'
            
            % Get the value of the required parameter
            for k = 1 : 2 : numel(parameters)
                if strcmp(parameters{k}, name)
                    value = parameters{k+1};
                    return;
                end
            end
            
            % If value does not exist
            if nargin == 2
                coder.rtiostreamtest.Test.error('ErrorRequiredArgumentMissing', name, evalc('disp(parameters)'));
            else
                value = default;
            end
        end
        
        function parameters = setSettings(parameters, newparameters)
            %SETSETTINGS overwrites all PARAMETERS values if they exist
            %   Merges NEWPARAEMTERS and PARAMETERS so that all values
            %   specified in PARAMETERS are overridden by NEWPARAMETERS. If
            %   a parameter exists in NEWPARAMETERS but not in PARAMETERS,
            %   it is explicitly added to PARAMETERS
            %
            %   Usage: if PARAMETERS = {'a', [1 2 3], 'b', 'test'}
            %   Then SETSETTINGS(PARAMETERS, {'b', [1 2 3 4], 'c', false}
            %   would return {'a', [1 2 3], 'b', [1 2 3 4], 'c', false}
            
            % Get the value of the required parameter
            for k = 1 : 2 : numel(newparameters)
                parameters = coder.rtiostreamtest.Test.setSetting(parameters, newparameters{k}, newparameters{k+1});
            end
        end
        
        function parameters = setSetting(parameters, name, newValue)
            %SETSETTING overrides a value of a parameter in the array
            %	Replace value if a parameter with the specified NAME
            %	already exist in the PARAMETERS list with NEWVALUE,
            %	otherwise add a parameter with NAME and NEWVALUE to the
            %	PARAMETERS list
            %
            %   Usage: if PARAMETERS = {'a', [1 2 3], 'b', 'test'}
            %   Then SETSETTING{PARAMETERS, 'b', [1 2 3 4]} would return
            %       {'a', [1 2 3], 'b', [1 2 3 4]}
            %   and SETSETTING{PARAMETERS, 'c', false} would return
            %       {'a', [1 2 3], 'b', 'test', 'c', false}
            
            for k = 1 : 2 : numel(parameters)
                if strcmp(parameters{k}, name)
                    parameters{k+1} = newValue;
                    return;
                end
            end
            
            % Insert new value in case it does not exist
            parameters = [parameters {name, newValue}];
        end
        
        function result = displayDataSize(byteCount, rounding)
            %RESULT = DISPLAYDATASIZE(BYTECOUNT, ROUNDING) Decides whether to display
            %data in B or KB
            %
            %   BYTECOUNT   - The amount of data in bytes that should be displayed
            %   ROUNDING    - With what accuracy should the number be displayed
            %
            %   RESULT      - A string containing the data information
            
            if nargin == 1
                rounding = 2;
            end
            
            if byteCount < 1024
                result = coder.rtiostreamtest.Test.message('DataBytes', byteCount);
            elseif byteCount < 1024*1024
                result = coder.rtiostreamtest.Test.message('DataKilobytes', ...
                    sprintf(['%.' num2str(rounding) 'f'], double(byteCount)/1024.0));
            else
                result = coder.rtiostreamtest.Test.message('DataMegabyte', ...
                    sprintf(['%.' num2str(rounding) 'f'], double(byteCount)/(1024.0*1024.0)));
            end
        end
        
    end
    
%% For outputting errors and messages
% For internal use only. You should not be accessing those even if you are
% implementing a custom TEST.

properties(Constant = true, GetAccess = 'private')
    productId = 'Connectivity';
    component = 'RtIOStreamTest';
end

methods(Static = true)
    
    function error(messageId, varargin)
        %ERROR throw an error
        varargin = coder.rtiostreamtest.Test.stringify(varargin);
        try
            DAStudio.error(coder.rtiostreamtest.Test.getFullId(messageId), ...
                varargin{:});
        catch e
            e.throwAsCaller;
        end
    end
    
    function warning(messageId, varargin)
        %WARNING issue a warning
        varargin = coder.rtiostreamtest.Test.stringify(varargin);
        MSLDiagnostic(coder.rtiostreamtest.Test.getFullId(messageId), ...
            varargin{:}).reportAsWarning;
    end
    
    function verboseln(condition, messageId, varargin)
        %VERBOSE output verbose message an a new line if CONDITION is TRUE
        if condition
            fprintf( coder.rtiostreamtest.Test.messageln('Verbose', ...
                coder.rtiostreamtest.Test.message(messageId, varargin{:}) ...
                ));
        end
    end
    
    function lnverboseln(condition, messageId, varargin)
        %VERBOSE output a new line, verbose message an a new line again
        if condition
            fprintf( ['\n' coder.rtiostreamtest.Test.messageln('Verbose', ...
                coder.rtiostreamtest.Test.message(messageId, varargin{:}) ...
                )] );
        end
    end
    
    function warning_mild(messageId, varargin)
        %WARNING_MILD issue a warning as a fprintf
        coder.rtiostreamtest.Test.println('MildWarning', ...
            coder.rtiostreamtest.Test.message(messageId, varargin{:}) ...
            );
    end
    
    function print(messageId, varargin)
        %PRINT decode message and fprintf to screen
        fprintf( coder.rtiostreamtest.Test.message(messageId, varargin{:}) );
    end
    
    function println(messageId, varargin)
        %PRINTLN print message to screen with new line at the end
        fprintf( coder.rtiostreamtest.Test.messageln(messageId, varargin{:}) );
    end
    
    function lnprintln(messageId, varargin)
        %LNPRINTLN print new line, message and new line again at the end
        fprintf( ['\n' coder.rtiostreamtest.Test.messageln(messageId, varargin{:})] );
    end
    
    function link = buildhyperlink(text, hint, shorttext)
        %BUILDHYPERLINK creates a new clickable hyperlink ready for print
        if nargin == 2, shorttext = text; end;
        link = targets_hyperlink_manager('new', text, @disp, sprintf('\n%s:\n\n%s', shorttext, hint));               
    end
    
    function msg = message(messageId, varargin)
        %MESSAGE decode a message. Arguments must be either char arrays or
        %numbers
        varargin = coder.rtiostreamtest.Test.stringify(varargin);
        msg = DAStudio.message(coder.rtiostreamtest.Test.getFullId(messageId), ...
            varargin{:});
    end
    
    function msg = messageln(messageId, varargin)
        %MESSAGELN decode a message and put a new line at the end.
        msg = [coder.rtiostreamtest.Test.message(messageId, varargin{:}) '\n'];
    end
    
    function header(messageId, varargin)
        %HEADER prints a message formatted as a header
        coder.rtiostreamtest.Test.headerraw(coder.rtiostreamtest.Test.message(messageId, varargin{:}));
    end
    
    function headerraw(string)
        %HEADERRAW prints a header from string
        fprintf ('\n### %s\n', string);
    end
    
    function majorheader(messageId, varargin)
        %MAHORHEADER prints a message formatted as a major header
        coder.rtiostreamtest.Test.majorheaderraw(coder.rtiostreamtest.Test.message(messageId, varargin{:}));
    end
    
    function majorheaderraw(string)
        %MAJORHEADERRAW prints a major header from string
        fprintf ('\n### %s ###\n', string);
    end
    
    function fullId = getFullId(messageId)
        %FULLID get the full id of the message for use with DASTUDIO
        fullId = ...
            [coder.rtiostreamtest.Test.productId ':' coder.rtiostreamtest.Test.component ':' messageId];
    end
    
    function args = stringify(args)
        %STRINGIFY turn numeric arguments to string arguments if any
        for i = 1 : numel(args)
            if isnumeric(args{i})
                args{i} = num2str(args{i});
            end
        end
    end
    
end

end
