classdef (Hidden = true) StandaloneCodeInfoUtils < coder.connectivity.SimulinkCodeInfoUtils
    %STANDALONECODEINFOUTILS provides CodeInfo extensions and utilities
    %tailored towards standalone code interfaces.
    %
    %   This is an undocumented class. Its methods and properties are
    %   likely to change without warning from one release to the next.
    
    %   Copyright 2013-2021 The MathWorks, Inc.
    
    
    methods (Access = 'public')
        
        % returns true if this a top model Export Functions Diagram
        function val = isTopModelExportFunctions(this)
            assert(~isempty(this.ConfigInterface),...
                'this.ConfigInterface should not be empty!');
            assert(~isempty(this.ConfigInterface),...
                'this.ConfigInterface should not be empty!');
            val = this.ClientInterface.isExportFcnModel(this.ConfigInterface);
        end
        
        function validateGroupedModelArgs(this)
            % check if the block uses any ungrouped model arguments
            mf0 = this.ConfigInterface.getMF0ModelInfo;
            registeredArgs = mf0.interfaceParameterInfo.modelArguments.toArray;
            ungroupedArgs = arrayfun(@(x)(isempty(x.coderGroup)), registeredArgs);
            hasUngroupedMdlArgs = any(ungroupedArgs);
            
            if hasUngroupedMdlArgs
                % Ungrouped model arguments are not supported by standalone
                % model block. The ungrouped arguments are inlined in the 
                % generated code and therefore we cannot pass the correct
                % instance-specific value from the SL engine.
                ungroupedArgNames = arrayfun(@(x)x.Name, registeredArgs(ungroupedArgs), ...
                    'UniformOutput', false);
                rtw.pil.ProductInfo.error('pil','UnsupportedModelArgs', ...
                    this.CodeInfo.GraphicalPath, ...
                    strjoin(ungroupedArgNames, ', '));
            end
        end
    end
    
    methods (Access = protected)
        function ds = getGlobalDataStoresFromCodeInfo(this)
            ds = getGlobalDataStoresFromCodeInfo@coder.connectivity.SimulinkCodeInfoUtils(this);
            
            if this.hasModelWideEvents
                initTiming = this.createModelWideTimingInterface('INITIALIZE', 1);
                termTiming = this.createModelWideTimingInterface('TERMINATE', 2);
                
                for idx=1:numel(ds)
                    dataWrites = this.getArray(ds(idx).DataWrites);
                    for writeIdx = 1:numel(dataWrites)
                        %Capture if there are any writes to Data Stores in
                        %INITIALIZE functions. We need to do this before
                        %modifying CodeInfo to add missing Data Reads and
                        %Writes.
                        if strcmp(dataWrites(writeIdx).TimingMode,'INITIALIZE')
                            this.HasDSMWritesInInit = true;
                            break;
                        end
                    end
                    %We can be missing Initialize and Terminate Reads and
                    %Writes Data Stores, so always add them. See g153411.
                    %This means we will always transfer them in Initialize
                    %and Terminate calls.
                    currentDataReads = this.getArray(ds(idx).DataReads);
                    dataReadsTimingList = unique([currentDataReads initTiming termTiming]);
                    currentDataWrites = this.getArray(ds(idx).DataWrites);
                    dataWritesTimingList = unique([currentDataWrites initTiming termTiming]);
                    if this.UseMF0CodeDescriptor
                        %Remove the DataReads
                        ds(idx).DataReads.clear
                        for readIdx = 1:numel(dataReadsTimingList)
                            %Add back a unique list including the current
                            %DataReads, initTiming and termTiming
                            ds(idx).DataReads.add(dataReadsTimingList(readIdx));
                        end

                        %Remove the DataWrites
                        ds(idx).DataWrites.clear
                        for writesIdx = 1:numel(dataWritesTimingList)
                            %Add back a unique list including the current
                            %DataWrites, initTiming and termTiming
                            ds(idx).DataWrites.add(dataWritesTimingList(writesIdx));
                        end
                    else
                        ds(idx).DataReads = dataReadsTimingList;
                        ds(idx).DataWrites = dataWritesTimingList;
                    end
                end
            end
            if this.isExportFunctions
                for idx=1:numel(ds)
                    curDs = ds(idx);
                    % Reads and Writes, from child models don't bubble up
                    % from Export Functions models. See G1568323
                    % which tracks this underlying issue.
                    % To workaround this, add INHERITED rate to both.
                    % See G1538574.
                    %
                    % Note, just because there IS a read/write in the
                    % current model does not mean that there isn't ALSO a
                    % read/write in a child.  So if there isn't already an
                    % inherited or periodic (i.e., task linked) timing mode,
                    % attach an INHERITED timing mode to
                    % DataReads/DataWrites.
                    dataReads = this.getArray(curDs.DataReads);
                    dataWrites = this.getArray(curDs.DataWrites);
                    if this.UseMF0CodeDescriptor
                        dataReadsTimingModes = arrayfun(@(x)char(x.TimingMode), dataReads, 'UniformOutput', false);
                        dataWritesTimingModes = arrayfun(@(x)char(x.TimingMode), dataWrites, 'UniformOutput', false);
                        allTimingModes = [dataReadsTimingModes, dataWritesTimingModes];
                        if ~ismember('PERIODIC', allTimingModes)
                            tmpTiming = this.createTimingInterface(this.getMF0ModelInstance, 'INHERITED');
                            tmpTiming.Priority = 40; %normal default priority
                            if ~ismember('INHERITED', dataReadsTimingModes)
                                curDs.DataReads.add(tmpTiming);
                            end
                            if ~ismember('INHERITED', dataWritesTimingModes)
                                curDs.DataWrites.add(tmpTiming);
                            end
                        end
                    else
                        if ~ismember('PERIODIC', {curDs.DataReads.TimingMode, curDs.DataWrites.TimingMode})
                            tmpTiming = this.createTimingInterface(this.getMF0ModelInstance, 'INHERITED');
                            tmpTiming.Priority = 40; %normal default priority
                            if ~ismember('INHERITED', {curDs.DataReads.TimingMode})
                                curDs.DataReads(end+1) = tmpTiming;
                            end
                            if ~ismember('INHERITED', {curDs.DataWrites.TimingMode})
                                curDs.DataWrites(end+1) = tmpTiming;
                            end
                        end
                    end
                end                
            end
        end
        
        % template method to return variable size signal information
        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. External(In/Out)putSize. Then
                    % matching fields with matching names to ports
                    tokens = regexp(internalData.GraphicalName, ...
                        '^External(In|Out)putSize$', 'tokens', 'once');
                    if ~isempty(tokens)
                        elements = this.getArray(internalData.Implementation.Type.Elements);
                        for idx = 1:numel(elements)                            
                            switch tokens{1}
                                case 'In'
                                    allInports = this.CodeInfo.Inports;
                                    [~, matchingPort] = ismember(elements(idx).Identifier, {allInports.GraphicalName});
                                    %Create new data interface, patching
                                    %in Implementation from Size Variable
                                    inportSizes(end+1) = this.createDataInterface; %#ok<AGROW>
                                    inportSizes(end).Implementation = allInports(matchingPort).Implementation.DynamicMemory.SizeVariable;
                                    inportSizeIndex = matchingPort;
                                    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'
                                    allOutports = this.CodeInfo.Outports;
                                    [~, matchingPort] = ismember(elements(idx).Identifier, {allOutports.GraphicalName});
                                    %Create new data interface, patching
                                    %in Implementation from Size Variable
                                    outportSizes(end+1) = this.createDataInterface; %#ok<AGROW>                                    
                                    outportSizes(end).Implementation = allOutports(matchingPort).Implementation.DynamicMemory.SizeVariable;
                                    outportSizesIndices(end+1) = matchingPort; %#ok<AGROW>
                                otherwise
                                    assert(false, 'Unexpected tokens{1}: %s', tokens{1});
                            end
                        end
                    end
                end
            end
        end
        
        function val = canUseDirectReadsAndWrites(this, ci)
            % TODO CTDSPI-1203 extend strategy to ModelRef hierarchy for 
            % subsystem use case
            val = this.CodeDescriptorStrategy.canUseDirectReadsAndWrites(ci);
        end
    end
    
    methods(Access = private)
        function dataInterface = createDataInterface(this)
            %Create dummy DataInterface.
            mf0Instance = this.getMF0ModelInstance;
            if isempty(mf0Instance)
                dataInterface = RTW.DataInterface();
            else
                dataInterface = coder.descriptor.DataInterface(mf0Instance);
            end
        end
        
        function timingInterface = createModelWideTimingInterface(this, TimingMode, SampleOffset)
            %Create dummy Model Wide Event timing interfaces.
            timingInterface = rtw.connectivity.CodeInfoUtils.createTimingInterface(this.getMF0ModelInstance, TimingMode);
            timingInterface.SamplePeriod = Inf;
            timingInterface.SampleOffset = SampleOffset;
            timingInterface.Priority = -32768; %normal default priority
        end
    end
end
