classdef (Hidden = true) ModelRefSILPILWrapperUtils < rtw.pil.SILPILWrapperUtils
%MODELREFSILPILWRAPPERUTILS SIL/PIL model reference code interface utilities
%
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2010-2023 The MathWorks, Inc.
        
    methods
        % constructor
        function this = ModelRefSILPILWrapperUtils(silPILInterface, ...
                                            infoStruct, ...
                                            clientInterface,...
                                            topModelOriginalParams, ...
                                            lXilCompInfo)

            % call super class constructor                             
            this@rtw.pil.SILPILWrapperUtils(silPILInterface, ...
                                            infoStruct, ...
                                            clientInterface, ...
                                            topModelOriginalParams, ...
                                            lXilCompInfo);
                                                    
            if this.isModelReferenceTarget && ...
                    ~silPILInterface.IsUpdateModelReferenceTargets
                        % Don't throw error if just updating RTW targets
                        % for model ref propagation.  This code path will
                        % be visited again during XILBuildHook for the top
                        % level of a SIL/PIL hierarchy.
               if strcmp(this.getParam('SupportVariableSizeSignals'), 'on')
                   % so long as sizes of variable-size signals are propagated
                   % during execution we will support variable size signals
                   if ~strcmp(this.getParam('PropagateVarSize'), 'During execution')                    
                       DAStudio.error('PIL:pil:ModelBlockVariableSizeSignals', ...
                                       this.getComponentForDiagnostics(), ...
                                       this.getParam('PropagateVarSize'));                    
                   end
               end
            end
            
            info = this.getSILPILInterface.getInfoStruct.modelInterface;
            
            
            % Check that the model has inports
            if ~isfield(info,'Inports')          
                this.InportDirectFeedThrough = [];
            else            
                % Filter out Function call Inports. 
                % info.Inports can be a single matrix or a cellarray,
                % depending by its cardinality 
                if isscalar(info.Inports)
                    inports{1} = info.Inports;
                else
                    inports = info.Inports;
                end                
                
                noFcn = @(y)~inports{y}.IsFcnCall; %Filter out function call inputs
                
                localInports = inports(arrayfun(noFcn,1:numel(info.Inports) ));
                
                for i = 1:numel(localInports)
                    this.InportDirectFeedThrough(i) = localInports{i}.DirectFeedThrough;
                end
            end
            
            
        end       
        
        function runDeferredValidation(this)
            % Deferred validation to be run only before attempting to build
            % the XIL application
            if this.isModelReferenceTarget && ...
                    ~this.getSILPILInterface.IsUpdateModelReferenceTargets
                % continuous time is not supported at all for Model Block SIL/PIL
                % owing to solver complexities
                if (strcmp(this.getParam('SupportContinuousTime'), 'on') && ~isempty(this.CodeInfoUtils.getCodeInfo.DerivativeFunction))
                    rtw.pil.ProductInfo.error('pil', 'ModelBlockContinuousTime', ...
                        this.getComponentForDiagnostics());
                end
            end
        end

        % return whether we should obfuscate the SIL/PIL wrapper files
        function obfuscateCode = shouldObfuscateXILWrapperFiles(this)
        % obfuscate the SIL/PIL wrapper files if the referenced model is protected
        % and if its contents is either obfuscated or binaries.
            obfuscateCode = false;
            if this.isModelProtected
                opts = Simulink.ModelReference.ProtectedModel.getOptions(...
                    this.SILPILInterface.ProtectedModelFullName);                
                obfuscateCode = opts.obfuscateCode || opts.binariesAndHeadersOnly;
            end
        end
        
        % Return the top model.
        function instrumentationSettingsModel = getInstrumentationSettingsModel(this)                    
            isTopModelXILSim = this.getSILPILInterface.isTopModelXILSim;
            if isTopModelXILSim
                instrumentationSettingsModel = this.SILPILInterface.getCodeName();
            else
                instrumentationSettingsModel = this.SILPILInterface.getTopModelForModelBlockPIL;        
            end
        end  
        
        % accessor for Top-Model ConfigSet params
        function value = getInstrumentationSettingsModelParam(this, param)                                   
           % assume model is open
           if ~isempty(this.TopModelOriginalParams) && this.TopModelOriginalParams.isKey(param)
               value = this.TopModelOriginalParams(param);
           else
               value = get_param(this.getInstrumentationSettingsModel, param);
           end
        end  
        
        function dataStoresToTransfer = getDataStoresToTransferIndices(this, allIndices, dsmType)
            % for backwards compatibility
            % (e.g. tSharedLocalDSM: verifyFileSSCSIL (File Scope)), limit
            % what we transfer when the Model block is used for top-model
            % SIL/PIL
            if this.getSILPILInterface.isTopModelXILSim
                dataStoresToTransfer = ...
                    getDataStoresToTransferIndices@rtw.pil.SILPILWrapperUtils(...
                        this, ...
                        allIndices, ...
                        dsmType); 
            else
                dataStoresToTransfer = allIndices;
            end
        end

        % Does this flavor support tunable workspace parameters?
        function supports = supportsModelWorkspaceParams(this)
            if this.getSILPILInterface.isTopModelXILSim || ...
                (matlab.internal.feature('XILInitModelWorkspaceParams') && ...
                    ~this.isModelProtected && ...
                    ~coder.connectivity.XILSubsystemUtils.isSubsystemWorkflow(this.SILPILInterface.getTopModelForModelBlockPIL))
                % Supports this by registering these
                % parameters as S-Function parameters on the Model block's
                % hidden S-Function.   The values come from the duplicated
                % model workspace in the harness model.
                supports = true;
            else
                supports = false;
            end
        end
    end     
    
    methods (Access = 'protected')                        
        
        function matFileName = getCAPIMatFileName(this)
            if this.isModelReferenceTarget 
               matFileName = 'capi_mdlref.mat';
            else
               matFileName = 'capi.mat';
            end
        end                                           
                                                      
        function warnForUnsupportedTunableParameters = warnForUnsupportedTunableParameters(this)            
            if this.getSILPILInterface.isTopModelXILSim
                % for backwards compatibility with classic top-model 
                % SIL/PIL, warn.
                warnForUnsupportedTunableParameters = true; 
            else
                % error
                warnForUnsupportedTunableParameters = false;
            end
        end
        
        % validate non-workspace parameters
        function paramIODatatypes = validateTunableNonWorkspaceParametersHook(this)            
            
            paramIODatatypes = [];
            allInstPIndices = [];
            this.InstPArgumentIndices =  [];
            isModelReferenceTarget = this.isModelReferenceTarget();
            isStandaloneMDX = ~isModelReferenceTarget && strcmp(this.getParam('SystemTargetFile'), 'mdx.tlc');
            if ~isStandaloneMDX
                % unless standalone target and mdx get all InstP indices
                [~, unsortedParamIndices] = this.CodeInfoUtils.isGroupedParameter(this.getCodeInfo().Parameters);
                % make sure indices associated to model arguments are 
                % ordered correctly, as expected by SimulinkEngine
                allInstPIndices = this.CodeInfoUtils.getSortedParamsIndices(unsortedParamIndices);
            end
            
            if isModelReferenceTarget || strcmp(this.getParam(...
                    'CodeInterfacePackaging'), 'Reusable function')
                % for Model reference CodeInterface or standalone code with
                % reusable function packaging, error out if we have
                % non-tunable parameters because their initial value is not 
                % available and simulation will have numerical mismatch
                
                % process all model args, including instP
                maIndices = this.CodeInfoUtils.getModelArgParams();                
                
                causes = {};
                for argIdx = maIndices
                    if isStandaloneMDX && ismember(allInstPIndices, argIdx)
                        % skip InstP for standalone MDX, no need to error
                        % since we do not process InstP in this case
                        continue;
                    end
                    param = this.getCodeInfo().Parameters(argIdx);
                    isReadOnly = this.CodeInfoUtils.isDataInterfaceReadOnly(param);
                    if isReadOnly
                        diString = this.CodeInfoUtils.getDataInterfaceString(param, ...
                            'Parameter');
                        causes{end+1} = MException(message('PIL:pil:ReadOnlyDataInterface', ...
                            diString)); %#ok<AGROW>
                        continue;
                    end
                    
                    isStatic = this.CodeInfoUtils.isDataInterfaceStatic(param);
                    if isStatic
                        diString = this.CodeInfoUtils.getDataInterfaceString(param, 'Parameter');
                        causes{end+1} = MException(message('PIL:pil:StaticDataInterface', ...
                            diString)); %#ok<AGROW>
                        continue;
                    end
                end
                
                if ~isempty(causes)
                    exception = MException(message('PIL:pil:UnsupportedReadOnlyModelArg'));
                    for idx = 1:length(causes)
                        exception = exception.addCause(causes{idx});
                    end
                    exception.throw();
                end
                
                dataElements = rtw.pil.CodeInfoData.createHostDataElements(this, maIndices, 'parameter');
                paramIODatatypes = this.validateDataElements(dataElements);
                % cache the InstP parameter indices, those are a subset of
                % modelArgs
                this.InstPArgumentIndices = allInstPIndices;
            else
                % for model block with Top model CodeInterface and top-level
                % simulations check if InstP arguments are tunable, cache 
                % the parameters index and data type
                for paramIdx = allInstPIndices
                    param = this.getCodeInfo.Parameters(paramIdx);
                    isTunable = this.checkParameterTunabilitySupportAndAppendWarning(param, paramIdx);
                    if isTunable
                        % create a DataElement and cache the parameter data type
                        dataElements = rtw.pil.CodeInfoData.createHostDataElements(this, paramIdx, 'parameter');
                        paramIODatatypes = [paramIODatatypes this.validateDataElements(dataElements)]; %#ok<AGROW>
                        % cache the parameter index
                        this.InstPArgumentIndices = [this.InstPArgumentIndices paramIdx];
                    end
                end
            end
        end                 
        
        function validateIndividualPIMMapping(this)
            % detect individual mapping of 'internals' to AUTOSAR PIM and
            % error out
            if this.isModelReferenceTarget && strcmp(this.getParam('AutosarCompliant'), 'on')
                for idx = 1:numel(this.getCodeInfoUtils.CodeDescriptorInternals)
                    impl = this.getCodeInfoUtils.CodeDescriptorInternals(idx).Implementation;
                    if ~isempty(impl) && ...
                            (isa(impl, 'coder.descriptor.AutosarMemoryExpression')) && ...
                            strcmp(impl.DataAccessMode, 'ArTypedPerInstanceMemory')
                        rtw.pil.ProductInfo.error('pil', 'UnsupportedIndividualPIMMapping');
                    end
                end
            end
        end
        
        function validateModelArguments(this)
            
            if ~this.getSILPILInterface.isTopModelXILSim && ...
                    ~this.isModelReferenceTarget
                % Standalone code generation fully supports instance
                % specific (grouped) Model Arguments.    Non-instance
                % specific Model Arguments become inlined in the generated
                % code.    This is ok for top-model SIL/PIL where the use
                % of the Model block is hidden in the wrapper model.    For
                % regular Model block SIL/PIL, to guarantee to match 
                % simulation behavior across different simulation modes we
                % error out.
                
                % Cell array of model args in MF0. For top-level code MF0
                % contains only instance specific parameters
                mf0 = this.MF0ModelInfo;
                modelArgs = mf0.interfaceParameterInfo.modelArguments.toArray;
                modelArgsInMF0 = arrayfun(@(x)x.parameterName, modelArgs, 'UniformOutput', false);
                
                % use serialized ParameterArgumentNames in binfo.
                parameterArgumentNames = this.getInfoStruct.ParameterArgumentNames;
                tokens = regexp(parameterArgumentNames, '(\w*),?', 'tokens');
                % cell array of model args in the block UI
                if isempty(tokens)
                    modelArgsInUI = tokens;
                else
                    modelArgsInUI = [tokens{:}];
                end
                
                % if the UI contains more arguments than MF0, i.e. there is
                % at least one non-instance-specific argument
                isInUIAndMF0 = ismember(modelArgsInUI, modelArgsInMF0);
                if ~all(isInUIAndMF0)
                    argumentNames = strjoin(modelArgsInUI(~isInUIAndMF0), ', ');
                    this.CodeInfoUtils.throwUnsupportedModelArgsError(...
                        this.getComponentForDiagnostics(), argumentNames);
                end
            end
        end

        function initDataStoresToTransferInterfaces(this)
            % This method initializes DataStoresToTransferInterfaces cache.
            % It is necessary to cache DSM data interfaces to avoid looping
            % over the interfaces every time we call 
            % "getDataStoresToTransferInterfaces". This can cause a
            % significant performance regression for models with large
            % quantities of DSM's. This overrides the default implementation
            % of the parent class.
            allInterfaces = this.CodeInfoUtils.getAllSharedDataStores();
            if this.getSILPILInterface.isTopModelXILSim
                % for backwards compatibility
                % (e.g. tSharedLocalDSM: verifyFileSSCSIL (File Scope)), limit
                % what we transfer when the Model block is used for top-model
                % SIL/PIL
                initDataStoresToTransferInterfaces@rtw.pil.SILPILWrapperUtils(this);
            else
                isActiveVariant = true(1,length(allInterfaces));
                for i=1:length(allInterfaces)
                    % Only include DSM's for active variants
                    interfaceVariantInfo = allInterfaces(i).VariantInfo;
                    isActiveVariant(i) = isempty(interfaceVariantInfo) || this.CodeInfoUtils.isActiveVariant(interfaceVariantInfo);
                end
                this.DataStoresToTransferInterfaces = allInterfaces(isActiveVariant);
            end
        end

        function validateUnsupportedDataStoreReferences(this)
            % data store references within Model block SIL/PIL components 
            % need validation   
            %
            % TopTester: matlab/test/toolbox/rtw/targets/ert/datastorage/coderdatagroups/scoped_dsm/tSILDataStoreReference.m
            %           
            isTopModelXILSim = this.getSILPILInterface.isTopModelXILSim;
            if isTopModelXILSim
                % data store references should be fully resolved in the
                % top-model, with no need for synchronization / transfer 
                % between host and target
                isStandalone = false;
                assert(isempty(this.CodeInfoUtils.getDataStoreReferences(isStandalone)), ...
                    'Should be no unresolved data store references for top-model XIL');
                return;
             end 
            % get the data store references corresponding to the code
            % interface
            isStandalone = ~this.isModelReferenceTarget;           
            dataStoreReferences = this.CodeInfoUtils.getDataStoreReferences(isStandalone);
            assert(isempty(this.CodeInfoUtils.getDataStoreReferences(~isStandalone)), ...
                    'StandaloneReference & Reference should be mutually exclusive, just like the code interface!');            
            for dataStoreReference = dataStoreReferences
                referenceDataStore = this.CodeInfoUtils.getDataInterfaceString(dataStoreReference, 'datastore');
                this.reportUnsupportedDataStoreReference(this.isModelReferenceTarget, referenceDataStore);                                    
            end
        end

        function reportUnsupportedDataStoreReference(this, isModelReferenceTarget, referenceDataStore)            
            % default implementation, can be overridden by subclasses
            if isModelReferenceTarget
                MSLException('PIL:pil:DataStoreReferenceModelRefCode', ...
                    this.getComponentForDiagnostics, referenceDataStore, ...
                    this.getSILPILInterface.getBlockPath{end}).throw();                
            else
                MSLDiagnostic('PIL:pil:DataStoreReferenceTopModelCode', ...
                    this.getComponentForDiagnostics, referenceDataStore, ...
                    this.getSILPILInterface.getBlockPath{end}).reportAsWarning();
            end
        end
    end          
end
