classdef(Hidden=true) AtomicSubsystemManager < rtw.pil.SubsystemManager
    % ATOMICSUBSYSTEMMANAGER implements the Atomic Subsystem workflow
    
    % Copyright 2020-2023 The MathWorks, Inc.

    properties(Access = private)
        ContextIsComponent (1,1) logical;
        OriginalLockState (1,1) logical = true;
        OriginalDirtyFlag (1,1) matlab.lang.OnOffSwitchState = ...
            matlab.lang.OnOffSwitchState.off;
        OriginalAutosarMappingName char;
        ParentIsAutosar (1,1) logical = false;
        ParentIsERT (1,1) logical = true;
    end
    
    methods
        function this = AtomicSubsystemManager(harnessName)
            this = this@rtw.pil.SubsystemManager(harnessName);
        end 
    end
    
    methods (Static, Access=private)
        function errMsg = compatibilityCheckInterface(subsysInterAdaptor, subsystemFullPath, isAutosar)
            % Check on the interface for atomic and export function subsystems.
            arguments
                subsysInterAdaptor (1,1) rtw.connectivity.ISubsysAdaptor
                subsystemFullPath (1,:) char
                isAutosar (1,1) logical
            end

            errMsg = [];

            if ~coder.connectivity.XILSubsystemUtils.hasEntryPoint(subsysInterAdaptor)
                errMsg = MSLException('PIL:pil_subsystem:NoEntryPoint', subsystemFullPath);
                return;
            elseif ~coder.connectivity.XILSubsystemUtils.hasActiveEntryPoint(subsysInterAdaptor)
                errMsg = MSLException('PIL:pil_subsystem:NoActiveEntryPoint', subsystemFullPath);
                return;
            end

            % Check for existence of unsupported Simulink.ImageType at interface
            % until they are supported g2445333
            allTypes = subsysInterAdaptor.Types;
            if any(arrayfun(@(x) rtw.connectivity.CodeInfoTypeUtils.isImage(x), allTypes))
                errMsg = MSLException("PIL:pil_subsystem:UnsupportedImageType",...
                    subsystemFullPath);
                return;
            end

            % Autosar-specific interface validation, ensure no system
            % constant IO ports
            if isAutosar

                inPorts = [subsysInterAdaptor.Inports];
                outPorts = [subsysInterAdaptor.Outports];

                systemConstantPorts = [ ...
                    rtw.pil.AtomicSubsystemManager.stringIdsForSysConstantIO(inPorts, 'Inport'), ...
                    rtw.pil.AtomicSubsystemManager.stringIdsForSysConstantIO(outPorts, 'Outport')];

                if ~isempty(systemConstantPorts)
                    errMsg = MSLException(message('PIL:pil_subsystem:UnsupportedSystemConstant',...
                        subsystemFullPath, strjoin(systemConstantPorts, '\n')));  
                    return;
                end
            end

            % Look at the subsystem interface to determine if an inport or 
            % outport is a virtual bus
            if rtw.pil.AtomicSubsystemManager.hasVirtualBusesOnInterface(subsysInterAdaptor)
                errMsg = MSLException('PIL:pil_subsystem:UnsupportedVirtualBuses', ...
                    subsystemFullPath);
                return;
            end

            % Look at the subsystem interface to determine if an inport or 
            % outport is a message
            if rtw.pil.AtomicSubsystemManager.hasMessagesOnInterface(subsysInterAdaptor)
                errMsg = MSLException('PIL:pil_subsystem:UnsupportedMessages', ...
                    subsystemFullPath);
                return;
            end

            ssTimingInterfaces = subsysInterAdaptor.TimingProperties;
            if all(arrayfun(@(ti)rtw.connectivity.CodeInfoUtils.isModelWideEvent(ti), ssTimingInterfaces))
                errMsg = MSLException('PIL:pil_subsystem:UnsupportedIRT', ...
                    subsystemFullPath);
                return;
            end

        end

        function errMsg = compatibilityCheckRunnable(cDesc, subsystemSID, subsystemFullPath)
            % Additional compatibility checks for runnables

            bhm = cDesc.getBlockHierarchyMap();
            subsys = bhm.getBlocksBySID(subsystemSID);
            assert(numel(subsys) == 1, 'SIL runnable: found multiple blocks with subsystem''s SID');

            errMsg = [];

            rootInport = rtw.connectivity.ExportFcnAdaptor.getFcnCallRootInport( ...
                subsys, cDesc.getMF0Model());
            if isempty(rootInport)
                errMsg = MSLException("PIL:pil_subsystem:UnsupportedNonRootInport", ...
                    subsystemFullPath);
                return;
            end
        end
    end

    methods (Static)
        function [errMsgs, warnMsgs] = compatibilityCheck(parentModelName, subsystemFullPath)
            
            arguments
                parentModelName char
                subsystemFullPath char
            end
            
            errMsgs = cell(1,0);
            warnMsgs = cell(1,0);
            
            % evaluate the parent model parameters to make sure subsequent
            % checks use up-to-date values even if the model has not yet 
            % been compiled
            parentModelCS = getActiveConfigSet(parentModelName);
            parentModelCS.evalParams();
            
            % parent model validation
            isCPPClass = strcmp(get_param(parentModelName, 'TargetLang'), 'C++') && ...
                    strcmp(get_param(parentModelName, 'CodeInterfacePackaging'), 'C++ class');
            if isCPPClass
                % Subsystem code in C++ class generates private methods
                errMsgs{end+1} = MSLException(message('PIL:pil_subsystem:UnsupportedCppClass'));
                return;
            end
            
            if strcmp(get_param(parentModelName, 'AutosarCompliant'), 'on')
                % assert for Adaptive because already guarded by C++ check
                % above
                assert(~isCPPClass, 'AUTOSAR Adaptive not supported for SIL/PIL atomic subsystem simulation');
                isMappedTosubComp = Simulink.CodeMapping.isMappedToAutosarSubComponent(parentModelName);
                if isMappedTosubComp
                    errMsgs{end+1} = MSLException(message('PIL:pil_subsystem:UnsupportedAUTOSARSubcomponent'));
                    return;
                end
            end

            % subsystem type validation
            ssObj = Simulink.SubsystemType(subsystemFullPath);
            if ssObj.isVariantSubsystem()
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedVariantSubsystem', ...
                    subsystemFullPath);
                return;
            end

            allowedFcnCallSubsys = coder.internal.connectivity.featureOn('ATSFunctionCall') ...
                && ssObj.isFunctionCallSubsystem() ...
                && ~ssObj.isSimulinkFunction();

            if ~(ssObj.isAtomicSubsystem() || ...
                    ssObj.isEnabledSubsystem() || ...
                    ssObj.isEnabledAndTriggeredSubsystem() || ...
                    ssObj.isTriggeredSubsystem() || ...
                    ssObj.isForIteratorSubsystem() || ...
                    ssObj.isWhileIteratorSubsystem() || ...
                    ssObj.isResettableSubsystem() || ...
                    allowedFcnCallSubsys)
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedSubsystemType', ...
                    subsystemFullPath);
                return;
            end

            % subsystem code generation settings validation:
            % allow atomic subsystems
            ssFunctionPackaging = get_param(subsystemFullPath, 'RTWSystemCode');
            isAtomicSubsystem = ismember(ssFunctionPackaging, {'Nonreusable function', 'Reusable function'});
            % and top-level subsystems in export function model
            if isequal(get_param(parentModelName, 'AutosarCompliant'), 'on')
                allowExportFunction = coder.internal.connectivity.featureOn('AUTOSARExportFuncSubsysSIL');
            else
                allowExportFunction = coder.internal.connectivity.featureOn('ERTExportFuncSubsysSIL');
            end

            isTopLevelExportFunction = isequal(get_param(subsystemFullPath, 'Parent'), parentModelName) && ...
                isequal(get_param(parentModelName, 'SetExecutionDomain'), 'on') && ...
                isequal(get_param(parentModelName, 'ExecutionDomainType'), 'ExportFunction') && ...
                allowExportFunction;
            if ~(isAtomicSubsystem || isTopLevelExportFunction)
                errMsgs{end+1} = MSLException(message('PIL:pil_subsystem:SubsystemMustGenerateFunction', ...
                    subsystemFullPath, parentModelName));
                return;
            end

            % check subsystem library link, a linked and reusable
            % subsystem generates shared utils
            if strcmp(ssFunctionPackaging, 'Reusable function') && ...
                    ~strcmp(get_param(subsystemFullPath, 'LinkStatus'), 'none')
                errMsgs{end+1} = MSLException(message('PIL:pil_subsystem:InvalidRLSWorkflow', ...
                    subsystemFullPath));
                return;
            end
            
            % subsystem contents validation
            % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
            % instead use the post-compile filter activeVariants() - g2596051
            modelBlocks = find_system(subsystemFullPath,'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices, 'FollowLinks', 'on', 'FirstResultOnly', 'on', 'BlockType', 'ModelReference' ); % look only inside active choice of VSS
            if ~isempty(modelBlocks)
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedModelBlocks', ...
                    subsystemFullPath);
                return;
            end
            
            % error if subsystem contains bus element ports at the
            % interface
            % TODO: support bus element ports. See g2355609.
            bepBlocks = find_system(subsystemFullPath,'FirstResultOnly','on','SearchDepth',1,'Type','block','IsBusElementPort','on');
            if ~isempty(bepBlocks)
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedBusElementPorts',...
                    subsystemFullPath);
                return;
            end

            if ~coder.internal.connectivity.featureOn('DSMForAtomicSubsystem')
                % error if there is a data store read / write block in the
                % system until they are supported in g2357020
                % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
                % instead use the post-compile filter activeVariants() - g2596051
                dataStoreRWBlocks = find_system(subsystemFullPath,'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices, 'FollowLinks', 'on', 'FirstResultOnly', 'on', 'regexp', 'on', 'BlockType', 'DataStoreRead|DataStoreWrite' ); % look only inside active choice of VSS
                if ~isempty(dataStoreRWBlocks)
                    errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedDataStoreBlocks', ...
                        subsystemFullPath);
                    return;
                end
            end
            
            if rtw.pil.AtomicSubsystemManager.hasUnsupportedMaskParameters(subsystemFullPath)
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedMaskParameters', ...
                    subsystemFullPath);
                return;
            end
            
            if rtw.pil.AtomicSubsystemManager.hasUnsupportedParent(subsystemFullPath)
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedParent', ...
                    subsystemFullPath);
                return;
            end
 
            % check code is available
            buildDirStruct = RTW.getBuildDir(parentModelName);
            isTopModel = isfile( ...
                rtw.pil.AtomicSubsystemManager.getCodeDescriptorFullPath(buildDirStruct.BuildDirectory));
            mdlRefBuildDir = fullfile(buildDirStruct.CodeGenFolder, buildDirStruct.ModelRefRelativeBuildDir);
            isModelRef = isfile( ...
                rtw.pil.AtomicSubsystemManager.getCodeDescriptorFullPath(mdlRefBuildDir));
            if isTopModel && isModelRef
                errMsgs{end+1} = MSLException(message('PIL:pil_subsystem:CannotSelectGeneratedCode', ...
                    subsystemFullPath, buildDirStruct.BuildDirectory, mdlRefBuildDir));
                return;
            elseif (~isTopModel && ~isModelRef)
                errMsgs{end+1} = MSLException(message('PIL:pil_subsystem:GeneratedCodeMissingOrIncomplete', ... 
                    subsystemFullPath, buildDirStruct.BuildDirectory, mdlRefBuildDir, parentModelName));
                return;
            end
            
            % up-to-date validation
            [upToDateErr, codegenCS] = rtw.pil.AtomicSubsystemManager.upToDateValidation( ...
                isTopModel, parentModelName, buildDirStruct.BuildDirectory, mdlRefBuildDir);
            if ~isempty(upToDateErr)
                errMsgs{end+1} = upToDateErr;
                return;
            end
            
            % Only Task level profiling is supported
            if ~strcmp(codegenCS.get_param('CodeProfilingInstrumentation'), 'off')
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedCodeProfilingInstrumentation', parentModelName);
                return;
            end

            % Subsystems scheduled as partitions are not supported
            if ~strcmp(get_param(subsystemFullPath, 'ScheduleAs'), 'Sample time')
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedPartitions', subsystemFullPath);
                return;
            end

            % Subsystem Interface validation
            codeDesc = coder.getCodeDescriptor( ...
                rtw.pil.AtomicSubsystemManager.getCodeDescriptorFullPath( ...
                rtw.pil.AtomicSubsystemManager.getCodeDir(isTopModel, ...
                buildDirStruct.BuildDirectory, mdlRefBuildDir)));
            sutSID = [parentModelName ':' get_param(subsystemFullPath, 'SID')];

            compInterface  = codeDesc.getFullComponentInterface();

            unverifiableSIDs = compInterface.UnverifiableSubsystemSIDs.toArray();
            if any(arrayfun(@(x) strcmp(x, sutSID), unverifiableSIDs))
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnverifiableSubsystem', subsystemFullPath);
                return;
            end

            % Check subsystem code is not static
            if isTopModel && ...
                    strcmp(get_param(parentModelName, 'IsERTTarget'), 'on') && ...
                    ~strcmp(get_param(parentModelName, 'ERTFilePackagingFormat'), 'Modular') && ...
                    strcmp(get_param(parentModelName, 'PreserveStaticInFcnDecls'), 'on')
                % Compact ERTFilePackagingFormat generates static subsystem
                % functions that cannot be accessed by the xil wrappers
                % outside of the model source/header translation unit
                errMsgs{end+1} = MSLException(message('PIL:pil_subsystem:InvalidFilePackaging', ...
                    subsystemFullPath, parentModelName));
                return;
            end

            % late subsystem contents validation
            if rtw.pil.AtomicSubsystemManager.containsSLFunctionsAndCallers( ...
                    subsystemFullPath, compInterface)
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedFunctionAndCallerBlocks', ...
                    subsystemFullPath);
                return;
            end

            % Checks that are specific to the subsystem type
            if isAtomicSubsystem
                ssInterface = coder.descriptor.SubsystemInterface.findSubsystemInterfaceBySubsystemSID( ...
                    codeDesc.getMF0Model(), sutSID);
                if isempty(ssInterface)
                    errMsgs{end + 1} = ...
                        MSLException('PIL:pil_subsystem:NoSubsystemInterface', subsystemFullPath);
                    return;
                end
            else
                oErr = rtw.pil.AtomicSubsystemManager.compatibilityCheckRunnable( ...
                    codeDesc, sutSID, subsystemFullPath);
                if ~isempty(oErr)
                    errMsgs{end+1} = oErr;
                    return
                end
            end

            subsysAdaptor = coder.connectivity.SubsystemCodeDescriptorUtils.getInterfaceForSubsystem( ...
                sutSID, codeDesc);
            isAutosar = strcmp(get_param(parentModelName, 'AutosarCompliant'), 'on');
            oErr = rtw.pil.AtomicSubsystemManager.compatibilityCheckInterface( ...
                subsysAdaptor, subsystemFullPath, isAutosar);

            if ~isempty(oErr)
                errMsgs{end+1} = oErr;
                return
            end

            % Check subsystem functions Type and Code Type for prototype arguments
            % and actual arguments match respectively.
            functionInterfaces = ["InitializeFunctions", "OutputFunctions", ...
                "UpdateFunctions", "TerminateFunctions", "SystemResetFunction", ...
                "SystemInitializeFunction", "InitConditionsFunction", ...
                "EnableFunction", "DisableFunction", "ConstructorFunction", ...
                "DerivativeFunction"];
            
            for i = 1:length(functionInterfaces)
                subsysFuncInterfaces = subsysAdaptor.(functionInterfaces(i));
                for j = 1:length(subsysFuncInterfaces)
                    funcInterface = subsysFuncInterfaces(j);
                    funcProtArgs = funcInterface.Prototype.Arguments;
                    funcActArgs = funcInterface.ActualArgs;
                    for k = 1:length(funcActArgs)
                        actArgType = funcActArgs(k).Implementation.CodeType;
                        protArgType = funcProtArgs(k).Type;
                        % Check if actArgType isn't empty. This can
                        % occur with RTM objects
                        if ~isempty(actArgType)
                            isEqual = rtw.connectivity.CodeInfoTypeUtils.compareTypes(actArgType, ...
                                protArgType);
                            if ~isEqual 
                                errMsgs{end+1} = MSLException('PIL:pil_subsystem:MismatchSubsystemArguments', ...
                                    funcProtArgs(k).Name, ...
                                    funcInterface.Prototype.Name);
                                return
                            end
                        end
                    end
                end
            end
        end
        
                
        function diffString = identifyParameterDifferences(relevantDiffs, oldConfigSet, newConfigSet)
            
            % Create a string identifying parameter changes between two
            % config sets
            diffString = '';
            isPrintable = @(x) isnumeric(x) || isstring(x) || ischar(x);
            
            for i = 1:length(relevantDiffs)
                param = relevantDiffs{i};
                
                newParamValue = newConfigSet.get_param(param);
                oldParamValue = oldConfigSet.get_param(param);
                
                %print the parameter that doesn't match
                diffString = sprintf('%s\n%s', diffString, param);
                
                if isPrintable(newParamValue)
                    %print the stored values from each config
                    diffString = sprintf('%s: %s ==> %s', diffString, ...
                        num2str(oldParamValue), num2str(newParamValue));
                    
                elseif iscell(newParamValue)
                    if isPrintable(newParamValue{1})
                        %print the first stored values in the cell
                        %array for the param in each config
                        diffString = sprintf('%s: %s ==> %s', diffString, ...
                            num2str(oldParamValue{1}), num2str(newParamValue{1}));
                    end
                end
            end
        end
        
        function fileName = getXILCompInfoFileName(compileInfoName)
            fileName = ['xil_' compileInfoName];
        end
    end

    methods (Access=protected)
        
       function [errMsgs, warnMsgs] = validation(this, runCompatibilityCheck)

            if runCompatibilityCheck
                [errMsgs, warnMsgs] = this.compatibilityCheck(this.HarnessInfo.model, this.SubsystemFullPath);
            else
                errMsgs = cell(1,0);
                warnMsgs = cell(1,0);
            end

            % harness related validation
            harnessName = this.HarnessInfo.name;
            covSettings = get_param(harnessName, 'CodeCoverageSettings');
            if ~strcmp(covSettings.CoverageTool, 'None')
                errMsgs{end+1} = MSLException(message('PIL:pil_subsystem:UnsupportedCodeCoverageTool', ...
                    harnessName));
            end
            
            if strcmp(get_param(harnessName, 'CodeStackProfiling'), 'on')
                errMsgs{end+1} = MSLException('PIL:pil_subsystem:UnsupportedStackProfiling', harnessName);
            end
        end
        
        function [errMsgs, warnMsgs] = setup(this)
            
            errMsgs = cell(1,0);
            warnMsgs = cell(1,0);
            
            % these assignments must be made after compatibilityCheck
            buildDirStruct = RTW.getBuildDir(this.HarnessInfo.model);
            this.ContextIsComponent = isfile(fullfile(buildDirStruct.BuildDirectory, 'codedescriptor.dmr'));
            this.ParentIsAutosar = strcmp(get_param(this.HarnessInfo.model, 'AutosarCompliant'), 'on');
            this.ParentIsERT = strcmp(get_param(this.HarnessInfo.model, 'SystemTargetFile'), 'ert.tlc');
            % cache the state of the lock
            this.OriginalLockState = strcmp(get_param(this.HarnessInfo.model, 'Lock'), 'on');
            % cache and deactivate undesired mapping on the harness
            this.clearHarnessMapping();
            
            % Make and cd to temp dir
            this.HiddenModelBuildDir = tempname;
            mkdir(this.HiddenModelBuildDir);
            startingDir = cd(this.HiddenModelBuildDir);
            restoreLocation = onCleanup(@()cd(startingDir));
            % addpath to the starting dir before extracting the subsystem 
            % and invoking build, needed e.g. to find CSC that have not 
            % been added to the path
            origPath = addpath(startingDir);
            restorePath = onCleanup(@()path(origPath));
            
            % unlock the model, the build process and the update report will 'modify'
            % the model, e.g. setting status and saving data on the model
            Simulink.harness.internal.setBDLock(this.HarnessInfo.model, false);
                        
            % catch any exceptions from the following stages to make sure
            % we clean-up correctly before reporting error to callsite
            try
                this.createHiddenModel(buildDirStruct, startingDir);
                this.generateHiddenModelBinfoAndCompileInfo(buildDirStruct);
            catch ME
                this.workflowTeardown();
                errMsgs{end+1} = ME;
            end
            this.clearDirtyFlag();
        end
        
        function teardown(this)
            % restore original state
            if bdIsLoaded(this.HarnessInfo.name)
                this.restoreHarnessMapping();
            end
            if bdIsLoaded(this.HarnessInfo.model)
                this.restoreDirtyFlag();
                Simulink.harness.internal.setBDLock(this.HarnessInfo.model, this.OriginalLockState);
            end
        end
        
        function params = getWorkflowSpecificParamValuePairs(this)
            if this.ContextIsComponent
                value = 'on';
            else
                value = 'off';
            end
            params = { ...
                'XILSubsystemWorkflow', 'Atomic', ...
                'XILContextIsComponent', value, ...
                'XILAtomicFolderName', this.HiddenModelName};
        end
    end
    
    methods (Access = private)
        function createHiddenModel(this, buildDirStruct, startingDir)            
            % use the CUT (subsystem in the harness) for extraction as the 
            % context is simpler than the parent model
            [extractedMdlName, error_occ] = this.extractSubsystem(buildDirStruct);
            if error_occ
                exception = MSLException( ...
                    'PIL:pil_subsystem:UnsupportedSubsystem', this.SubsystemUTFullName);
                exception.throw();
            end
            
            % we extracted the subsystem so we need to assign the 
            % HiddenModelName property to ensure clean up works correctly
            this.HiddenModelName = this.getUniqueSubsystemName();
            
            % attach a copy of the parent's configset to the hidden model
            % to reflect changes even if the harness is not updated (see
            % g2323714) and to avoid changing the actual reference
            csCopy = this.getCopyOfParentModelConfigSet();
            this.patchConfigSet(csCopy);
            attachConfigSet(extractedMdlName, csCopy);
            setActiveConfigSet(extractedMdlName, csCopy.Name);
            
            this.removeGetSetLatchBlocks(extractedMdlName);
            this.removeHiddenSubsystemCodeGenSettings(extractedMdlName);
            this.updateCustomIncludePaths(extractedMdlName, startingDir);

            % avoid errors or warnings about parameters changed in the engine when compiling the
            % hidden model (g2946960). If appropriate the harness will warn or error.
            set_param(extractedMdlName, 'SolverPrmCheckMsg', 'none');

            % save the extracted model with a unique name within the
            % parent model, this will be the name of the Sfunction and the
            % name of the sub-folder
            save_system(extractedMdlName, fullfile(this.HiddenModelBuildDir, this.HiddenModelName));
            
            % error out if the extracted model cannot be compiled
            this.doCompiledModelChecks(buildDirStruct);
            if matlab.internal.feature("CopyCodeMappingsForTestHarness") == 0         
                this.propagateSDPModelMapping();
                this.propagateParameterMapping();
            end
            this.propagateTunableVarsSettings();

            if strcmp(...
                    get_param(this.HarnessInfo.name, 'FrameProcessingCompatibilityMsg'), 'error')
                % this parameter must match in the hierarchy, the
                % extraction does not propagate the value from the model
                % leading to error
                % 'Simulink:modelReference:InconsistentMdlrefFrameDiagnosticsSettings'
                set_param(this.HiddenModelName, 'FrameProcessingCompatibilityMsg', 'error');
            end
        end
        
        function generateHiddenModelBinfoAndCompileInfo(this, parentBuildDirStruct)
            if this.ContextIsComponent
                compileInfoName = coder.connectivity.ComponentInterfaceArtifacts.CompileInfoTop;
                bInfoName = coder.connectivity.ComponentInterfaceArtifacts.BinfoTop;
                target = 'StandaloneCoderTarget';
            else
                compileInfoName = coder.connectivity.ComponentInterfaceArtifacts.CompileInfoRef;
                bInfoName = coder.connectivity.ComponentInterfaceArtifacts.BinfoRef;
                target = 'ModelReferenceCoderTargetOnly';
            end

            % generate code for the temp model
            defaultCompInfo = coder.internal.DefaultCompInfo.createDefaultCompInfo;
            isERTSFunction = false;
            modelCompInfo = coder.internal.ModelCompInfo.createModelCompInfo...
                (this.HiddenModelName, defaultCompInfo.DefaultMexCompInfo, isERTSFunction);
            callSlBuild = @()slprivate('slbuild_private', this.HiddenModelName , target, ...
                'SlbModelCompInfo', modelCompInfo, ...
                'SlbDefaultCompInfo', defaultCompInfo, ...
                'generateCodeOnly', true, ...
                'IsXILSubsystemHiddenModelBuild', true); %#ok<NASGU>
            
            % Get the current configuration
            originalCfg = Simulink.fileGenControl('getInternalConfig');
            restoreCfg = onCleanup(@()Simulink.fileGenControl('setConfig', 'config', originalCfg));
            % redirect code gen to temp dir
            Simulink.fileGenControl('set', ...
                'CacheFolder', this.HiddenModelBuildDir, ...
                'CodeGenFolder', this.HiddenModelBuildDir);
            
            rtw.pil.AtomicSubsystemUtils.markUpTempModel(this.HiddenModelName);
            set_param(this.HiddenModelName, ...
                'GenerateReport', 'off', ...
                'LaunchReport', 'off', ...
                'SILDebugging', 'off', ...
                'CreateSILPILBlock', 'none', ...
                'CodeProfilingInstrumentation', 'off', ...
                'ModelReferenceSymbolNameMessage', 'none', ...
                'Dirty', 'off');
            save_system(fullfile(this.HiddenModelBuildDir, this.HiddenModelName ));
            
            [output, buildResults] = evalc('callSlBuild()'); %#ok<ASGLU>

            % copy binfo.mat and compileInfo.xml to original folder into
            % slprj/ert/hiddenMdl/tmwinternal/* the files need to be there for modelRef
            hiddenMdlDirStruct = RTW.getBuildDir(this.HiddenModelName);
            tmwDestinationFolder = fullfile(parentBuildDirStruct.CodeGenFolder, ...
                hiddenMdlDirStruct.ModelRefRelativeBuildDir, 'tmwinternal');
            if isfolder(tmwDestinationFolder)
                % remove the existing folder to avoid conflict, could be 
                % improved by using an up-to-date mechanisms
                rmdir(tmwDestinationFolder, 's');
            end
            mkdir(tmwDestinationFolder);
            
            dstTempMldCompileInfo = fullfile(tmwDestinationFolder, compileInfoName);
            dstTempMldBInfo = fullfile(tmwDestinationFolder, bInfoName);
            tmwSourceFolder = fullfile(hiddenMdlDirStruct.CodeGenFolder, ...
                hiddenMdlDirStruct.ModelRefRelativeBuildDir, 'tmwinternal');
            copyfile(fullfile(tmwSourceFolder, compileInfoName), dstTempMldCompileInfo, 'f');
            copyfile(fullfile(tmwSourceFolder, bInfoName), dstTempMldBInfo, 'f');
            
            % save a copy of the compile info file without the uuid strings
            % to have a stable file that won't change if there are no
            % changes in the hidden model
            fileContents = fileread(dstTempMldCompileInfo);
            editedFileContents = regexprep(fileContents, 'uuid=\&quot;.{36}\&quot;|uuid=".{36}"', '');
            % also remove <id>hidden tags
            editedFileContents = regexprep(editedFileContents, '<id>hidden:\d*.\d*</id>', '');
            % In the future, we should check for a better way to understand
            % if the model / code interface has changed, rather than 
            % processing the whole content of the serialized file.
            fid = fopen(fullfile(tmwDestinationFolder, ...
                rtw.pil.AtomicSubsystemManager.getXILCompInfoFileName(compileInfoName)), 'w');
            fprintf(fid, '%s', editedFileContents);
            fclose(fid);
            
            % patch the hidden model binfo.mat with the required 
            % information from the parent binfo.mat
            hiddenMdlInfoStruct = load(dstTempMldBInfo);
            parentBInfo = fullfile(parentBuildDirStruct.CodeGenFolder, ...
                parentBuildDirStruct.ModelRefRelativeBuildDir, 'tmwinternal', bInfoName);
            parentMdlInfoStruct = load(parentBInfo);
            if this.ParentIsAutosar 
                if this.ContextIsComponent
                    hiddenMdlInfoStruct.infoStruct.GeneralDataFromTLC = parentMdlInfoStruct.infoStruct.GeneralDataFromTLC;
                    % get client operation names
                    hiddenMdlInfoStruct.infoStruct.AUTOSARClientBlockOperationNames = ...
                        parentMdlInfoStruct.infoStruct.AUTOSARClientBlockOperationNames;
                end
                % push the parent model configset into infostruct
                hiddenMdlInfoStruct.infoStructConfigSet = this.getCopyOfParentModelConfigSet();
            end
            % get info on models referenced by the parent model
            hiddenMdlInfoStruct.infoStruct.modelRefsAll = parentMdlInfoStruct.infoStruct.modelRefsAll;
            hiddenMdlInfoStruct.infoStruct.protectedModelRefs = parentMdlInfoStruct.infoStruct.protectedModelRefs;
            hiddenMdlInfoStruct.infoStruct.modelRefsBuildDirsAll = parentMdlInfoStruct.infoStruct.modelRefsBuildDirsAll;
            hiddenMdlInfoStruct.infoStruct.protectedModelRefsBuildDirsAll = parentMdlInfoStruct.infoStruct.protectedModelRefsBuildDirsAll;
            save(dstTempMldBInfo, '-struct', 'hiddenMdlInfoStruct');
        end
        
        function updateCustomIncludePaths(this, extractedModel, startingDir)
            % Relative custom include paths must be relative to the folder 
            % containing the model file.
            % To avoid resolving relative paths to the location of the 
            % hidden model, which is saved in a temp folder, update all 
            % relative custom include paths to absolute paths using the
            % location of the original model file as anchor.
            
            customIncludeParams = ...
                rtw.pil.AtomicSubsystemManager.getCustomIncludeParamsToUpdate(extractedModel);
            for idx = 1:length(customIncludeParams)
                customIncludeParamName = customIncludeParams{idx};
                origCustomPaths = get_param(extractedModel, customIncludeParamName);
                if ~isempty(origCustomPaths)
                    % transform relative paths to absolute paths               
                    result = rtwprivate('rtw_resolve_custom_code', this.HarnessInfo.model, ...
                        '', startingDir, '', origCustomPaths, '', '');
                    % remove empty paths and wrap the remaining ones in "" as
                    % they might contain spaces
                    newCustomPaths = rmmissing(result.parsedIncludePaths);
                    newCustomPaths = cellfun(@(x) sprintf('"%s"', x), newCustomPaths, 'UniformOutput', false);
                    % recreate the space separated char array
                    newCustomPaths = strjoin(newCustomPaths, ' ');
                    set_param(extractedModel, customIncludeParamName, newCustomPaths);
                end
            end
        end
        
        function propagateParameterMapping(this)
            % Propagate parameter mapping from the original model to the
            % hidden model.
            % This is needed to support Instance Specific parameters
            % because without mapping they would behave like regular model
            % arguments.
            
            origParams = [];
            try
                % the get api throws 'coderdictionary:api:NoCodeMapping'
                % when no mapping is available
                origModelMapping = coder.mapping.api.get(this.HarnessInfo.model);
                origParams = origModelMapping.find('ModelParameters');
                origParamArgs = origModelMapping.find('ModelParameterArguments');
                origParams = [origParams origParamArgs];
            catch
            end
            
            if ~isempty(origParams)
                % if the original model has mapped parameters, create a new
                % mapping in the hidden model and setup its parameters as
                % the original ones
                % Copy over coder dictionary first to ensure hidden model
                % has any custom storage classes etc.
                if coder.dictionary.exist(this.HarnessInfo.model)
                    coder.dictionary.copy(this.HarnessInfo.model, this.HiddenModelName);
                end
                hiddenModelMapping = coder.mapping.utils.create(this.HiddenModelName);
                hiddenModelParams = hiddenModelMapping.find('ModelParameters');
                hiddenModelParamArgs = hiddenModelMapping.find('ModelParameterArguments');
                hiddenModelParams = [hiddenModelParams hiddenModelParamArgs];
                for pIdx = 1:length(hiddenModelParams)
                    origStorageClass = origModelMapping.getModelParameter(hiddenModelParams(pIdx), 'StorageClass');
                    hiddenModelMapping.setModelParameter(hiddenModelParams(pIdx), 'StorageClass', origStorageClass)
                end
            end
        end

        function propagateSDPModelMapping(this)
            % Minimal mapping propagation to avoid 
            % RTW:buildProcess:NoMappingForFunctionPlatform.
            % This should be reviewed when SDP is supported for ATS, do we 
            % need any mapping at all in the hidden model? I.e. does 
            % mapping affect the generated artifacts for the hidden model?
            if strcmp(coder.dictionary.internal.getPlatformType(this.HarnessInfo.model), 'FunctionPlatform')
                origModelMapping = Simulink.CodeMapping.getCurrentMapping(this.HarnessInfo.model);
                coder.mapping.utils.create(this.HiddenModelName);
                hiddenModelMapping = Simulink.CodeMapping.getCurrentMapping(this.HiddenModelName);
                hiddenModelMapping.DeploymentType = origModelMapping.DeploymentType;
            end
        end
        
        function propagateTunableVarsSettings(this)
            % Configure the storage classes for implicit tunable parameters
            % (i.e., those associated with a normal variable rather than a
            % Simulink.Parameter object).
            parameterNames = {'TunableVars', 'TunableVarsStorageClass', 'TunableVarsTypeQualifier'};
            for idx =1:length(parameterNames)
                currParam = parameterNames{idx};
                set_param(this.HiddenModelName, currParam, ...
                    get_param(this.HarnessInfo.model, currParam));
            end
        end
        
        function clearDirtyFlag(this)
            if ~this.ContextIsComponent
                % cache dirty flag and clear it if needed
                parentModel = this.HarnessInfo.model;
                this.OriginalDirtyFlag = matlab.lang.OnOffSwitchState(get_param(parentModel, 'Dirty'));
                if this.OriginalDirtyFlag
                    set_param(parentModel, 'Dirty', 'off');
                end
            end
        end
        
        function restoreDirtyFlag(this)
            if ~this.ContextIsComponent && this.OriginalDirtyFlag
                % restore dirty flag if needed
                parentModel = this.HarnessInfo.model;
                set_param(parentModel, 'Dirty', char(this.OriginalDirtyFlag));
            end
        end
        
        function uniqueName = getUniqueSubsystemName(this)
            % Get a subsystem name that is unique within the parent model.
            
            % Get the subsystem SID in the parent model
            sidInParent = get_param(this.SubsystemFullPath, 'SID');
            subsystemName = this.getEscapedSubsystemName();
            % Get a valid identifier for the subsystem name; use ReplacementStyle='hex' to avoid
            % multiple underscore in the subsystem name, which are not valid for AUTOSAR.
            validSubsystemName = matlab.lang.makeValidName(subsystemName, ReplacementStyle='hex');
            % A nested subsystem that is linked to a library can have a colon ':' in
            % the SID. We need to make sure we replace these before
            % constructing the model name.
            validSID = strrep(sidInParent, ':', '_');
            % Create the unique name and clip its length (arbitrarily 
            % limit at 35 chars, in line with toolstrip) to avoid possible
            % issues, e.g. compiling very long paths
            uniqueName = sprintf('SID_%s_%s', validSID, validSubsystemName);
            maxLength = min([length(uniqueName), 35]);
            uniqueName = uniqueName(1:maxLength);
        end
        
        function doCompiledModelChecks(this, buildDirStruct)
            % Check that the extracted model compiles correctly, use evalc
            % to prevent any output on cmd line or diagnostic viewer
            try 
                if this.ContextIsComponent
                    evalc('feval(this.HiddenModelName, [], [], [], ''compileForRTW'');');
                else
                    evalc('feval(this.HiddenModelName, [], [], [], ''compile'');');
                end
            catch ME %#ok<NASGU>
                exception = MSLException( ...
                    'PIL:pil_subsystem:UnsupportedSubsystem', this.SubsystemUTFullName);
                exception.throw();
            end
            % restore state if compile succeeded
            restoreState = onCleanup(@()feval(this.HiddenModelName, [], [], [], 'term'));

            % While the model is in compiled state check if the subsystem
            % has unsupported ports. We look for unsupported ports at this
            % stage as compiling the extracted model is potentially less 
            % expensive than compiling the parent model.
            % Note that 'action' ports are also terminated in the test
            % harnesses but, since they cannot cross non-virtual subsystem
            % boundaries, we could skip the check.
            escapedSubsystemPath = [this.HiddenModelName, '/', this.getEscapedSubsystemName()];
            portHandles = get_param(escapedSubsystemPath, 'PortHandles');
            exception = MSLException('PIL:pil_subsystem:UnsupportedIOPortType', this.SubsystemFullPath);
            hasUnsupportedPortType = false;
            outportHandles = portHandles.Outport;

            % create a outport sample time dictionary (port idx vs sample time)
            portSTDict = dictionary;
            if this.ParentIsAutosar
                % load the subsystem interface to access the outport sample
                % times at a later stage
                mdlRefBuildDir = fullfile(buildDirStruct.CodeGenFolder, buildDirStruct.ModelRefRelativeBuildDir);
                codeDesc = coder.getCodeDescriptor( ...
                    rtw.pil.AtomicSubsystemManager.getCodeDescriptorFullPath( ...
                    rtw.pil.AtomicSubsystemManager.getCodeDir(this.ContextIsComponent, ...
                    buildDirStruct.BuildDirectory, mdlRefBuildDir)));
                subsysSID = [this.HarnessInfo.model ':' get_param(this.SubsystemFullPath, 'SID')];
                mf0Model = codeDesc.getMF0Model();
                ssInterface = coder.descriptor.SubsystemInterface.findSubsystemInterfaceBySubsystemSID( ...
                    mf0Model, subsysSID);
                % get a handle to all outport blocks
                outports = find_system(escapedSubsystemPath, ...
                    'SearchDepth', 1, 'BlockType', 'Outport');
            end

            for idx = 1:length(outportHandles)
                currOutportHandle = outportHandles(idx);
                portDataType = get_param(currOutportHandle, 'CompiledPortDataType');
                if ismember(portDataType, {'fcn_call', 'action'})
                    hasUnsupportedPortType = true;
                    exception = exception.addCause(...
                        MSLException(message('PIL:pil_subsystem:UnsupportedOutportTypeCause', ...
                        get_param(currOutportHandle, 'PortNumber'), portDataType)));
                end
                if this.ParentIsAutosar
                    % The STF change from AUTOSAR to ERT might have an 
                    % impact on the sample time assigned to constant
                    % outports. E.g. a const (inf) outport in AUTOSAR might
                    % have a finite sample time because its value is
                    % updated as part of model_step. The same port in ERT
                    % might have inf sample time because its value is only
                    % updated in model_initialize.
                    % If the compiled sample time is constant (inf) and the
                    % SamplePeriod in code descriptor is not, add an entry
                    % to the dictionary.
                    portST = get_param(currOutportHandle, 'CompiledSampleTime');
                    if any(isinf(portST))
                        portSTFromCodeDesc = ssInterface.Outports(idx).Timing.SamplePeriod;
                        if portSTFromCodeDesc ~= 0
                            portSTDict(idx) = num2str(portSTFromCodeDesc);
                        end
                    end
                end
            end

            % Check for virtual buses on IO that where not caught by the
            % earlier check on subsystem interface because are not
            % implemented as TypedCollections
            [hasVirtualBusOutport, exception] = rtw.pil.AtomicSubsystemManager.hasVirtualBusInCompiledPorts( ...
                outportHandles, exception, this.SubsystemFullPath);
            hasUnsupportedPortType = hasUnsupportedPortType || hasVirtualBusOutport;

            [hasVirtualBusInport, exception] = rtw.pil.AtomicSubsystemManager.hasVirtualBusInCompiledPorts( ...
                portHandles.Inport, exception, this.SubsystemFullPath);
            hasUnsupportedPortType = hasUnsupportedPortType || hasVirtualBusInport;

            if hasUnsupportedPortType
                exception.throw();
            end

            % TODO: g2846269
            % Check that the IO Datatypes in the compiled model match those
            % specified in Code Descriptor to ensure the harness hasn't
            % been modified (g2634466)

            % exit compile
            clear restoreState;

            % If any entry has been added to the dictionary, update the
            % sample time in the associated port with sample time from the
            % subsystem interface. This makes sure there are no sample time
            % discrepancies after switching from AUTOSAR to ERT.
            % Sample times from the hidden model are serialized to
            % infoStruct and CompileInfo and control the model block
            % Sfunction.
            % TopTester: matlab/test/toolbox/rtw/targets/ert/pil/subsystem/atomic/AUTOSAR/tATS_InfSTOutports.m
            if portSTDict.numEntries > 0
                for pIdx = portSTDict.keys('cell')'
                    idxVal = pIdx{:};
                    set_param(outports{idxVal}, 'SampleTime', portSTDict(idxVal));
                end
            end
        end
        
        function [extractedMdlName, error_occ] = extractSubsystem(this, buildDirStruct)
            
            ssObj = Simulink.SubsystemType(this.SubsystemUTFullName);           
            if ssObj.isFunctionCallSubsystem()
                
                origUnifiedHarnessBackendMode = slsvTestingHook('UnifiedHarnessBackendMode', 1);
                cleanupFeatureSetting1 = onCleanup(@()slsvTestingHook('UnifiedHarnessBackendMode', origUnifiedHarnessBackendMode));
                % we have control over the extracted model name, use
                % the unique subsystem name
                extractedMdlName = this.getUniqueSubsystemName();
                
                try
                    harnessHandle = get_param(this.HarnessName, 'Handle');
                    mainSysH = get_param(this.HarnessInfo.model, 'Handle');
                    cleanupCreateForHarness = Simulink.harness.internal.EnableCreateForHarnessBD(mainSysH, harnessHandle); %#ok<NASGU>
                    error_occ = false;
                    checkoutLicense =  false;
                    wstate1 = warning('off', 'Simulink:Commands:AutosaveCreateError');
                    warningRestore1 = onCleanup(@()warning(wstate1));
                    wstate2 = warning('off', 'Simulink:Harness:CannotCreateSnapshotOfInternalHarness');
                    warningRestore2 = onCleanup(@()warning(wstate2));
                    Simulink.harness.internal.create(this.SubsystemUTFullName, ...
                        false, ... % createForLoad
                        checkoutLicense, ... % checkoutLicense
                        'Source', 'Inport', ...
                        'Name', extractedMdlName, ...
                        'AutoShapeInputs', false, ...
                        'DriveFcnCallWithTestSequence', false, ...
                        'SynchronizationMode', 'SyncOnOpen', ...
                        'SaveExternally', false, ...
                        'RebuildOnOpen', false,...
                        'PostCreateCallback', 'Simulink.harness.internal.groundOrTermAutogeneratedIO',...
                        'SLDVCompatible', true);    % TODO g2481218: Revisit the use of SLDVCompatible parameter here
                    
                    % export
                    wstate3 = warning('off', 'Simulink:Harness:ExportDeleteHarnessFromSystemModel');
                    warningRestore3 = onCleanup(@()warning(wstate3));
                    Simulink.harness.internal.export(this.SubsystemUTFullName, ...
                        extractedMdlName, checkoutLicense);
                    
                    this.postProcessFcnCallModel(extractedMdlName, buildDirStruct);
                    save_system(extractedMdlName);
                    clear cleanupCreateForHarness;
                catch ME %#ok<NASGU>
                    error_occ = true;
                    % clean up bdclose
                    bdclose(extractedMdlName);
                end
            else
                groundOrTermAutogeneratedIO = true;
                [extractedMdlHandle, portInfo, error_occ, extractSubsysExc] = ...
                    Simulink.harness.internal.extractSubsystem(this.SubsystemUTFullName, ...
                    groundOrTermAutogeneratedIO); %#ok<ASGLU>
                if error_occ
                    extractedMdlName = '';
                else
                    extractedMdlName = get_param(extractedMdlHandle, 'Name');
                end
            end
            this.replaceReferecedSubsystem(extractedMdlName);
        end

        function replaceFunctionCallGenerator(this, ...
                fcncallGenHandle, inputConversionSS, extractedMdlName, buildDirStruct)
            % Replace a function call generator block with an inport.

            dst_pos = get_param(fcncallGenHandle, 'Position');
            % delete the original block
            delete_block(fcncallGenHandle);
            % add the inport
            add_block('simulink/Sources/In1', [inputConversionSS '/Inport'],...
                'Position', dst_pos, 'MakeNameUnique', 'on');

            % add a root inport and connect it to the new
            % inport of the inport conversion ss
            ports = get_param(inputConversionSS, 'PortHandles');
            % get all root inports now so that the new inport is not
            % included in the next processing stage (inherit sample 
            % time)
            newRootInport = add_block('simulink/Sources/In1', [extractedMdlName '/Inport'],...
                'MakeNameUnique', 'on');
            set_param(newRootInport, 'OutputFunctionCall', 'on')
            src = get_param(newRootInport, 'PortHandles');
            % add line from newRootInport to the last inport
            % of the inport conversion ss
            add_line(extractedMdlName, src.Outport, ports.Inport(end));


            % use the timing properties to set the sample time
            % on the root inport connected to the periodic fcn
            % call
            mdlRefBuildDir = fullfile(buildDirStruct.CodeGenFolder, buildDirStruct.ModelRefRelativeBuildDir);
            codeDesc = coder.getCodeDescriptor( ...
                rtw.pil.AtomicSubsystemManager.getCodeDescriptorFullPath( ...
                rtw.pil.AtomicSubsystemManager.getCodeDir(this.ContextIsComponent, ...
                buildDirStruct.BuildDirectory, mdlRefBuildDir)));

            subsysSID = [this.HarnessInfo.model ':' get_param(this.SubsystemFullPath, 'SID')];
            mf0Model = codeDesc.getMF0Model();

            ssFunctionPackaging = get_param(this.SubsystemFullPath, 'RTWSystemCode');
            isAtomicSubsystem = ismember(ssFunctionPackaging, ...
                {'Nonreusable function', 'Reusable function'});

            if isAtomicSubsystem
                % For atomic subsystem find the sample time from the subsystem interface

                ssInterface = coder.descriptor.SubsystemInterface.findSubsystemInterfaceBySubsystemSID( ...
                    mf0Model, subsysSID);
                timingProperties = ssInterface.TimingProperties.toArray();
                assert(numel(timingProperties) >= 1, 'expect at least 1 timing property'); % e.g. PERIODIC and ONESHOT
                periodicRate = rtw.connectivity.CodeInfoUtils.findTimingRates(timingProperties, 'PERIODIC');
                assert(numel(periodicRate) == 1, 'expect 1 periodic rate');
                newRootInportSampleTime = num2str(periodicRate.SamplePeriod);
            else
                % For export function runnables use the sample time of the corresponding root inport

                bhm = codeDesc.getBlockHierarchyMap();
                subsys = bhm.getBlocksBySID(subsysSID);
                fcncallRootInport = ...
                    rtw.connectivity.ExportFcnAdaptor.getFcnCallRootInport(subsys, mf0Model);
                newRootInportSampleTime = get_param(fcncallRootInport.Path, 'SampleTime');
            end
            set_param(newRootInport, 'SampleTime', newRootInportSampleTime);

        end

        function removeSignalSpecification(this, sigSpec)
            % Remove the signal specification block passes, setting the corresponding attributes in
            % the inport/outport to which it is connected.

            dataType = get_param(sigSpec, 'OutDataTypeStr');
            dimensions = get_param(sigSpec, 'Dimensions');
            portConnectivity = get_param(sigSpec, 'PortConnectivity');
            assert(numel(portConnectivity) == 2, ...
                'Signal specification block should have only two ports')

            connectedBlocks = [ ...
                portConnectivity(1).SrcBlock ...
                portConnectivity(2).SrcBlock ...
                portConnectivity(1).DstBlock ...
                portConnectivity(2).DstBlock];

            for idx = 1:numel(connectedBlocks)
                block = connectedBlocks(idx);
                blockType = get_param(block, 'BlockType');
                if isequal(blockType, 'Inport') || isequal(blockType, 'Outport')
                    set_param(block, 'OutDataTypeStr', dataType);
                    set_param(block, 'PortDimensions', dimensions);
                end
            end
            this.removeBlock(sigSpec);
        end

        function postProcessFcnCallModel(this, extractedMdlName, buildDirStruct)

            % Top-level function call subsystem needs ExportFunction execution domain
            set_param(extractedMdlName, 'SetExecutionDomain', 'on');
            set_param(extractedMdlName, 'ExecutionDomainType', 'ExportFunction');

            inputConversionSS = [extractedMdlName '/Input Conversion Subsystem'];
            % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
            % instead use the post-compile filter activeVariants() - g2596051
            fcnCallGenerator = find_system(inputConversionSS, ...
                'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices,... % look only inside active choice of VSS
                'RegExp', 'on', 'Type', 'block', 'Name', 'Fcn Call Generator');
            fcnCallGeneratorHandle = get_param(fcnCallGenerator{1}, 'Handle');
            set_param(inputConversionSS, 'Permissions', 'ReadWrite');

            statesWhenEnabling = this.getFcnCallPortStatesWhenEnabling(this.SubsystemFullPath);
            subsys = [extractedMdlName '/' get_param(this.SubsystemFullPath, 'Name')];
            subsysHandle = get_param(subsys, 'handle');
            triggerPort = find_system(subsysHandle, 'SearchDepth', 1, 'BlockType', 'TriggerPort');
            set_param(triggerPort, 'StatesWhenEnabling', statesWhenEnabling);
            isAsync = strcmp(get_param(triggerPort, 'SampleTimeType'), 'triggered');

            % set output function call on the function port assuming it 
            % is the last one
            rootInports = find_system(extractedMdlName, 'SearchDepth', 1, 'BlockType', 'Inport');

            if isAsync
                % remove the function call generator
                sampleTime = get_param(fcnCallGeneratorHandle, 'sample_time');

                this.removeBlock(fcnCallGeneratorHandle);

                % set output function call on the function port assuming it 
                % is the last one
                set_param(rootInports{end}, 'OutputFunctionCall', 'on');
                set_param(rootInports{end}, 'SampleTime', sampleTime);
            else
                % replace the fcnCall gen with an inport
                this.replaceFunctionCallGenerator( ...
                    fcnCallGeneratorHandle, inputConversionSS, extractedMdlName, buildDirStruct);
            end

            for i = 1:numel(rootInports)
                if strcmp(get_param(rootInports{i}, 'OutputFunctionCall'), 'off')
                    % For function call input sample time is already set
                    set_param(rootInports{i}, 'SampleTime', '[-1, 0]');
                end
            end
            outputConversionSS = [extractedMdlName '/Output Conversion Subsystem'];
            set_param(outputConversionSS, 'Permissions', 'ReadWrite');
            sigSpecs = find_system(inputConversionSS, 'SearchDepth', 1, 'BlockType', 'SignalSpecification');
            for i = 1:length(sigSpecs)
                set_param(sigSpecs{i}, 'SampleTime', '[-1, 0]');
            end
            sigSpecs = find_system(outputConversionSS, 'SearchDepth', 1, 'BlockType', 'SignalSpecification');
            for i = 1:length(sigSpecs)
                set_param(sigSpecs{i}, 'SampleTime', '[-1, 0]');
            end
        end
        
        function removeGetSetLatchBlocks(this, extractedModel)
            % Remove the latch blocks for VectorGetSet.
            % The extracted model may contain VectorGetSet latch blocks
            % which trigger an error at code generation if their IO signal
            % does not use GetSet storage class.
            % Since it's complicated to replicate the correct mapping in
            % the extracted model (e.g. a parent root port could use GetSet
            % and its latch block could be inside the SUT) we just remove
            % the latch block.
            % A GetSet without latch block does not cause an error at
            % code generation.
            
            ssInHiddenModel = [extractedModel '/' this.getEscapedSubsystemName()];
            % find the blocks using the name of the Sfunction
            % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
            % instead use the post-compile filter activeVariants() - g2596051
            latchHandles = find_system(ssInHiddenModel,'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices, 'BlockType', 'S-Function', 'FunctionName', 'Safeguard' ); % look only inside active choice of VSS
            if ~isempty(latchHandles)
                % Create a new system to dump out 'Signal Specification'
                % blocks (pass through) and use pil_block_replace to swap 
                % them with the latch blocks. This avoids dealing with
                % connecting multiple lines.
                tempMdl = new_system();
                closeTempMdl = onCleanup(@()bdclose(tempMdl));
                tempBlockName = [get_param(tempMdl, 'Name') '/tempPassThroughBlock'];
                for idx = 1:length(latchHandles)    
                    add_block('simulink/Signal Attributes/Signal Specification', tempBlockName);
                    % use evalc to suppress pil_block_replace text output
                    replaceCmd = @()pil_block_replace(tempBlockName, ...
                        [get_param(latchHandles{idx}, 'Parent') '/' get_param(latchHandles{idx}, 'Name')], false); %#ok<NASGU>
                    evalc('replaceCmd()');
                end
                clear closeTempMdl;
            end
        end

        function removeHiddenSubsystemCodeGenSettings(this, extractedMdlName)
            % Remove any non-default code generation settings from the
            % subsystem in the hidden model to avoid possible errors, e.g.
            % due to missing CSC packages
            ssInHiddenModel = [extractedMdlName '/' this.getEscapedSubsystemName()];
            if ~strcmp(get_param(ssInHiddenModel, 'LinkStatus'), 'none')
                % if linked, break the link before changing the subsystem
                set_param(ssInHiddenModel, 'LinkStatus', 'none');
            end
            set_param(extractedMdlName, 'MemSecDataIO', 'Default');
            set_param(ssInHiddenModel, 'RTWSystemCode', 'Auto');
        end
        
        function clearHarnessMapping(this)
            % If the harness is mapped to an AUTOSAR target, cache the name
            % of the active mapping and deactivate it.
            mappingManager = get_param(this.HarnessInfo.name, 'MappingManager');
            autosarMapping = mappingManager.getActiveMappingFor('AutosarTarget');
            if ~isempty(autosarMapping)
                this.OriginalAutosarMappingName = autosarMapping.Name;
                mappingManager.deactivateMapping('AutosarTarget');
            end
        end
        
        function restoreHarnessMapping(this)
            % If we deactivated a mapping and cached its name, reactivate 
            % the mapping 
            if ~isempty(this.OriginalAutosarMappingName)
                mappingManager = get_param(this.HarnessInfo.name, 'MappingManager');
                mappingManager.activateMapping(this.OriginalAutosarMappingName, 'AutosarTarget');
            end
        end

        function csCopy = getCopyOfParentModelConfigSet(this)
            parentModelCS = getActiveConfigSet(this.HarnessInfo.model);
            if isa(parentModelCS, 'Simulink.ConfigSetRef')
                csCopy = parentModelCS.getRefConfigSet.copy();
            else
                csCopy = parentModelCS.copy();
            end
            csCopy.Name = this.HiddenModelName;
        end
    
        function patchConfigSet(this, hiddenModelCS)
            % For targets that do not use vanilla ERT setup the configset to use ERT. This avoids
            % issues with AUTOSAR (g2945529) and locked parameters in derived TLC (g3046366).
            if ~this.ParentIsERT
                origCS = hiddenModelCS.copy();
                hiddenModelCS.set_param('SystemTargetFile', 'ert.tlc');
                
                % restore any parameters that might have changed after the
                % STF change (ignore the STF itself and Boolean*Id)
                [~, diffs] = origCS.isequal(hiddenModelCS);
        		paramsToIgnore = {'SystemTargetFile', ...
                    'BooleanFalseId', 'BooleanTrueId', ...
                    'ERTHdrFileBannerTemplate', 'ERTSrcFileBannerTemplate', ...
                    'ERTDataSrcFileTemplate', 'ERTDataHdrFileTemplate', ...
                    'ERTCustomFileTemplate'};
                diffs = diffs(~ismember(diffs, paramsToIgnore));
                % turn off warnings in this section to robustly avoid noise
                % from deprecated parameters
                warnStruct = warning();
                restoreWarningState = onCleanup(@()warning(warnStruct));
                warning('off');
                for param = diffs
                    paramName = param{1};
                    if hiddenModelCS.isValidParam(paramName)
                        try
                            hiddenModelCS.set_param(paramName, origCS.get_param(paramName));
                        catch ME
                            if ~strcmp(ME.identifier, 'configset:diagnostics:CannotChangeProp')
                                % the parameter can be read-only, but there
                                % is no easy way to check
                                ME.throwAsCaller();
                            end
                        end
                    end
                end
                clear restoreWarningState;
            end
        end
    end
    
    methods (Static, Access = private)
        function [errMsg, codegenCS] = upToDateValidation(isTopModel, parentModelName, topMdlCodeGenDir, mdlRefCodeGenDir)
            % check if the parent model configset has been modified since
            % code was generated
            errMsg = cell(1,0);
            if isTopModel
                inTheLoopType = rtw.pil.InTheLoopType.ModelBlockStandalone;
                buildTypeChecksumName = 'TopModel';
                % exclude this parameter as it is changed by
                % SimulationInput>enforceReturnWorkspaceOutputsIsOn prior
                % to caching
                excludedParams = {'ReturnWorkspaceOutputs'};
            else
                inTheLoopType = rtw.pil.InTheLoopType.ModelBlock;
                buildTypeChecksumName = 'MdlRef';
                % exclude these parameters because the model ref build sets
                % a different value in the codegen configset
                excludedParams = [ ... 
                    {'GenerateSampleERTMain'}, ...
                    {'LimitDataPoints'}, ...
                    {'SaveOutput'}, ...
                    {'SaveTime'}, ...
                    {'CustomSymbolStrFcn'} , ...
                    {'CustomSymbolStrGlobalVar'} ,...
                    {'CustomSymbolStrMacro'}, ...
                    {'CustomSymbolStrType'}];
            end
            codeDir = rtw.pil.AtomicSubsystemManager.getCodeDir(isTopModel, ...
                topMdlCodeGenDir, mdlRefCodeGenDir);
            usingXrelResourcesDir = false;
            infoStruct = coder.connectivity.ComponentInterfaceArtifacts. ...
                createLocatorAndGetInfoStruct(codeDir, inTheLoopType, usingXrelResourcesDir);
            codegenCS = infoStruct.configSet;
            currentCS = getActiveConfigSet(parentModelName);
            
            [csMatch, allDiffs] = currentCS.isequal(codegenCS);
            if ~csMatch

                % generate a list of relevant params and check for any differences
                % between the configurations
                
                sharedFcnParams = codegenCS.getPropsThatAffectChecksum('SharedFcn');
                % sil/pil checksum parameters default to normal, see g1381303
                silpilParams = codegenCS.getPropsThatAffectChecksum('Normal');
                buildTypeParams = codegenCS.getPropsThatAffectChecksum(buildTypeChecksumName);
                
                relevantParams = unique([buildTypeParams; sharedFcnParams; silpilParams]);
                relevantParams = setdiff(relevantParams, excludedParams);
                relevantDiffs = allDiffs(ismember(allDiffs, relevantParams));
                
                if ~isempty(relevantDiffs)
                    % Create a error message to describe params changes
                    diffParamString = rtw.pil.AtomicSubsystemManager.identifyParameterDifferences(relevantDiffs, codegenCS, currentCS);
                    errMsg = MSLException('PIL:pil_subsystem:ConfigSetChangedAfterCodeGen', ...
                        parentModelName, diffParamString);
                end
            end
        end
        
        function codeDir = getCodeDir(isTopModel, topModelCodeDir, modelRefCodeDir)
            if isTopModel
                codeDir = topModelCodeDir;
            else
                codeDir = modelRefCodeDir;
            end
        end

        function result = containsSLFunctionsAndCallers(subsystemFullPath, componentInterface)
            
            result = false;
            if componentInterface.ServerCallPoints.Size > 0
                % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
                % instead use the post-compile filter activeVariants() - g2596051
                callerBlocks = find_system(subsystemFullPath, ...
                    'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices,... % look only inside active choice of VSS
                    'FollowLinks', 'on', 'FirstResultOnly', 'on', 'BlockType', 'FunctionCaller');
                if ~isempty(callerBlocks)
                    result = true;
                    return;
                end
            end
            
            outputFunctions = componentInterface.OutputFunctions.toArray();
            if any(arrayfun(@(x) isa(x, 'coder.descriptor.SimulinkFunctionInterface'), outputFunctions))
                % @todo update the usage of edit-time filter filterOutInactiveVariantSubsystemChoices()
                % instead use the post-compile filter activeVariants() - g2596051
                simulinkFunctionBlocks = find_system(subsystemFullPath, ...
                    'MatchFilter',@Simulink.match.internal.filterOutInactiveVariantSubsystemChoices,... % look only inside active choice of VSS
                    'FollowLinks', 'on', 'FirstResultOnly', 'on', 'SystemType', 'SimulinkFunction');
                if ~isempty(simulinkFunctionBlocks)
                    result = true;
                    return;
                end
            end
        end
        
        function codeDesc = getCodeDescriptorFullPath(codeDir)
            codeDesc = fullfile(codeDir, 'codedescriptor.dmr');
        end

        function hasUnsupportedParams = hasUnsupportedMaskParameters(subsystemFullPath)
            hasUnsupportedParams = false;
            maskObj = Simulink.Mask.get(subsystemFullPath);
            if ~isempty(maskObj) && ~isempty(maskObj.Parameters)
                hasUnsupportedParams = true;
            end
        end
        
        function hasVbus = hasVirtualBusesOnInterface(ssInterface)
            hasVbus = false;
            allPorts = [ssInterface.Inports, ssInterface.Outports];
            for idx=1:numel(allPorts)
                
                %check if is a typed collection before checking if the
                %collectionType is a virtual bus
                if isa(allPorts(idx).Implementation, 'coder.descriptor.TypedCollection')
                    
                    type = allPorts(idx).Implementation.CollectionType;
                    if type == coder.descriptor.TypedCollectionType.VIRTUAL_BUS
                        hasVbus = true;
                        break;
                    end
                end
            end
        end
        
        function unsupportedParent = hasUnsupportedParent(subsystemFullPath)
            % return true if the SUT is a child of a For Iterator or
            % While Iterator subsystem
            
            unsupportedParent = false;           
            % / in names are escaped (i.e. //) so that a single / is path
            % separator.
            % split the subsystems hierarchy, using a / that is not
            % preceded of followed by other /
            hierarchy = regexp(subsystemFullPath, '(?<!/)/(?!/)', 'split');
            
            % longer than parentModel/subsystem
            if length(hierarchy) > 2
                
                % remove the subsystem under test, last item
                hierarchy = hierarchy(1:end-1);
                % explore the hierarchy all the way up to the parent model
                for i = length(hierarchy):-1:2
                    % create the path to the current level
                    intermediateSS = strjoin(hierarchy(1:i), '/');
                    ssObj = Simulink.SubsystemType(intermediateSS);
                    if ssObj.isForIteratorSubsystem() || ssObj.isWhileIteratorSubsystem()
                        unsupportedParent = true;
                        break;
                    end
                end 
            end
        end

        function removeBlock(block)
            % Remove single-input single-output block connecting its input
            % and its output.

            lines = get_param(block, 'LineHandles');
            assert(numel(lines.Inport) == 1);
            assert(numel(lines.Outport) == 1);
            srcPort = get_param(lines.Inport, 'SrcPortHandle');
            dstPort = get_param(lines.Outport, 'DstPortHandle');
            parent = get_param(block, 'Parent');
            delete_line(lines.Inport);
            delete_line(lines.Outport);
            delete_block(block);
            add_line(parent, srcPort, dstPort);

        end

        function hasMessages = hasMessagesOnInterface(ssInterface)
            hasMessages = false;
            allPorts = [ssInterface.Inports, ssInterface.Outports];
            for idx=1:numel(allPorts)
                if isa(allPorts(idx), 'coder.descriptor.MessageDataInterface')
                    hasMessages = true;
                    break;
                end
            end
        end

        function [hasVirtualBus, exception] = hasVirtualBusInCompiledPorts(portHandles, exception, subsystemFullPath)
            % check if any of the compiled ports is a virtual bus and add
            % an exception cause if so
            hasVirtualBus = any(arrayfun(@(x) ...
                strcmp(get_param(x, 'CompiledBusType'), 'VIRTUAL_BUS'), portHandles));
            if hasVirtualBus
                exception = exception.addCause( ...
                    MSLException('PIL:pil_subsystem:UnsupportedVirtualBuses', ...
                    subsystemFullPath));
            end
        end

        function customIncludeParams = getCustomIncludeParamsToUpdate(extractedModel)
            % Always update SimUserIncludeDirs and update CustomInclude
            % only if RTWUseSimCustomCode is off
            customIncludeParams = {'SimUserIncludeDirs'};
            if strcmp(get_param(extractedModel, 'RTWUseSimCustomCode'), 'off')
                customIncludeParams = [customIncludeParams {'CustomInclude'}];
            end
        end

        function ret = getFcnCallPortStatesWhenEnabling(subSys)
            % Return the value of 'StatesWhenEnabling' that the function call port should use for a
            % fcn call subsystem. This is the value of 'StatesWhenEnabling' set in any parent
            % subsystem and different from 'Inherit'.

            ssObj = Simulink.SubsystemType(subSys);
            assert(ssObj.isFunctionCallSubsystem());

            while true

                % check if the current subsystem's fcn call port has the value set
                port = find_system(subSys, 'SearchDepth', 1, 'BlockType', 'TriggerPort');
                assert(numel(port) == 1, 'Function call subsystem with multiple trigger ports');
                statesWhenEnabling = get_param(port{1}, 'StatesWhenEnabling');
                if ~isequal(statesWhenEnabling, 'inherit')
                    ret = statesWhenEnabling;
                    return;
                end

                % look for a parent subsystem that is a fcn call subsystem
                while true
                    subSys = get_param(subSys, 'Parent');
                    ssObj = Simulink.SubsystemType(subSys);
                    if ssObj.isFunctionCallSubsystem()
                        break
                    end
                end
            end

        end

        function isSystemConstant = isImplementationSystemConstant(dataInterface)
            
            implementation = dataInterface.Implementation;
            isSystemConstant = rtw.connectivity.CodeInfoUtils.isa(implementation, 'AutosarCalibration') &&...
                isempty(implementation.Type);
        end

        function portStringArray = stringIdsForSysConstantIO(ports, portType)

            portStringArray = {};
            for idx = 1:length(ports)
                if coder.internal.connectivity.AutosarCodeDescriptorStrategy.isDataInterfaceSystemConstant(ports(idx))
                    graphicalName = get_param(ports(idx).SID, "Name");
                    parentName = get_param(ports(idx).SID, "Parent");
                    fullPath = [parentName '/' graphicalName];
                    portStringArray{end+1} = [portType ' ' graphicalName ' (''' fullPath ''')']; %#ok<AGROW>
                end
            end
        end

        function escapedName = getEscapedName(name)
            escapedName = regexprep(name, '/', '//');
        end
    end
end

% LocalWords:  VSS
