classdef (Hidden = true) AtomicSubsystemTargetInterface < rtw.pil.SubsystemTargetInterface   
%ATOMICSUBSYSTEMTARGETINTERFACE creates a subsystem target interface file 
%for atomic subsystem workflows
%
%   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.

    methods
        function this = AtomicSubsystemTargetInterface(silPILWrapperUtils, ...
                targetInterfacePath, varargin)
            % call super class constructor
            this@rtw.pil.SubsystemTargetInterface(silPILWrapperUtils, ...
                targetInterfacePath, varargin{:});  
            
            % use the parent interface to cache the RTEInstance identifier
            % in case it's not directly used by the subsystem
            cdUtils = this.getCodeInfoUtils();
            prevState = cdUtils.shouldUseParentInterface(true);
            restoreState = onCleanup(@()cdUtils.shouldUseParentInterface(prevState));
            this.InterfaceStrategy.refreshRTEInstanceCache();
        end
    end
    
    methods (Access = protected)
        function checkIOPortsCanDefineStorage(this)
            % We need to check that storage for AutosarSenderReceiver has been defined,
            % otherwise xil_interface will fail to compile. Storage might not be defined if the
            % input is not used and we cannot match an inport with a direct read.

            indicesWithStorage = this.InterfaceStrategy.getInportIndicesForStorage();
            indicesToTransfer = this.getInportIndicesForTransfer();

            indicesWithoutStorage = setdiff(indicesToTransfer, indicesWithStorage);
            numIndWithoutStorage = numel(indicesWithoutStorage);
            if numIndWithoutStorage > 0
                thisSubsystem = this.SILPILWrapperUtils.getComponentForDiagnostics();
                if numIndWithoutStorage == 1
                    msg = message( ...
                        'PIL:pil_subsystem:UnsupportedOneUnusedAutosarSenderReceiver', ...
                        thisSubsystem, num2str(indicesWithoutStorage(1)));
                else
                    indicesWithoutStorage = ...
                        arrayfun(@(i) num2str(i), indicesWithoutStorage, UniformOutput=false);
                    inports = strjoin(indicesWithoutStorage(1:end-1), ', ');
                    msg = message( ...
                        'PIL:pil_subsystem:UnsupportedMultipleUnusedAutosarSenderReceiver', ...
                        thisSubsystem, inports, indicesWithoutStorage{end});
                end
                error = MSLException(msg);
                error.throw();
            end
        end
        function checkIOReadOnly(this, dataInterface, dataInterfaceType)
            % Default to base class implementation
            checkIOReadOnly@rtw.pil.TargetInterface(this, dataInterface, dataInterfaceType);
        end

        function throwReadOnlyDiagnostic(this, dataInterfaceString)
            % Throw read only error. This method overrides the parent class
            % method.
            subsystemName = this.SILPILWrapperUtils.getComponentForDiagnostics();
            diagObj = MSLException(message('PIL:pil_subsystem:ReadOnlyDataInterface', ...
                dataInterfaceString, subsystemName));
            diagObj.throw();
        end

        function writeSectionStorageOutOfBoundaryDataHook(this)
            % Allocate storage for interfaces outside of the subsystem
            % boundaries. This is needed to make sure we can compile and
            % link the whole model.c file.
                   
            % allocate storage for IO in the parent
            filterInactiveInterfaces = false;
            [inportInterfaces, outportInterfaces] = this.getRTWDataInterfacesForComponentIO(filterInactiveInterfaces);
            dataInterfaces = [inportInterfaces, outportInterfaces];
            mustAllocate = true;
            this.writeSectionStorageForData(dataInterfaces, mustAllocate);  
            
            % allocate storage for parameters in the parent
            dataInterfaces = this.getRTWDataInterfacesForComponentParameters();
            this.writeSectionStorageForData(dataInterfaces);
            
            % allocate storage for DSMs in the parent
            cdUtils = this.getCodeInfoUtils();
            prevState = cdUtils.shouldUseParentInterface(true);
            restoreState = onCleanup(@()cdUtils.shouldUseParentInterface(prevState));
            
            [~, ~, ...
                allSharedDS, ...
                readerSharedDS, ...
                writerSharedDS] = cdUtils.getDataStorePropertiesFromCodeDescriptor();
            useCache = false;
            dataInterfaces = [rtw.pil.CodeInfoData.createDataInterfacesFromList(...
                readerSharedDS, 1:length(readerSharedDS), 'dsread', ...
                this.SILPILWrapperUtils, filterInactiveInterfaces, useCache), ...
                rtw.pil.CodeInfoData.createDataInterfacesFromList(...
                writerSharedDS, 1:length(writerSharedDS), 'dswrite', ...
                this.SILPILWrapperUtils, filterInactiveInterfaces, useCache), ...
                rtw.pil.CodeInfoData.createDataInterfacesFromList( ...
                allSharedDS, 1:length(allSharedDS), 'sharedLocalDataStore', ...
                this.SILPILWrapperUtils, filterInactiveInterfaces, useCache)];
            this.writeSectionStorageForData(dataInterfaces);

            % Allocate storage for InternalData in the parent interface.
            % Setup the interface strategy to skip data interfaces from the
            % parent interface that are not used in the subsystem's
            % xil_interface, i.e. the RTE_instance static variable.
            % If the RTE_instance is needed by the subsystem interface it
            % will be defined when processing the subsystem's internal data
            this.writeSectionStorageInternalData(UseCachedDataInterfaces=false);
        end
        
        function writeSectionEntryPointDeclarations(this)
            % Write a function declaration for the subsystems entry points
            % if one is not available in the generated headerfile
            
            if this.SILPILWrapperUtils.getSILPILInterface.getInTheLoopType == rtw.pil.InTheLoopType.ModelBlockStandalone &&...
                    strcmp(this.SILPILWrapperUtils.getParam('IsERTTarget'), 'on') && ...
                    ~strcmp(this.SILPILWrapperUtils.getParam('ERTFilePackagingFormat'), 'Modular')
                
                assert(~this.SILPILWrapperUtils.isCPPEncap(), ...
                    'C++ classes are not supported by this implementation');
                ssInterface = this.getCodeInfo();
                entryPoints = rtw.connectivity.CodeInfoUtils.getActiveEntryPoints(ssInterface);
                for idx = 1:numel(entryPoints)
                    this.writer.wLine('%s;', ...
                        this.getFunctionDeclaration(entryPoints{idx}));
                end
            end
        end
        
        function dataInterfaces = getDataInterfacesForGetSetFunctionProcessingHook(this)
            % return the set of dataInterfaces to process for Get/Set
            % function header files and definitions looking into the parent
            % interfaces, this is needed to compile model.c
            
            cdUtils = this.getCodeInfoUtils();
            prevState = cdUtils.shouldUseParentInterface(true);
            restoreState = onCleanup(@()cdUtils.shouldUseParentInterface(prevState));
            
            [~, ~, allComponentSharedDS] = cdUtils.getDataStorePropertiesFromCodeDescriptor();
            
            [componentInports, componentOutports] = this.getCodeDescDataInterfacesForComponentIO();
            dataInterfaces = [...
                componentInports, ...
                componentOutports, ...
                this.getParametersForStorageInComponent(), ...
                allComponentSharedDS];
        end
        
        function dataInterfaces = getDataInterfacesForDynamicAccessFunctionsHook(this)
            % return the set of dataInterfaces to process for dynamic
            % access functions looking into the parent interfaces, this is
            % needed to compile model.c
            
            filterInactiveInterfaces = true;
            [inportInterfaces, outportInterfaces] = this.getRTWDataInterfacesForComponentIO(filterInactiveInterfaces);
            parameterInterfaces = this.getRTWDataInterfacesForComponentParameters();
            dataInterfaces = [inportInterfaces, outportInterfaces, parameterInterfaces];
        end
                
        function checkIfImplementationsNeedToSyncInUpdate(this)
            % For each output/update function pair extract the
            % implementations that are:
            % - written to in output functions
            % - read in output functions
            % - written to in update functions
            % - data read in update functions
            % and check if any Inport implementation should be synced 
            % during update
            
            cdUtils = this.getCodeInfoUtils();
            ssInterface = cdUtils.getCodeInfo();
            inports = ssInterface.Inports;
            outputFunctions = ssInterface.OutputFunctions;
            updateFunctions = ssInterface.UpdateFunctions;
            
            for fIdx = 1:length(updateFunctions)
                
                % Create list of implementations written to in the output
                % function
                allOutputWrites = [...
                    cdUtils.getAllImplementations(outputFunctions(fIdx).ActualReturn), ...
                    cdUtils.getAllImplementations(outputFunctions(fIdx).DirectWrites)];
                % Include IN_OUT arguments
                rhsIOType = arrayfun( @(x) x.IOType, ...
                    rtw.connectivity.CodeInfoUtils.getArray(outputFunctions(fIdx).Prototype.Arguments));
                rhsInOut = rhsIOType == coder.descriptor.types.IOTypes.INPUT_OUTPUT; 
                allOutputWrites = [allOutputWrites, ...
                    cdUtils.getAllImplementations(outputFunctions(fIdx).ActualArgs(rhsInOut))]; %#ok<AGROW>
                
                % Create list of implementations read in the output
                % function
                rhsIn = rhsIOType == coder.descriptor.types.IOTypes.INPUT; 
                allOutputReads = [ ...
                    cdUtils.getAllImplementations(outputFunctions(fIdx).DirectReads), ...
                    cdUtils.getAllImplementations(outputFunctions(fIdx).ActualArgs(rhsIn))];
                
                % Create list of implementations written to in the output
                % function
                allUpdateReads = [...
                    cdUtils.getAllImplementations(updateFunctions(fIdx).ActualArgs), ...
                    cdUtils.getAllImplementations(updateFunctions(fIdx).DirectReads)];
                
                % Create list of implementations written to in the update
                % function
                allUpdateWrites = [...
                    cdUtils.getAllImplementations(updateFunctions(fIdx).ActualReturn), ...
                    cdUtils.getAllImplementations(updateFunctions(fIdx).DirectWrites)];
                % Include IN_OUT arguments
                rhsIOType = arrayfun( @(x) x.IOType, ...
                    rtw.connectivity.CodeInfoUtils.getArray(updateFunctions(fIdx).Prototype.Arguments));
                rhsInOut = rhsIOType == coder.descriptor.types.IOTypes.INPUT_OUTPUT; 
                allUpdateWrites = [allUpdateWrites, ...
                    cdUtils.getAllImplementations(updateFunctions(fIdx).ActualArgs(rhsInOut))]; %#ok<AGROW>
                
                % Error out if any Inport implementation should be synced
                % during update
                for idx = 1:length(inports)
                    this.errorIfNeedToSyncInUpdate(inports(idx), ...
                        allOutputWrites, allOutputReads, allUpdateWrites, allUpdateReads);
                end
                
            end
            
        end
        
        function needExtDecl = needExternDeclarationForVariable(this, implementation)
            needExtDecl = false;
            owner = this.getImplementationOwner(implementation);
            assert(~isempty(owner), 'needExternDeclarationForVariable should be called only on defined implementations');
            % if the variable is not defined (and declared) by PIL
            if ~strcmp(owner, 'PIL')
                baseStorage = this.getBaseStorageImplementation(implementation);
                % if this is a variable or derived class with a
                % DeclarationFile not set to a header (empty or in a .c/cpp)
                needExtDecl = this.codeInfoUtils.isa(baseStorage, 'Variable') && ...
                    ~rtw.connectivity.CodeInfoUtils.isDeclararedInHeader(baseStorage) && ...
                    ~this.InterfaceStrategy.isInternalDataWithMappingToRTE(baseStorage);
            end
        end
    end
    
    methods (Access = private)
        function componentOnlyParams = getParametersForStorageInComponent(this)
            % return tunable parameters in the parent that are not part of
            % the subsystem interface
            
            cdUtils = this.getCodeInfoUtils();
            origState = cdUtils.shouldUseParentInterface(false);
            restoreState = onCleanup(@()cdUtils.shouldUseParentInterface(origState));
            % access the subsystem interface
            ssInterface = this.getCodeInfo();
            subsystemParams = ssInterface.Parameters;
            
            % access the parent interface
            cdUtils.shouldUseParentInterface(true);
            componentInterface = this.getCodeInfo();
            allParams = componentInterface.Parameters;
            componentOnlyParams = [];
            for pIdx = 1:length(allParams)
                currParam = allParams(pIdx);
                if ~any(arrayfun(@(pArray) currParam == pArray, subsystemParams))
                    % if the current parameter doesn't match any of the
                    % subsystem parameters
                    if this.SILPILWrapperUtils.checkParameterTunabilitySupport(currParam) && ...
                            ~cdUtils.isDirectModelArgParamInComponent(currParam)
                        % add the current parameter to the list only if:
                        % - it is tunable, non-tunable parameters should 
                        % not be defined by SIL/PIL as we expect users to 
                        % provide a value e.g. via custom code
                        % - it is not a direct model argument, these are
                        % arguments in model_step and don't need to be
                        % defined
                        componentOnlyParams = [componentOnlyParams, currParam]; %#ok<AGROW>
                    end
                end
            end
        end
        
        function [inports, outports] = getCodeDescDataInterfacesForComponentIO(this)
            % return the parent's DataInterfaces for Inports and Outports
            cdUtils = this.getCodeInfoUtils();
            prevState = cdUtils.shouldUseParentInterface(true);
            restoreState = onCleanup(@()cdUtils.shouldUseParentInterface(prevState));
                     
            componentInterface = this.getCodeInfo();
            inports = componentInterface.Inports(this.InterfaceStrategy.getInportIndicesForStorage());
            outports = componentInterface.Outports(this.InterfaceStrategy.getOutportIndicesForStorage());
        end
        
        function [inportInterfaces, outportInterfaces] = getRTWDataInterfacesForComponentIO(this, filterInactiveInterfaces)
            % return Inports and Outports RTW DataInterfaces for the parent
            
            cdUtils = this.getCodeInfoUtils();
            prevState = cdUtils.shouldUseParentInterface(false);
            % access the subsystem interface
            ssInterface = this.getCodeInfo();
            subsystemInports = ssInterface.Inports;
            subsystemOutports = ssInterface.Outports;
            cdUtils.shouldUseParentInterface(prevState);
            
            useCache = false;
            % create data interfaces for inports outside of the subsystem
            % interface
            [componentInports, componentOutports] = this.getCodeDescDataInterfacesForComponentIO();
            interfaceIdxs = (1:length(componentInports)) + length(subsystemInports);
            inportInterfaces = rtw.pil.CodeInfoData.createDataInterfacesFromList( ...
                componentInports, interfaceIdxs, 'input', ...
                this.SILPILWrapperUtils, filterInactiveInterfaces, useCache);
            % create data interfaces for outports outside of the subsystem
            % interface
            interfaceIdxs = (1:length(componentOutports)) + length(subsystemOutports);
            outportInterfaces = rtw.pil.CodeInfoData.createDataInterfacesFromList( ...
                componentOutports, interfaceIdxs, 'output', ...
                this.SILPILWrapperUtils, filterInactiveInterfaces, useCache);
        end
        
        function parameterInterfaces = getRTWDataInterfacesForComponentParameters(this)
            % return Parameters RTW DataInterfaces for the parent
            
            cdUtils = this.getCodeInfoUtils();
            % access the subsystem interface
            prevState = cdUtils.shouldUseParentInterface(false);
            ssInterface = this.getCodeInfo();
            subsystemParams = ssInterface.Parameters;
            cdUtils.shouldUseParentInterface(prevState);

            % create the interfaces
            componentOnlyParams = this.getParametersForStorageInComponent();
            filterInactiveInterfaces = true;
            useCache = false;
            interfaceIdxs = (1:length(componentOnlyParams)) + length(subsystemParams);
            parameterInterfaces = rtw.pil.CodeInfoData.createDataInterfacesFromList( ...
                componentOnlyParams, interfaceIdxs, 'parameter', ...
                this.SILPILWrapperUtils, filterInactiveInterfaces, useCache);
        end
        
        function  errorIfNeedToSyncInUpdate(this, inport, allOutputWrites, allOutputReads, allUpdateWrites, allUpdateReads)
            % Error out if any Inport implementation read by the update 
            % function should be synced between output and update rather
            % then before output.
            
            % We can't sync muxed ports between output and update because
            % the logic in our infrastructure is based on DataInterfaces
            % and not on implementations (see InportDirectFeedThrough in
            % rtw.pil.SILPILWrapperUtils).
            % Some implementations in a muxed port could have to be synced 
            % before output while the others between output and update.
            
            inportImplementations = this.getCodeInfoUtils().getInterfaceImplementations(inport.Implementation);
            for idx = 1:length(inportImplementations)              
                inportReadByUpdateFcn = ...
                    arrayfun(@(x) x == inportImplementations(idx), allUpdateReads);
                
                if any(inportReadByUpdateFcn) 
                    
                    inportWrittenInOutputFcn = ...
                        arrayfun(@(x) x == inportImplementations(idx), allOutputWrites);
                    inportReadInOutputFcn = ...
                        arrayfun(@(x) x == inportImplementations(idx), allOutputReads);                  
                    inportWrittenInUpdateFcn = ...
                        arrayfun(@(x) x == inportImplementations(idx), allUpdateWrites);
                    
                    if any(inportWrittenInOutputFcn)
                        % if the current inport is:
                        % - written by the output function
                        % - read by the update function
                        % it should be synced between output/update to
                        % ensure the update function has fresh data from
                        % Simulink and not a stale value from generate code
                        thisSubsystem = this.SILPILWrapperUtils.getComponentForDiagnostics();
                        thisInport = this.codeInfoUtils.getDataInterfaceString(inport, 'inport');
                        sharedExpression = this.getExpression(inportImplementations(idx));
                        
                        error = MSLException(message( ...
                            'PIL:pil_subsystem:ImplementationReadByUpdateAndWrittenByOutput', ...
                            thisSubsystem, thisInport, sharedExpression));
                        error.throw();
                    elseif any(inportWrittenInUpdateFcn) && any(inportReadInOutputFcn)
                        % if the current inport is:
                        % - read by the output function
                        % - read by the update function
                        % - written by the update function
                        % it should be synced between output/update to
                        % ensure the output function has data computed in
                        % the update function and not directly from
                        % Simulink
                        thisSubsystem = this.SILPILWrapperUtils.getComponentForDiagnostics();
                        thisInport = this.codeInfoUtils.getDataInterfaceString(inport, 'inport');
                        sharedExpression = this.getExpression(inportImplementations(idx));
                        
                        error = MSLException(message( ...
                            'PIL:pil_subsystem:ImplementationAccessedByUpdateReadByOutput', ...
                            thisSubsystem, thisInport, sharedExpression));
                        error.throw();
                    end
                end
            end
        end

        function indices = getInportIndicesForTransfer(this)
            % Return indices for inports that xil_interface will transfer from host to target.

            tasks = this.getOutputTasks;
            numOutputTasks = numel(tasks);
            tasks = [tasks this.codeInfoUtils.getUpdateTasks];

            indices = [];
            for idx = 1:numel(tasks)
                task = tasks(idx);
                portIndices = this.codeInfoUtils.getInputPortsForTask(task);
                if idx <= numOutputTasks
                    indices = [indices ...
                        this.SILPILWrapperUtils.getDFInputPortsIndices(portIndices)]; %#ok<AGROW>
                else
                    indices = [indices ...
                        this.SILPILWrapperUtils.getNonDFInputPortsIndices(portIndices)]; %#ok<AGROW>
                end
            end

        end
    end
end
