classdef (Hidden = true) AtomicSubsystemSILPILWrapperUtils < rtw.pil.SubsystemSILPILWrapperUtils
%ATOMICSUBSYSTEMSILPILWRAPPERUTILS SIL/PIL atomic subsystem wrapper utilities
%
% This is an undocumented class. Its methods and properties are likely to
% change without warning from one release to the next.

% Copyright 2020-2023 The MathWorks, Inc.

properties (Access = private)
MF0ParentModelInfo Simulink.ModelReference.internal.compileInfo.ModelRefInfoRoot ...
{mustBeScalarOrEmpty} = ...
Simulink.ModelReference.internal.compileInfo.ModelRefInfoRoot.empty() % parent Model interface data
end

methods
% constructor
function this = AtomicSubsystemSILPILWrapperUtils(silPILInterface, ...
infoStruct, ...
clientInterface,...
topModelOriginalParams, ...
lXilCompInfo)

% call super class constructor
this@rtw.pil.SubsystemSILPILWrapperUtils(silPILInterface, ...
infoStruct, ...
clientInterface, ...
topModelOriginalParams, ...
lXilCompInfo);
end

function isReadOnly = isComponentReadOnly(~)
% consider this workflow read-only
isReadOnly = true;
end

function dataStoresToTransfer = getDataStoresToTransferIndices(this, allIndices, dsmType)
% Get the indexes of data store to transfer, remove the data
% stores whose implementation is also an inport implementation.
% This is needed to avoid overwriting the values synced from
% inports with stale data store values.

keepIdx = true(1, length(allIndices));
if strcmp(dsmType, 'dsread')
ssInterface = this.getCodeInfo();
inports = ssInterface.Inports;
% for each data store, check if its implementation is also
% an inport implementation.
for dsIdx =1:length(allIndices)
dataInterface = ssInterface.DataStores(allIndices(dsIdx));
for inIdx = 1:length(inports)
if dataInterface.Implementation == inports(inIdx).Implementation
% remove this index
keepIdx(dsIdx) = false;
break;
end
end
end
end
dataStoresToTransfer = allIndices(keepIdx);
end

function isSupported = isStateLoggingSupported(~)
isSupported = coder.internal.connectivity.featureOn('ATSLogging');
end

function isSupported = isSignalLoggingSupported(~)
isSupported = coder.internal.connectivity.featureOn('ATSLogging');
end

function res = isLoggingFromTransformedModel(~)
res = true;
end

function includePrivateH = shouldIncludePrivateHeader(~)
% Always include model_private.h if available as some subsystem
% data may be declared there.
includePrivateH = true;
end

function dependencies = getWorkflowSpecificDependencies(this)
mf0FileName = this.SILPILInterface.getCompInfoFileName();
[path, name, ext] = fileparts(mf0FileName);
dependencies = {fullfile(path, ...
rtw.pil.AtomicSubsystemManager.getXILCompInfoFileName([name, ext]))};
end

function sigLogInfo = processUnloggedSignals(this, sigLogInfo)
% process unlogged signals to make their block paths point to
% the harness model rather than the tmp model

topModel = this.getSILPILInterface.getTopModelForModelBlockPIL();
harnessInfo = Simulink.harness.internal.getHarnessInfoForHarnessBD(topModel);
% newPath contains the subsystem path in the test harness
newPath = Simulink.harness.internal.getLoadedHarnessCUT(harnessInfo.model, harnessInfo.name);
% tmpPath contains the subsystem path in the hidden model
hiddenModel = get_param(topModel, 'XILModelName');
tmpPath = strrep(newPath, topModel, hiddenModel);

for i = 1:length(sigLogInfo)
origBlockPathObj = sigLogInfo(i).BlockPath;
% nested referenced models are not supported yet
assert(origBlockPathObj.getLength() == 1);
origBlockPath = origBlockPathObj.getBlock(1);
if startsWith(origBlockPath, tmpPath)
newBlockPath = strrep(origBlockPath, tmpPath, newPath);
sigLogInfo(i).BlockPath = Simulink.BlockPath(newBlockPath);
end
end
end
end

methods(Access = protected)
function newXilAppDir = appendXILApplicationDirChild(this, xilAppDir)
% append a child dir for the subsystem under the main xil folder
subfolderName = get_param( ...
this.getSILPILInterface().getTopModelForModelBlockPIL, ...
'XILAtomicFolderName');
newXilAppDir = fullfile(xilAppDir, subfolderName);
end

function allocFunction = getAllocationFunctionForComponent(this)
% Return the allocation function for the parent model
% (ComponentInterface)
componentInterface = this.getCodeInfo().getComponentInterface();
allocFunction = componentInterface.AllocationFunction;
end

function states = postProcessLoggedStates(this, states)
% post process states to:
% - delete states that aren't part of the subsystem (they
% belonged to the original model containing the atomic
% subsystem)
% - update traceability to point to the harness model
topModel = this.getSILPILInterface.getTopModelForModelBlockPIL();
harnessInfo = Simulink.harness.internal.getHarnessInfoForHarnessBD(topModel);

% newPath contains the subsystem path in the test harness
newPath = Simulink.harness.internal.getLoadedHarnessCUT(harnessInfo.model, harnessInfo.name);
% oldPath contains the subsystem path in the original model
oldPath = harnessInfo.ownerFullPath;
oldPathWithFinalMarker = [oldPath '/'];
numStates = length(states.loggedStates);
toRemove = true(1, numStates);

for i = 1:numStates
originalPath = getfullname(states.loggedStates(i).SID);
if startsWith(originalPath, oldPathWithFinalMarker)
% need to update several fields:
% SID: to highlight the block
% parentChain: to identify the state
newBlockPath = strrep(originalPath, oldPath, newPath);
newSid = Simulink.ID.getSID(newBlockPath);
states.loggedStates(i).SID = newSid;
x = strrep(states.stateInfo(i).parentChain{1}, oldPath, newPath);
states.stateInfo(i).parentChain{1} = x;
toRemove(i) = false;
end
end
states.loggedStates(toRemove) = [];
states.stateInfo(toRemove) = [];
states.sidParents(toRemove) = [];
states.parents(toRemove) = [];
end

function logData = postProcessLoggedSignals(this, logData)
% post process signal to:
% - delete signal that aren't part of the subsystem
% - update traceability to point to the harness model
% - update sigInfo traceability to point to the hidden model
topModel = this.getSILPILInterface.getTopModelForModelBlockPIL();
harnessInfo = Simulink.harness.internal.getHarnessInfoForHarnessBD(topModel);

% newPath contains the subsystem path in the test harness
newPath = Simulink.harness.internal.getLoadedHarnessCUT(harnessInfo.model, harnessInfo.name);
% oldPath contains the subsystem path in the original model
oldPath = harnessInfo.ownerFullPath;
oldPathWithFinalMarker = [oldPath '/'];
% tmpPath contains the subsystem path in the hidden model
hiddenModel = get_param(topModel, 'XILModelName');
tmpPath = strrep(newPath, topModel, hiddenModel);

numSignals = length(logData.sigLogInfo);
toRemove = true(1, numSignals);
for i = 1:numSignals
sid = extractBefore(logData.sigLogInfo(i).sid, '#');
originalPath = getfullname(sid);
if startsWith(originalPath, oldPathWithFinalMarker)
toRemove(i) = false;
tmpBlockPath = strrep(originalPath, oldPath, tmpPath);
tmpSid = Simulink.ID.getSID(tmpBlockPath);
portUrl = extractAfter(logData.sigLogInfo(i).sid, '#');
logData.sigLogInfo(i).sid = [tmpSid '#' portUrl];
% update first element in sidHier to refer the new
% subsystem
x = strrep(logData.sigLogInfo(i).sidHier{1}, oldPath, tmpPath);
logData.sigLogInfo(i).sidHier{1} = x;
newBlockPath = strrep(originalPath, oldPath, newPath);
newSid = Simulink.ID.getSID(newBlockPath);
logData.loggedSignals(i).SID = newSid;
end
end
logData.loggedSignals(toRemove) = [];
logData.sigLogInfo(toRemove) = [];
logData.sidParents(toRemove) = [];
logData.parents(toRemove) = [];
end

function cacheMF0ModelInterfaceInfoHook(this, configInterface, parser)
% parse and cache the MF0 information for the parent model
silPilInterface = this.getSILPILInterface();
inTheLoopType = silPilInterface.getInTheLoopType();
switch inTheLoopType
case 'ModelBlockStandalone'
compileInfoXML = coder.connectivity.ComponentInterfaceArtifacts.CompileInfoTop;
case 'ModelBlock'
compileInfoXML = coder.connectivity.ComponentInterfaceArtifacts.CompileInfoRef;
otherwise
assert(false, sprintf('invalid in-the-loop type %s',inTheLoopType));
end
usingXrelResourcesDir = false;
locator = coder.connectivity.ComponentInterfaceArtifacts.createLocator(...
silPilInterface.getCodeDir(), ...
inTheLoopType, usingXrelResourcesDir, ...
silPilInterface.getCodeName());
this.MF0ParentModelInfo = parser.parseFile(fullfile(locator.getTMWInternalArtifactsPath(), compileInfoXML));
configInterface.setMF0ParentModelInfo(this.MF0ParentModelInfo)
end

function reportUnsupportedDataStoreReference(~, ~, ~)
assert(false, 'Atomic subsystem workflow does not support data stores yet!');
end
end
end