classdef (Hidden = true) TargetInterfaceStrategy < handle
    %TARGETINTERFACESTRATEGY provides TargetInterface 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)
        SILPILWrapperUtils
        TargetInterface rtw.pil.TargetInterface
    end
    
    methods
        function this = TargetInterfaceStrategy(silpilWrapperUtils)
            arguments
                silpilWrapperUtils (1,1) rtw.pil.SILPILWrapperUtils
            end
            this.SILPILWrapperUtils = silpilWrapperUtils;
        end
        
        function initialize(this, targetInterface)
            this.TargetInterface = targetInterface;
            this.initializeHook();
        end
        
        function expression = getExpression(this, implementation)
            switch rtw.connectivity.CodeInfoUtils.class(implementation)
                case 'BasicAccessFunctionExpression'
                    ioAccessMode = implementation.IOAccessMode;
                    switch ioAccessMode
                        case {'ByReference', 'BY_REFERENCE', ...
                                'ByValue', 'BY_VALUE','ByReferencePass', 'BY_POINTERPASS'}
                            expression = rtw.pil.AccessFunctions.getExpression(implementation);
                        otherwise
                            assert(false, 'Only ByReference or ByValue access mode are supported');
                    end
                case 'StructAccessorVariable'
                    % In reference to g2682441, getExpression() API from
                    % C++ code will return containing object which will
                    % cause an error when running SIL verification. This
                    % case will ensure that the correct output is returned
                    % to xil_interface.cpp
                    expression = implementation.Identifier;
                case 'StructExpression'
                    if isa(implementation.BaseRegion,'coder.descriptor.StructAccessorVariable')
                        expression = append(this.getExpression(implementation.BaseRegion), '.', implementation.ElementIdentifier);
                    else
                        expression = this.getExpressionInternal(implementation);
                    end
                otherwise
                    expression = this.getExpressionInternal(implementation);
            end
        end
                
        function assignment = getRTMPointerAssignment(~)
            assignment = '';
        end
        
        function indices = getInportIndicesForStorage(this)
            
            % by default, process all Inports
            % may be overridden in subclass to change behavior
            indices = 1:length(this.getCodeDescriptorUtils().getCodeInfo().Inports);
        end

        function indices = getOutportIndicesForStorage(this)
            % by default, process all Outports
            % may be overridden in subclass to change behavior
            indices = 1:length(this.getCodeDescriptorUtils().getCodeInfo().Outports);
        end
        
        function callerFunctionsInfo = getCallerFunctionsInfo(this)
            
            % return the caller functions which are not defined on the
            % target. SIL/PIL needs to provide the implementation by going
            % back to Simulink and requesting the service
            callerFunctionsInfo = this.getCodeDescriptorUtils().getUndefinedCallerFunctionsInfo();
        end
        
        function writeSectionCallerFunctionBody( ...
                ~, writer, isExecutionProfilingEnabled, callerFunctionInfo)
            if isExecutionProfilingEnabled
                writer.wComment('freeze execution profiling timer during the call to Simulink');
                writer.writeLine('xilProfilingTimerFreeze();');
            end
            
            % execute function
            writer.wBlockStart('');
            writer.writeLine('uint32_T xilFcnId = 0;');
            writer.writeLine('uint32_T xilCommandIdx = %d;', callerFunctionInfo.getTID);
            writer.writeLine('uint32_T serverFcnId = %d;', callerFunctionInfo.getFcnId);
            writer.writeLine(['xilCommandDispatchAndResponse(XIL_CLIENT_SERVER_COMMAND,'...
                'xilCommandIdx, xilFcnId, serverFcnId, XIL_RESPONSE_CS_REQUEST_SERVICE, xilGetCurrentProcessMsgContext());']);
            writer.wBlockEnd(); % if xilCommandDispatchAndResponse
            
            if isExecutionProfilingEnabled
                writer.wComment('unfreeze execution profiling timer');
                writer.writeLine('xilProfilingTimerUnFreeze();');
            end
        end
        
        function writeSectionIncludesHook(~, ~)
            % nothing to do
        end
        
        function writeSectionExternalInitialization(~, ~)
            % nothing to do
        end
        
        function [inputType, baseInputType] = getCallerInputArgType(...
                ~, argInfo, ~, argCData)
            % determine the caller input argument type we will use
            if argCData.isStructElement
                baseInputType = argCData.getStructArrayTopLevelInfo;
            else
                baseInputType = argCData.getType.Identifier;
            end
            
            % determine whether we need pointer "*" in this input argument
            % type's expression. The default rules are:
            %
            %  Scalar inputs are passed by value
            %  Complex inputs are passed by value
            %  Enum inputs are passed by value
            %
            %  Array inputs are passed using expression "T a[N]"
            %
            %  Bus Inputs are passed by pointer
            if argInfo.IsBus && ~argInfo.IsVector
                inputType = [baseInputType '*'];
            elseif argInfo.Implementation.Type.isPointer
                % Follow Function Prototype Control type
                inputType = [baseInputType '*'];
            else
                inputType = baseInputType;
            end
            
            % Add const qualifier
            % determine whether we need the const qualifier for this caller
            % argument
            if (argInfo.IsInArg)
                argImplType = argInfo.Implementation.Type;
                if (argInfo.IsVector || argInfo.IsBus)
                    inputType = ['const ' inputType];
                else
                    if argImplType.isPointer
                        if argImplType.BaseType.ReadOnly
                            inputType = ['const ' inputType];
                        end
                    elseif argImplType.ReadOnly
                        inputType = ['const ' inputType];
                    end
                end
                
                if argImplType.isPointer && argImplType.ReadOnly
                    inputType = [inputType 'const'];
                end
            end
        end
        
        function [outputType, baseOutputType] = getCallerOutputArgType( ...
                ~, argInfo, ~, argCData)
            % determine the caller output argument type we will use
            if argCData.isStructElement
                baseOutputType = argCData.getStructArrayTopLevelInfo;
            else
                baseOutputType = argCData.getType.Identifier;
            end
            
            % determine whether we need pointer "*" in this output argument
            % type's expression.
            if argInfo.IsVector
                outputType = baseOutputType;
            else
                outputType = [baseOutputType '*'];
            end
        end
        
        function storageSizeStr = getCallerArgStorageSizeString(~, argInfo)
            
            % return the storage size string for multidimensional signals
            if argInfo.IsVector
                storageSizeStr = ['[' argInfo.NumElements ']'];
            else
                storageSizeStr = '';
            end
        end
        
        function returnArgType = getCallerFunctionReturnArgType(this, callerFunctionInfo)
            [hasReturnArg, codeInfoArg] = callerFunctionInfo.hasReturnArg;
            if hasReturnArg
                codeArgPosition = callerFunctionInfo.getCodeArgPosition(codeInfoArg);
                argInfo = this.TargetInterface.getCallerArgInfo(callerFunctionInfo, codeInfoArg, codeArgPosition);
                returnArgType = argInfo.ArgBaseType;
            else
                % function does not have a return argument
                returnArgType = 'void';
            end
        end
        
        function writeSectionCallerFunctionTrailerReturnArg(this, writer, callerFunctionInfo)
            if callerFunctionInfo.hasReturnArg
                codeInfoPrototype = callerFunctionInfo.getCodeInfoData.Prototype;
                returnArgDataInterface = callerFunctionInfo.getDataInterfacesForArgs(...
                    callerFunctionInfo.getCodeArgPosition(codeInfoPrototype.Return));
                returnArgExpression = this.getExpression(returnArgDataInterface.Implementation);
                writer.wLine('return %s;', returnArgExpression);
            else
                % no return
            end
        end
        
        function hasOnlyLocalAccess = hasOnlyLocalAccess(this, dataInterface)
            % override in subclass
            hasOnlyLocalAccess = this.TargetInterface.hasOnlyLocalAccess(dataInterface);
        end
        
        % return the "base" storage implementation for a root implementation
        function storageImplementation = getBaseStorageImplementation(this, implementation)
            %Call shared implementation for getBaseStorageImplementation in
            %SILPILWrapperUtils.
            storageImplementation = this.SILPILWrapperUtils.getBaseStorageImplementation(implementation);
        end
        
        function isDef = isDefined(this, implementation)
            isDef = implementation.isDefined;
            if ~isDef
                switch rtw.connectivity.CodeInfoUtils.class(implementation)
                    case 'PointerExpression'
                        %For PointerExpressions, codeInfo only returns true for
                        %isDefined if BaseRegion is defined, however we set the
                        %Owner of the Target Region first. See G1044788
                        isDef = implementation.TargetRegion.isDefined;
                    case 'AutosarCalibration'
                        %Needed to support Autosar subcomponent model
                        %block, recursively call isDefined on the
                        %calibration base storage
                        isDef = this.isDefined(this.getBaseStorageImplementation(implementation));
                end
            elseif rtw.connectivity.CodeInfoUtils.isa(implementation, 'PointerVariable')
                %If a PointerVariable is defined, check if also its
                %TargetVariable is defined and mark the whole
                %implementation as not defined if the TargetVariable is not
                isDef = this.isDefined(implementation.TargetVariable);
            end
        end
        
        function setImplementationOwner(~, implementation, owner)
            rtw.pil.OwnerAccessor.setOwner(implementation, owner);
        end
        
        function owner = getImplementationOwner(~, implementation)
            owner = rtw.pil.OwnerAccessor.getOwner(implementation);
        end
        
        function addStaticDataPrefix(this, dataInterface, implementation)
            if this.hasOnlyLocalAccess(dataInterface) && ...
                    ~(rtw.connectivity.CodeInfoUtils.isa(implementation, 'CustomExpression') || ...
                    rtw.connectivity.CodeInfoUtils.isa(implementation, 'ClassMethodExpression') || ...
                    rtw.connectivity.CodeInfoUtils.canGetDefineDataFromOpaqueRegion(implementation)) % Data defined
                % in the opaque region does not use the static prefix. To
                % avoid a missmatch between definition and call sites, we
                % don't modify the implementation identifier for this case.

                % prepend a tag to the identifier to de-risk name
                % clashes with global storage defined by the
                % component
                %
                % Note: we assume that writeSectionStorageForData
                % is called before any other functions that access
                % the Identifier.
                %
                % "xsd" stands for "XIL static data"
                tag = 'xsd_';
                id = implementation.Identifier;
                if isempty(regexp(id, ['^' tag], 'once'))
                    implementation.Identifier = [tag implementation.Identifier];
                end
            end
        end
        
        function validateParamDataInterfaceHook(~, ~)
            % no op
        end
        
        function writeSectionUserFunctions(~, ~)
            % no op
        end

        function [argDataInterface, argDataElement] = getCallerArgumentDataInterfaceAndElement(this, callerFunctionInfo, arg, argPosition)
            % call implementation
            [argDataInterface, argDataElements] = this.getCallerArgumentDataInterfaceAndElementImplementation( ...
                callerFunctionInfo, arg, argPosition);
            % the first data element has all the info we need
            argDataElement = argDataElements(1);
        end

        function res = isInternalDataWithMappingToRTE(~, ~)
            % default implementation
            res = false;
        end

        function refreshRTEInstanceCache(~)
            % no op
        end

    end
    
    methods (Access = protected)
        function cdUtils = getCodeDescriptorUtils(this)
            cdUtils = this.SILPILWrapperUtils.getCodeInfoUtils();
        end
        
        function initializeHook(~)
            % no op, overwritten by subclass
        end

        function [argDataInterface, argDataElements] = getCallerArgumentDataInterfaceAndElementImplementation(~, callerFunctionInfo, ~, argPosition)
            argDataInterface = callerFunctionInfo.getDataInterfacesForArgs(argPosition);
            argDataElements = callerFunctionInfo.getDataElementsForArgs(argPosition);
        end
    end

    methods (Access = private)
        function expression = getExpressionInternal(this, implementation)
            expression = implementation.getExpression();
            % Check if the implementation is a GetSetFunctionAccess
            % or ClassMethodExpression or a structured parameter
            % accessed via ClassMethodExpression
            
            % Check for StructExpressions that have a
            % ClassMethodExpression in their BaseRegion
            nestedClassMethodImplementation = rtw.connectivity.CodeInfoUtils.getNestedImplementation( ...
                implementation, 'ClassMethodExpression');
            % The cache variable for struct parameters must have
            % already been defined at this stage, so we can use
            % that to differentiate them from structures like dwork
            % that are also accessed via method.
            % Access to private dwork elements (e.g. for logging)
            % is read only so the cache variable is not created and
            % no replacement is needed in the expression, see
            % tsil_signal_logging_top_model.m
            isStructuredParameterViaMethod = @() ...
                ~isempty(nestedClassMethodImplementation) && ...
                this.TargetInterface.isGetSetMemoryDefined(nestedClassMethodImplementation);
            if rtw.pil.TargetInterface.isGetSetFunctionAccess(implementation) || ...
                    rtw.connectivity.CodeInfoUtils.isa(implementation,'ClassMethodExpression') || ...
                    isStructuredParameterViaMethod()
                
                if ~isempty(nestedClassMethodImplementation) || rtw.pil.TargetInterface.isGetSetFunctionAccess(implementation)
                    % For get/set we need to strip off trailing round braces to allow it's use as a valid variable.
                    % Additionally, for class methods, need to strip off the model object.
                    % This is needed to transform rtObject.getIn1() into
                    % getIn1 which can be used (with the modification below) as a valid identifier.
                    expression = regexprep(expression,'^[^.]*\.','');
                    
                    % If any, extract X from X() or X and Y from
                    % X()Y and join them into one expression
                    tokens = regexp(expression,'(\w*)\(\)$|(^\w*)\(\)(.*)', 'tokens');
                    if ~isempty(tokens)
                        expression = strjoin(tokens{1}, '');
                    end
                end
                % use a different name to the read / write functions
                expression = ['xsd_' expression];
            end
            % remove the hidden root subsystem, see g1314398
            expression = strrep(expression,'.TmpModelReferenceSubsystem','');
        end
    end 
end
