classdef (Hidden = true) CodeDescriptorStrategy < handle
    %CODEDESCRIPTORSTRATEGY provides CodeDescriptorUtils functionality
    %that can be replaced by other strategies.
    %
    %   This is an undocumented class. Its methods and properties are
    %   likely to change without warning from one release to the next.
    
    %   Copyright 2021-2023 The MathWorks, Inc.
    
    properties (Access = protected, NonCopyable = true)
        CodeDescriptorUtils rtw.connectivity.CodeInfoUtils
    end
    
    methods (Access = public)
        
        function initialize(this, codeDescriptorUtils)
            this.CodeDescriptorUtils = codeDescriptorUtils;
        end
        
        % return the storage specifier string for an implementation
        function storageSpecifier = getStorageSpecifier(this, implementation)
            switch rtw.connectivity.CodeInfoUtils.class(implementation)
                case {'Variable', ...
                        'StructAccessorVariable', ...
                        'PointerVariable', ...
                        'CustomVariable'}
                    storageSpecifier = implementation.StorageSpecifier;
                case {'StructExpression', ...
                        'ArrayExpression',  ...
                        'PointerExpression', ...
                        'ClassMethodExpression', ...
                        'ClassMemberExpression', ...
                        'StaticMemberExpression'}
                    % recurse to a terminal Variable or PointerVariable
                    % case
                    storageSpecifier = this.getStorageSpecifier(implementation.BaseRegion);
                case 'AutosarCalibration'
                    baseRegion = rtw.connectivity.CodeInfoUtils.getAutosarCalibrationBaseRegion(implementation);
                    storageSpecifier = this.getStorageSpecifier(baseRegion);      
                case {'CustomExpression', ...
                        'Literal', ...
                        'BasicAccessFunctionExpression'}
                    % only Variable's have a storage specifier
                    % we don't know the storage specifier for a
                    % CustomExpression - return "unknown"
                    storageSpecifier = 'unknown';
                case {'TypedCollection', 'BasicAccessFunctionExpressionCollection'}
                    %Use storageSpecifier from first element. This might
                    %need to change as we extend support for
                    %TypedCollection items.
                    elements = rtw.connectivity.CodeInfoUtils.getArray(implementation.Elements);
                    elImplementation = elements(1);
                    storageSpecifier = this.getStorageSpecifier(elImplementation);
                otherwise
                    assert(false, 'Unexpected implementation class: %s', ...
                        class(implementation));
            end
        end
        
        function numExportFunctionsInports = getNumExportFunctionsInports(this)
            numExportFunctionsInports = length( ...
                this.CodeDescriptorUtils.getExpInportsForComponentUnderTest());
        end
                
        function isSystemConstant = isDataInterfaceSystemConstant(~, ~)
            isSystemConstant = false;
        end
        
        % return any additional input indices. For most systems transfer
        % all inputs with the Initialize timing property.
        % Overridden in subclasses
        function inportIndices = getAdditionalInitializeInportIndices(this, inportIndices)
            cdUtils = this.CodeDescriptorUtils;
            inportIndices = unique([inportIndices ...
                cdUtils.getInputPortsForRates(cdUtils.getRates('INITIALIZE'))]);
        end
        
        function throwUnsupportedModelArgsError(~, componentName, argumentNames)
            % Throw model argument error
            DAStudio.error('PIL:pil:UnsupportedModelArgs', ...
                componentName, argumentNames);
        end
        
        function modelWorkspaceParametersCanBeUninitialized = modelWorkspaceParametersCanBeUninitialized(~)
            % model workspace params can typically be uninitialized, e.g. if a
            % parameter is mapped to a storage class that does not generate
            % data initialization
            modelWorkspaceParametersCanBeUninitialized = true;
        end
        
        function callerFunctionsInfo = initializeCallerFunctionsInfoHook(~, ~)
            callerFunctionsInfo = [];
        end
    
        function isValid = isValidServerCallPoint(~, ~)
            % For non-AUTOSAR targets serverCallPoint should always be
            % valid
            isValid = true;
        end
        
        function val = canUseDirectReadsAndWrites(this, ci)
            % DirectReads and DirectWrites are available for:
            % Extend base-class conditions for a few more models.ex
            % 
            % 1) Right-click export functions
            % 2) ExportFcnDiagram (If not C++ or a RESET function)
            % 3) AsyncFcnCallModel (Async timing only)
            %
            cdUtils = this.CodeDescriptorUtils;
            isCpp = strcmp(getParam(cdUtils.ConfigInterface,'TargetLang'), 'C++');
            isRESETTiming = strcmp(ci.TimingMode, 'RESET') || strcmp(ci.TimingMode, 'RESETWITHINIT');
            val = cdUtils.isRightClickSubsystemExportFcns() || ...
                (cdUtils.isExportFcnDiagram() && (~isCpp && ~isRESETTiming)) ||  ...
                (cdUtils.isExportFunctions() && strcmp(ci.TimingMode, 'ASYNCHRONOUS'));
        end
        
        function isAutosar = isAUTOSAR(~)
            isAutosar = false;
        end
        
        function indices = getDataInterfacesForFunctionsHook(~, ~, indices)
            % no op
        end
        
        function validateMessageInterfaces(this)
            if ~matlab.internal.feature("XILMessageSupport")
                inteface = this.CodeDescriptorUtils.getCodeInfo();
                this.errorForMessageImplementations(inteface.Inports, ...
                    'Simulink:Message:CannotSILPILFromRootInport');

                this.errorForMessageImplementations(inteface.Outports, ...
                    'Simulink:Message:CannotSILPILFromRootOutport');
            end
        end
        
        function errorStatusImpl = getStatusFromReceiver(~, ~, ~) %#ok<STOUT>
            assert(false, sprintf('getStatusFromReceiver is not supported in %s', mfilename));
        end
        
        function [receiverImpl, receiverDi] = getReceiverFromStatus(~, ~) %#ok<STOUT>
            assert(false, sprintf('getReceiverFromStatus is not supported in %s', mfilename));
        end
        
        function res = needsCustomFunctionCallHook(~, ~, ~)
            res = false;
        end
    end

    methods (Access = public, Sealed)
        function dataInterfaces = getAdditionalDirectReads(this, task)
            % template method to get additional direct reads that are not
            % directly captured in the component interface
            
            dataInterfaces = this.CodeDescriptorUtils.uniquifyDataInterfaces([ ...
                this.getAdditionalDirectReadsForExportFunction(), ...
                this.getAdditionalDirectReadsForResetTask(task), ...
                this.getAdditionalDirectReadsForOtherTasks(task)]);
        end
    end

    methods (Access = protected)
        function dataInterfaces = getAdditionalDirectReadsForOtherTasks(this, ~)
            dataInterfaces = this.CodeDescriptorUtils.getEmptyInterface('DataInterface');
        end
    end
    
    methods (Access = private)
        function errorForMessageImplementations(this, dataInterfaces, errorId)
            % find data interfaces with message implementations.  Allow the
            % interface to be empty if the interface is variant disabled.
            for i=1:length(dataInterfaces)
                dataInterface = dataInterfaces(i);
                if (isempty(dataInterface.VariantInfo) || ...
                        this.CodeDescriptorUtils.isActiveVariant(dataInterface.VariantInfo)) ...
                        && rtw.connectivity.CodeInfoUtils.isa(dataInterface, 'MessageDataInterface')
                    
                    sidString = this.CodeDescriptorUtils.getDisplayStringFromSID(dataInterface.SID);
                    exception = MSLException(errorId, sidString);
                    exception.throw();
                end
            end            
        end

        function dataInterfaces = getAdditionalDirectReadsForResetTask(this, task)
            % include RESETWITHINIT as inports with this timing
            % mode could be indirectly read by an output task
            % that invokes the reinit function.
            cdUtils = this.CodeDescriptorUtils;
            dataInterfaces = cdUtils.getEmptyInterface('DataInterface');
            indices = cdUtils.getInputPortsForRates(cdUtils.getRates('RESETWITHINIT'));
            if ~isempty(indices) && any(arrayfun(@(x) isequal(task, x.codeInfoData), cdUtils.getOutputTasks()))
                dataInterfaces = cdUtils.getCodeInfo().Inports(indices);
            end
        end

        function dataInterfaces = getAdditionalDirectReadsForExportFunction(this)
            % Imagine an Export function model running in SIL/PIL mode
            % containing the following:
            % 1) A Function call subsystem contains a call to a Simulink
            %    Function
            % 2) The Simulink function has root level I/O 
            % 3) The Simulink function is defined on the target.
            % 
            % When the function call subsystem is run by SIL/PIL, it calls
            % the Simulink function. This is all good. However, who will
            % upload/download the root level I/O for the Simulink function?
            % SIL/PIL normally looks at the DirectReads and DirectWrites of
            % the function call subsystem, but it won't look anywhere else.
            % Ideally, it would be nice if the DirectReads and DirectWrites
            % also capture the reads/writes of the "child/called" Simulink
            % functions. But this is not the case today, so to compensate
            % for this we will also return the reads/writes of all the
            % Simulink functions that are called and defined on the target.
            % This will in some cases, upload/download more stuff than
            % necessary and might degrade performance (?), but will give
            % correct results.
            %
            % Note that we only have to do this for export functions
            % because for periodic scheduling, we already correctly pick 
            % the required reads/writes based on timing properties of the
            % model and the ports.

            cdUtils = this.CodeDescriptorUtils;
            dataInterfaces = cdUtils.getEmptyInterface('DataInterface');
            if cdUtils.isExportFunctions()
                slFcns = cdUtils.getAllDefinedAndCalledSlFcns();
                for slFcnIdx = 1:length(slFcns)
                    dataInterfaces = [dataInterfaces, slFcns(slFcnIdx).DirectReads]; %#ok<AGROW>
                end
            end
        end
    end
end
