classdef (Hidden = true) ModelRefCodeInfoUtils < coder.connectivity.SimulinkCodeInfoUtils
%MODELREFCODEINFOUTILS provides CodeInfo extensions and utilities tailored
%towards model reference components.
%
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2008-2021 The MathWorks, Inc.
%   
        
    % private properties
    properties (SetAccess = 'private', GetAccess = 'private')
        timingBridgeRequired;
    end
    
    properties (SetAccess = 'private', GetAccess = 'private', NonCopyable = true)
        % do not copy this property on copy operations
        timingBridgeData;        
    end
    
    methods (Access = 'public')     
        
        function this = ModelRefCodeInfoUtils(codeInfoPath, ...
                                      clientInterface, ...
                                      configInterface, ...
                                      componentName, ...
                                      runValidation, varargin)
            % call super class constructor
            this@coder.connectivity.SimulinkCodeInfoUtils(codeInfoPath, ...
                clientInterface, configInterface, componentName, runValidation, varargin{:});
            
            this.initTimingBridgeData;
        end
            
        % return back the Enable/Disable function
        function fcn = getEnableDisableFcn(this, isEnable)
            
            if isEnable
                fcn = this.CodeInfo.EnableFunction;
            else
                fcn = this.CodeInfo.DisableFunction;
            end

            assert(isempty(fcn) || isscalar(fcn),...
                'Expect only one or no Enable/Disable Function!');
        end
                
        % abstract away the differences between the output tasks
        function tasks = getOutputTasks(this)
            if this.isSampleTimeIndependent
                if this.isExportFunctions
                    % If this is an export functions diagram, the timing
                    % modes for the rates in codeInfo appear as
                    % ASYNCHRONOUS. validateTimingProperties already
                    % asserts that we don't expect INHERITED rates in this
                    % case.
                    tasks = this.getAllAsyncOutputTasks;
                    % if any explicit periodic tasking, we append their tasks
                    if this.hasPeriodicExplicitTasking
                        tasks = [tasks this.getOutputTasksForTimingMode('PERIODIC')];
                    end
                    assert(isempty(this.getOutputTasksForTimingMode('INHERITED')),...
                        'INHERITED rates are not expected for export function diagram');
                else
                    % process inherited task
                    tasks = this.getOutputTasksForTimingMode('INHERITED');
                end
                % process any reset tasks
                if this.hasResetFunction
                    tasks = [tasks ...
                        this.getOutputTasksForTimingMode('RESET') ...
                        this.getOutputTasksForTimingMode('RESETWITHINIT')];
                end
            else
                % return all output tasks
                tasks = this.getAllOutputTasks;
            end
        end
        
        % abstract away the differences between the update tasks
        function tasks = getUpdateTasks(this)
            if this.isSampleTimeIndependent
                if this.isExportFunctions
                    % If this is an export functions diagram, the timing
                    % modes for the rates in codeInfo appear as
                    % ASYNCHRONOUS
                    tasks = this.getUpdateTasksForTimingMode('ASYNCHRONOUS');
                    assert(isempty(this.getUpdateTasksForTimingMode('INHERITED')),...
                        'INHERITED rates are not expected for export function diagram');
                else
                    % process inherited task
                    tasks = this.getUpdateTasksForTimingMode('INHERITED');
                end
            else
                % return all update tasks
                tasks = this.getAllUpdateTasks;
            end
        end               
                 
        function [timingBridgeRequired, timingBridgeData] = getTimingBridgeInfo(this)
            timingBridgeRequired = this.timingBridgeRequired;
            timingBridgeData = this.timingBridgeData;
        end
        
        function taskCounterRequired = isTaskCounterRequired(this)
            timingBridgeReq = this.getTimingBridgeInfo;
            taskCounterRequired = timingBridgeReq && ~this.isSampleTimeIndependent;
        end
        
        function rateInteractions = getRateInteractions(this)
            % Return all possible rate interactions between 2 periodic rates
            %
            % A row of rateInteractions looks like [0 1] which indicates a
            % possible interaction between tids 0 and 1
            %
            % This function filters out unnecessary interactions such as:
            %
            % [0 0]
            % [1 1]
            % [1 0] (covered by [0 1])
            %
            rateInteractions = [];
            % rate transition data is only required for multitasking
            % scheduling            
            if this.isMultiTasking
                periodicRates = this.getRates('PERIODIC');
                numRates = length(periodicRates);
                
                for i=1:numRates
                    for j=1:numRates
                        if (i~=j) && (j > i)
                            rateInteractions(end+1, :) = [(i-1) (j-1)]; %#ok<AGROW>
                        end
                    end
                end               
            end                                              
        end
        
        function data = getDataFromIdentifier(this, dataIn, id) %#ok<INUSL>
            % return empty if not found
            data = [];
            for i=1:length(dataIn)
                currData = dataIn(i);
                % get the Identifier
                currIdentifier = ...
                    rtw.connectivity.CodeInfoUtils.getIdentifier(currData);
                if strcmp(currIdentifier, id)
                    % found a match
                    data = currData;
                    break;
                end
            end
        end   

        function fun = getSystemInitializeFunction(this)
            fun = this.CodeInfo.SystemInitializeFunction;
            assert(isempty(this.CodeInfo.InitConditionsFunction), ...
                   'Should be no InitConditionsFunction');
        end 
        
        function sortedParamIndices = getParamIndicesForFunctionImpl(this, functionInterface)
            % return param indices for function
            unsortedParamIndices = this.getDataInterfacesForFunction(functionInterface, 'parameter');
            % make sure parameters associated to model arguments are ordered
            % correctly
            sortedParamIndices = this.getSortedParamsIndices(unsortedParamIndices);
        end        
        
        % override this method to always return false for Model Block SIL/PIL
        function val = isRightClickSubsystemExportFcns(this) %#ok<MANU>
            val = false;
        end
    end
    
    methods (Access = 'protected')                                    
        % get data interfaces for all global data stores that we must
        % process
        function ds = getGlobalDataStoresFromCodeInfo(this)
            % call superclass method to find global data stores accessed
            % by the top-model
            topModelDS = getGlobalDataStoresFromCodeInfo@rtw.connectivity.CodeInfoUtils(this);
            % find global data stores only accessed by sub-models
            subModelDS = this.getSubModelGlobalDataStores;
            
            % A data store could be accessed in the parent and child model
            % but with different timing interfaces on the DataReads and
            % DataWrites.  We need to merge the DataReads and DataWrites
            % into unique entries
            
            % combine and find unique ds by name
            ds = coder.connectivity.DataStoreTimingUtils.mergeSubModelDataStoreTimings(...
                topModelDS, subModelDS, this.UseMF0CodeDescriptor);            
        end
        
        function validateTimingProperties(this)
            % call super-class method first
            validateTimingProperties@rtw.connectivity.CodeInfoUtils(this);
            
            % If this is an export functions model, we don't expect to see
            % inherited rates in CodeInfo. The only allowed exception is
            % DataStores. G1070456 describes a case where CodeInfo will
            % mark the timing of a DataStore as Inherited if it is
            % "Composite" i.e. there are readers and writers in 2 different
            % rates.
            if this.isExportFunctions
                rate = this.getRates('INHERITED');
                if ~isempty(rate)
                    assert(isscalar(rate), ...
                        'Should only be a single inherited rate');
                    
                    assert(isempty(this.getPortsForTimingProperties(rate, ...
                        [this.getCodeInfo.Inports, this.getCodeInfo.Outports])),...
                        'Model block export functions: an inherited rate cannot be on an I/O port.');
                end
            end
        end
        
        function throwContinuousTimeError(this)
            % throw an error saying that continuous time is not supported
            % at all
            rtw.pil.ProductInfo.error('pil', 'ModelBlockContinuousTime', ...
                                      this.CodeInfo.GraphicalPath);
        end
        
        function [inportSizes, ...
                  inportSizesIndices, ...
                  outportSizes, ...
                  outportSizesIndices] = getVariableSignalSizes(this)
            inportSizes = this.getEmptyInterface('DataInterface');
            inportSizesIndices = [];
            outportSizes = this.getEmptyInterface('DataInterface');
            outportSizesIndices = [];
            % process all InternalData looking for variable-size signal
            % information
            allInternalData = this.CodeInfo.InternalData;
            for internalDataIdx = 1:length(allInternalData)
                internalData = allInternalData(internalDataIdx);
                % SID must be empty
                if isempty(internalData.SID)
                   % looking for e.g. InVarDims4 or OutVarDims0
                   tokens = regexp(internalData.GraphicalName, ...
                                   '^(In|Out)VarDims(\d+)$', 'tokens', 'once');
                   if ~isempty(tokens)
                      switch tokens{1}
                          case 'In'
                            inportSizes(end+1) = internalData; %#ok<AGROW>
                            % convert to 1-based index
                            inportSizeIndex = str2double(tokens{2}) + 1;                            
                            if this.isExportFunctions
                                % For Export functions, use expInports to
                                % map to the codeInfo index
                                inportSizeIndex = this.expInports(inportSizeIndex).Index;
                            end
                            inportSizesIndices(end+1) = inportSizeIndex; %#ok<AGROW>
                          case 'Out'
                            outportSizes(end+1) = internalData; %#ok<AGROW>
                            % convert to 1-based index
                            outportSizesIndices(end+1) = str2double(tokens{2}) + 1; %#ok<AGROW>
                          otherwise
                            assert(false, 'Unexpected tokens{1}: %s', tokens{1});
                      end                       
                   end                    
                end                                
            end                                  
        end        
        
        function res = addRebuildNeverMessage(this) %#ok<MANU>
            % this method can be overwritten by subclasses as needed
            res = true;
        end
        
        function dispString = getParameterInterfaceString_Hook(this, dataInterface, origString)
            % get description string for DataInterfaces of parameters that
            % are model arguments
            if this.isModelArgParam(dataInterface)
                sidString = this.getDisplayStringFromSID(dataInterface.SID);
                dispString = ['Model argument ' dataInterface.GraphicalName ' in ' sidString];
            else
                % if not a model argument return the original string
                dispString = origString;
            end
        end   
        
        function initalizeFunctions = getInitializeFunctions(this)
            %Group System Initialize functions with Initialize functions.
            initalizeFunctions = [this.CodeInfo.InitializeFunctions this.getSystemInitializeFunction];
        end
        function isExportFunctions = isExportFunctionsFromMF0(this)
            mf0 = this.ConfigInterface.getMF0ModelInfo;
            
            isExportFunctions = mf0.configSettingsForConsistencyChecks.isExportFcnDiagram;
        end
        
        % find global data stores only accessed by sub-models
        function subModelDS = getSubModelGlobalDataStores(this)
            % look for DataStores where the SID is a model name:
            %
            % - These DataStores are only accessed by a sub-model.
            % - We must load the sub-model CodeInfo in order to get a
            % data interface containing valid implementation details                        
            subModelDS = this.getEmptyInterface('DataInterface');
            allSIDs = {this.CodeInfo.DataStores.SID};            
            % find indices that could correspond to a root model
            SIDdelim = ':';
            possibleRootModelIndices = strcmp(allSIDs, strtok(allSIDs, SIDdelim));
            % filter out empty SID's
            nonEmptyIndices = ~strcmp(allSIDs, '');
            % find intersection
            indices = nonEmptyIndices & possibleRootModelIndices;
            dsToProcess = this.CodeInfo.DataStores(indices);
            
            %Find all model reference instances down the hierarchy
            allReferencedModelInstances = this.getAllReferenceModelInstances(this.CodeDescriptorHandle);

            % process each ds
            lMf0 = this.ConfigInterface.getMF0ModelInfo;
            lDataStoreTimingUtils = coder.connectivity.DataStoreTimingUtils(lMf0, this.getCodeInfo, this.UseMF0CodeDescriptor);
            
            for dsIdx = 1:length(dsToProcess)
               ds = dsToProcess(dsIdx);
               assert(isempty(ds.Implementation), ...
                   'Implementation of data store accessed only in sub-model must be empty.');
               subModelCodeInfo = this.getSubModelCodeInfo(ds.SID);   
               matchedChildInstances = allReferencedModelInstances(arrayfun(@(refModel) strcmp(ds.SID, refModel.ReferencedModelName), allReferencedModelInstances));
               parentCodeInfoTimingProperties = this.getCodeInfo().TimingProperties;
               mappedRates = coder.connectivity.DataStoreTimingUtils.mapChildInheritedRatesToParentRates(subModelCodeInfo,parentCodeInfoTimingProperties,matchedChildInstances);
               
               % find global data stores
               subModelGlobalDSIdx = strcmp({subModelCodeInfo.DataStores.SID}, '');
               % find data stores with matching GraphicalName
               subModelGraphicalNameMatchIdx = ...
                   strcmp({subModelCodeInfo.DataStores.GraphicalName}, ds.GraphicalName);
               if isempty(ds.SharedLocalDSMInfo) %Skip Shared Local Data Stores                  
                   % find intersection
                   subModelDSIdx = subModelGlobalDSIdx & subModelGraphicalNameMatchIdx;
                   newSubModelDS = subModelCodeInfo.DataStores(subModelDSIdx);
                   assert(isscalar(newSubModelDS), ...
                       'Number of matching submodel global data stores must be 1');
                   assert(isempty(newSubModelDS.SID), 'SID must be empty');
                   
                   lDataStoreTimingUtils.updateSubModelGlobalDataStore(newSubModelDS, ds, mappedRates);
                   
                   subModelDS = [subModelDS, newSubModelDS]; %#ok<AGROW>
               end
            end                        
        end 
    end
        
    methods (Access = 'private')
        function initTimingBridgeData(this)
            % process timingBridge
            this.timingBridgeData = this.getDataFromIdentifier(this.CodeInfo.InternalData, 'timingBridge');
            if ~isempty(this.timingBridgeData)
                % check timing bridge needs to be defined
                assert(~this.timingBridgeData.Implementation.isDefined);
                this.timingBridgeRequired = true;
            else
                this.timingBridgeRequired = false;
            end            
            % check that there are no offset sample times
            % they are not supported because the timingBridge setup is more
            % complicated than for regular sample times
            %
            % note: for consistency, offset sample times are not supported
            % regardless of whether or not the timingBridge is required
            %
            % INHERITED sample times that turn out to have an offset are
            % ok - the timingBridge is not used in this case
            rates = this.getRates('PERIODIC');
            for i=1:length(rates)
                rate = rates(i);
                % (@todo [SCHEDTR-2147]):
                % Skipping PDR rates for now but this needs to take the clock type into account.
                % Currently the type of clock (event vs timer) is not captured in rate info
                % The issue is similar to DSM
                if rate.SampleOffset ~= 0 && ~(rate.SamplePeriod > 0 && rate.SampleOffset == -30)
                    rtw.pil.ProductInfo.error('pil', 'ModelBlockSampleOffset', ...
                        this.CodeInfo.GraphicalPath, ...
                        rtw.connectivity.CodeInfoUtils.double2str(rate.SamplePeriod), ...
                        rtw.connectivity.CodeInfoUtils.double2str(rate.SampleOffset));
                end
            end
        end                
       
    end
end
