classdef utils
    %XCP.BLKMASKS.UTILS -  Utility functions for XCP block masks.  These
    %functions are either called by the mask classes, or by the block mask
    %initialization/display commands

%   Copyright 2018-2023 The MathWorks, Inc.

    methods (Static = true)
        
        function help(docTag)
            % Callback for Help button on the mask dialog.
            block = gcb;
            if bdIsLibrary(bdroot(block))
                % Block in libraries return a different product code. 
                % As the library links to shared common library and product code will return corresponding top that. 
                % So overwrite based on the owner product for the library.
                mdlPath = split(block, '/');
                if ~isempty(mdlPath{1})
                    docType = Simulink.internal.license.getProductInfo(mdlPath{1});
                    % Consider MathWorks shipping library only. 
                    if (~isempty(docType) && ~isempty(docType.ProductCode)) && ...
                            ~isequal(docType.ProductCode, '?')
                        block = mdlPath{1};
                    end
                end
            end

            % Identify the product which owns parent library. 
            docType = Simulink.internal.license.getProductInfo(block);
            % Initialize map file location.
            productName = '';

            % Warning if empty product code below. 
            if (~isempty(docType) && ~isempty(docType.ProductCode))
                % The product is SLRT.
                if isequal(docType.ProductCode, 'XP')
                    productName = 'slrt';
                % The product is VNT. 
                else
                    productName = 'vnt';
                end
            end

            % Scenarios possible: The product code is empty because of the custom user library. 
            if isempty(productName)
                warndlg(DAStudio.message('xcp:xcpblks:noProductDocumentation'),getString(message('xcp:xcpblks:Warning')),'modal');
                return;
            end

            % Display the help page. 
            helpview(productName, docTag);
        end
        
        function [dispString, port, NMeasurements] = getDisplayString(enableTimestamp)
            %Return a display string for XCP blocks.
            %
            % We are called every time the mask is initialized,
            % so the current block is always ours.
            blk = gcb;
            blkh = gcbh;
            
            
            if (~exist('enableTimestamp','var'))
                enableTimestamp = 0;
            end

            % Set port to empty.
            port = [];
            % Set dispString to empty.
            dispString = [];

            % Set the default.
            NMeasurements = 1;
            
            % Get information for Dislay of physical unit based on block mask selection for CompuMethod conversion
            % The try catch makes sure that the mask is still displayed if we fail to capture the a2l file.
            % Find config block and get slave ID
            dispFlag = true;
            a2lObj = [];
            try
                configName = get_param(blkh, 'SlaveName');
                TLTypeStr = xcp.blkmasks.utils.getTLTypeStr(blkh);
                % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
                % instead use the post-compile filter activeVariants() - g2603543
                configBlk = find_system(bdroot(blk), 'FollowLinks', 'on', ...
                            'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices,... % look only inside active choice of VSS
                            'LookUnderMasks', 'all', ...
                            'MaskType',['XCP ',TLTypeStr,' Configuration'],...
                            'SlaveName',configName);
                fileName = get_param(configBlk{1}, 'A2LFile');        
                a2lObj = xcpA2L(fileName,'FillTopLevelView',false); 
            catch
                dispFlag = false;
            end

            % Check if we're in the library. If so, don't display any dynamic info.
            parentBlk = get_param(blk, 'Parent');
            % Determine the block type.
            maskType = get_param(blk, 'MaskType');
            if ( strcmpi(parentBlk, 'xcpprotocollib/UDP') || ...
                 strcmpi(parentBlk, 'xcpprotocollib/CAN') || ...
                 strcmpi(parentBlk, 'xcpprotocollib/CAN FD') || ...
                 strcmpi(parentBlk, 'xcpcantllib')        || ...
                 strcmpi(parentBlk, 'vntxcplib/CAN')      || ...
                 strcmpi(parentBlk, 'vntxcplib/CAN FD')   || ...
                 strcmpi(parentBlk, 'vntxcplib/UDP')      || ...
                 strcmpi(parentBlk, 'xcpcantllib')        || ...
                 strcmpi(parentBlk, 'xcpcanfdtllib')      || ...
                 strcmpi(parentBlk, 'xcprtlib/CAN')        || ...
                 strcmpi(parentBlk, 'xcprtlib/UDP')       || ...
                 strcmpi(parentBlk, 'xcprtlib/CAN FD'))
             
                blkDiagType = get_param(bdroot, 'BlockDiagramType');
                if strcmpi(blkDiagType, 'library')
                    % Display String inside the library
                    switch maskType
                        case 'XCP UDP Configuration'
                            dispString = getString(message('xcp:xcpblks:XCPUDPConfiguration'));
                        case 'XCP UDP Data Acquisition'
                            dispString = getString(message('xcp:xcpblks:XCPUDPDataAcquisition'));
                            port(1).id = 1;
                            port(1).label = '';
                        case 'XCP UDP Data Stimulation'
                            dispString = getString(message('xcp:xcpblks:XCPUDPDataStimulation'));
                            port(1).id = 1;
                            port(1).label = '';
                        case 'XCP CAN Configuration'
                            dispString = getString(message('xcp:xcpblks:XCPCANConfiguration'));
                        case 'XCP CAN FD Configuration'
                            dispString = getString(message('xcp:xcpblks:XCPCANFDConfiguration'));
                        case 'XCP CAN Data Acquisition'
                            dispString = getString(message('xcp:xcpblks:XCPCANDataAcquisition'));
                            port(1).id = 1;
                            port(1).label = '';
                        case 'XCP CAN FD Data Acquisition'
                            dispString = getString(message('xcp:xcpblks:XCPCANFDDataAcquisition'));
                            port(1).id = 1;
                            port(1).label = '';
                        case 'XCP CAN Data Stimulation'
                            dispString = getString(message('xcp:xcpblks:XCPCANDataStimulation'));
                            port(1).id = 1;
                            port(1).label = '';
                        case 'XCP CAN FD Data Stimulation'
                            dispString = getString(message('xcp:xcpblks:XCPCANFDDataStimulation'));
                            port(1).id = 1;
                            port(1).label = '';
                        case 'XCP CAN Transport Layer Receive'
                            dispString = getString(message('xcp:xcpblks:XCPCANTLReceive'));
                        case 'XCP CAN Transport Layer Transmit'
                            dispString = getString(message('xcp:xcpblks:XCPCANTLTransmit'));
                        case 'XCP CAN FD Transport Layer Receive'
                            dispString = getString(message('xcp:xcpblks:XCPCANFDTLReceive'));
                        case 'XCP CAN FD Transport Layer Transmit'
                            dispString = getString(message('xcp:xcpblks:XCPCANFDTLTransmit'));
                        otherwise 
                            assert(false,'Internal Error: Invalid Use Case');
                    end
                    return;
                end
            end
            switch maskType
                case {'XCP UDP Configuration', 'XCP CAN Configuration', 'XCP CAN FD Configuration'}
                    slaveName = get_param(blkh, 'SlaveName');
                    fullFile = get_param(blkh, 'A2LFile');
                    [~, a2lFile, ext] = fileparts(fullFile);
                    a2lFile = strcat(a2lFile, ext);
                    if isempty(a2lFile)
                        a2lFile = getString(message('xcp:xcpblks:NoA2LFileSelected'));
                    end
                    dispString = getString(message('xcp:xcpblks:ConfigNameString', slaveName, a2lFile));
                    nInputPorts = 0;
                    nOutputPorts = 0;
                    if strcmpi(get_param(blkh, 'EnableStatus'), 'on')
                        nOutputPorts = 1;
                        port(1).id = 1;
                        port(1).label = 'Status';
                    end
                case {'XCP UDP Data Acquisition', 'XCP CAN Data Acquisition', 'XCP CAN FD Data Acquisition'}
                    dispString = localGetDisplayString(blkh);
                    nInputPorts = 0;
                    measurements = get_param(blkh, 'SelectedMeasurements');
                    Datatype = get_param(blkh,'InputOrOutputDatatype');
                    [nOutputPorts, port] = localGetPortInfo(measurements, a2lObj, Datatype, dispFlag);
                    NMeasurements = nOutputPorts;
                    %Add the timestamp port:
                    if (enableTimestamp)
                        timePort = port(end).id+1;
                        port(timePort).id = timePort; %#ok<*AGROW>
                        port(timePort).label = 'Timestamp';
                        nOutputPorts = nOutputPorts+1;
                    end
                case {'XCP UDP Data Stimulation', 'XCP CAN Data Stimulation', 'XCP CAN FD Data Stimulation'}
                    dispString = localGetDisplayString(blkh);
                    measurements = get_param(blkh, 'SelectedMeasurements');
                    Datatype = get_param(blkh,'InputOrOutputDatatype');
                    [nInputPorts, port] = localGetPortInfo(measurements, a2lObj, Datatype, dispFlag);
                    nOutputPorts = 0;
                    NMeasurements = nInputPorts;
                    %Add the timestamp port:
                    if (enableTimestamp)
                        timePort = port(end).id+1;
                        port(timePort).id = timePort; %#ok<*AGROW>
                        port(timePort).label = 'Timestamp';
                        nInputPorts = nInputPorts+1;
                    end
                case 'XCP CAN Transport Layer Receive'
                    % Form display string.
                    dispString = getString(message('xcp:xcpblks:XCPCANTLReceive'));
                    nInputPorts = 2;
                    nOutputPorts = 0;
                    port(1).id = 1;
                    port(1).label = sprintf('CAN Msg');
                    port(2).id = 2;
                    port(2).label = sprintf('N');
                case 'XCP CAN Transport Layer Transmit'
                    % Form display string.
                    dispString = getString(message('xcp:xcpblks:XCPCANTLTransmit'));
                    nInputPorts = 0;
                    nOutputPorts = 2;
                    port(1).id = 1;
                    port(1).label = sprintf('CAN Msg');
                    port(2).id = 2;
                    port(2).label = sprintf('N');     
                case 'XCP CAN FD Transport Layer Receive'
                    % Form display string.
                    dispString = getString(message('xcp:xcpblks:XCPCANFDTLReceive'));
                    nInputPorts = 2;
                    nOutputPorts = 0;
                    port(1).id = 1;
                    port(1).label = sprintf('CAN FD Msg');
                    port(2).id = 2;
                    port(2).label = sprintf('N');
                case 'XCP CAN FD Transport Layer Transmit'
                    % Form display string.
                    dispString = getString(message('xcp:xcpblks:XCPCANFDTLTransmit'));
                    nInputPorts = 0;
                    nOutputPorts = 2;
                    port(1).id = 1;
                    port(1).label = sprintf('CAN FD Msg');
                    port(2).id = 2;
                    port(2).label = sprintf('N');
                otherwise
                    assert(false,'Internal Error: Invalid Use Case');
            end
            maskDisplayString = localBuildMaskDisplayString(nInputPorts, nOutputPorts);
            set_param(blkh, 'MaskDisplay', maskDisplayString);
        end
            
        function isValid = validateSampleTime(value)
            % Convert sample time to number. 
            sampleTime = str2num(value); %#ok<ST2NM>

            % Initialize
            isValid = true;

            % Allow variable names but do not allow NaN, i and j (complex) as
            % variables. If they are left undefined, it yields incorrect results.
            if isvarname(value) && ~ismember(lower(value), {'nan', 'i', 'j', 'inf'})  
                return;
            end

            % Check for non-numeric
            if  isempty(sampleTime) || ~isscalar(sampleTime) || ~isnumeric(sampleTime) || ...
                    isnan(sampleTime) || isinf(sampleTime)
                isValid = false;
                return;
            end
     
            if (sampleTime<=0 && sampleTime~=-1)
                isValid = false;
                return;
            end

        end

        function TLTypeStr = getTLTypeStr(block)
            maskType = get_param(block,'MaskType');
            if contains(maskType,'UDP')
                TLTypeStr = 'UDP';
            elseif contains(maskType, 'CAN FD')
                TLTypeStr = 'CAN FD';
            elseif contains(maskType,'CAN')
                TLTypeStr = 'CAN';
            else
                assert(false,'Internal Error: Invalid Use Case');
            end
        end
        
    
        function onOff =  logical2OnOff(logicalVal)
            if logicalVal
                onOff = 'on';
            else
                onOff = 'off';
            end
        end

        function logicalVal = onOff2Logical(onOffString)
            if strcmp(onOffString, 'on')
                logicalVal = true;
            elseif strcmp(onOffString, 'off')
                logicalVal = false;
            else
                error(message('xcp:xcpblks:InvalidInput'))
            end
        end
        
        function ID = returnDTID(sigInfo)
            % % 
            % From matlab\toolbox\shared\xcpcore\src\xcpmpl\xcpmplprivate.h
            % /*Simulink Data Types*/
            % typedef enum {
            %   SS_DOUBLE = 0,                       /* real_T    */
            %   SS_SINGLE = 1,                       /* real32_T  */
            %   SS_INT8 = 2,                         /* int8_T    */
            %   SS_UINT8 = 3,                        /* uint8_T   */
            %   SS_INT16 = 4,                        /* int16_T   */
            %   SS_UINT16 = 5,                       /* uint16_T  */
            %   SS_INT32 = 6,                        /* int32_T   */
            %   SS_UINT32 = 7,                       /* uint32_T  */
            %   SS_BOOLEAN = 8,                      /* boolean_T */
            %   //Above are Simulink Basic Types.  Below are the additional needed types
            %   SS_INT64 = 9,                        /* int64_T   */
            %   SS_UINT64 = 10                       /* uint64_T  */
            % }SimulinkDT;

            % ASAM STANDARD Datatype 
            % UBYTE           unsigned        8     one's complement
            % SBYTE           signed          8     two's complement
            % UWORD           unsigned        16	one's complement
            % SWORD           signed          16	two's complement
            % ULONG           unsigned        32	one's complement
            % SLONG           signed          32	two's complement
            % A_UINT64        unsigned        64	one's complement
            % A_INT64         signed          64	two's complement
            % FLOAT16_IEEE    signed          16    IEEE 754 half   precision
            % FLOAT32_IEEE    signed          32	IEEE 754 single precision
            % FLOAT64_IEEE    signed          64	IEEE 754 double precision
            switch sigInfo.LocDataType
                case 'UBYTE'
                    ID = 0;
                case 'SBYTE'
                    ID = 1;
                case 'UWORD'
                    ID = 2;
                case 'SWORD'
                    ID = 3;
                case 'ULONG'
                    ID = 4;
                case 'SLONG'
                    ID = 5;
                case 'A_UINT64'
                    ID = 6;
                case 'A_INT64'
                    ID = 7;
                case 'FLOAT16_IEEE'
                    ID = 8;  %Note Half precision floating point not
                              %supported in Simulink.
                case 'FLOAT32_IEEE'
                    ID = 9;
                case 'FLOAT64_IEEE'
                    ID = 10;
                otherwise
                    ID = 100;  %Other unrecognized datatype
            end
        end
        
        %CLOSEDIALOG - called from the block CloseFcn callbacks
        function closeDialog(blkh)
           dlgs = DAStudio.ToolRoot.getOpenDialogs;
           for i = 1 : numel(dlgs)
               if blkh == dlgs(i).getSource.BlockHandle
                   delete(dlgs(i));
                   return;
               end    
           end    
        end
        
        
    end
end
   

%% %%%%%%%%%%%%%%%% Local Functions %%%%%%%%%%%%%%%%%%%%

function maskDisplayString = localBuildMaskDisplayString(nInputPorts, nOutputPorts)
    %LOCALUPDATEMASKDISPLAYSTRING Updates Mask Display String value
    %
    %    MASKDISPLAYSTRING = LOCALUPDATEMASKDISPLAYSTRING(NINPUTPORTS, NOUTPUTPORTS)
    %    updates MASKDISPLAYSTRING

    % Build the MaskDisplayString in the block library based on NUMBERPORTS
    maskDisplayString = 'disp(str);';

    portType = '''input''';
    % Loop through number of ports and update mask display string.
    startIdx = 1;
    nestedUpdateStrings(nInputPorts);
    startIdx = nInputPorts + 1;
    portType = '''output''';
    nestedUpdateStrings(nOutputPorts);
        % Nested function to update strings.
        function nestedUpdateStrings(ports)
            for idx = startIdx:startIdx+ports-1
                portID = sprintf('port(%d).id',idx);
                portLabel = sprintf('port(%d).label', idx);
                addStr = sprintf('\nport_label(%s,%s,%s);',portType, portID, portLabel);
                maskDisplayString = strcat(maskDisplayString, addStr);
            end
        end
end


function [nPorts, port] = localGetPortInfo(measurements, a2lObj, Datatype, dispFlag)
    % Initialize to default.
    nPorts = 1;
    port(1).id = 1;
    port(1).label = 'Data';

    measurementsCell = strsplit(measurements,';');    
    if isempty(measurementsCell{1})
        return;
    end
    nPorts = numel(measurementsCell);
    for idx = 1:nPorts
        port(idx).id = idx; %#ok<*AGROW>
        applyPhysUnit = false;
        if strcmpi(Datatype,'Physical values (apply compu method conversion)') && dispFlag
            % Find the conversion unit for display
            sigInfo = a2lObj.getMeasurementInfo(measurementsCell{idx});
            % The physical unit described on the measurement is checked if the conversion is empty. Otherwise, we take the physical unit from the conversion specified
            % If no unit is present then we just display the measurement as is without any units
            if isempty(sigInfo.ConversionName)
                if ~isempty(sigInfo.PhysUnit)
                    physUnit = sigInfo.PhysUnit;
                    applyPhysUnit = true;
                end                
            else
                conversion = a2lObj.Module.CompuMethods{sigInfo.ConversionName};
                if ~isempty(conversion) && ~isempty(conversion.Unit)
                    physUnit = conversion.Unit;
                    applyPhysUnit = true;
                end
            end
        end
        if applyPhysUnit
            port(idx).label = strcat(measurementsCell{idx}, '(', physUnit, ')');
        else
            port(idx).label = measurementsCell{idx};
        end
    end
end

function dispString = localGetDisplayString(blkh)

    slaveName = get_param(blkh, 'SlaveName');
    if strcmpi(slaveName, '<Please select a config name>')
        dispString = getString(message('xcp:xcpblks:NoConfigSelected'));
        return
    end
    eventName = get_param(blkh, 'EventName');
    if isempty(eventName)
        eventName = 'No events';
    end
    dispString = getString(message('xcp:xcpblks:ConfigNameEvent', slaveName, eventName));
end







