classdef CSFunctionInfo < handle & matlab.mixin.Heterogeneous
%CSFUNCTIONINFO: representation for client/server function

% Copyright 2014-2022 The MathWorks, Inc.
properties (Abstract, Constant)
IsSimulinkFunction;
end

properties (GetAccess = private, SetAccess = immutable)
TID = -1;
CodeInfoData = [];
IsAUTOSAR;
RTMTypeIdentifier;

IsLegacyAUTOSARClientBlock; % Always accessed through public getter function
OperationName; % Always accessed through public getter function
end

properties (Access = private)
ArgsDataElements = [];
ArgsDataInterfaces = [];
AreDataElementsInitialized = [];

InArgPositionsInCode = [];
OutArgPositionsInCode = [];
InOutArgPositionsInCode = [];
VoidPointerTypedArgPositionsInCode = [];
ReturnArgPositionInCode = [];
end

properties (Access = protected)
% These properties are set in construction of the sub classes
TargetFunctionName = '';
IsQueued = false;
end

methods (Abstract)
getArgDataInfo(this);
end

methods
function this = CSFunctionInfo(codeInfoData, operationName, RTMTypeIdentifier, tid, isAUTOSAR, isLegacyAutosarOperation)
%validate inputs
assert(isnumeric(tid) && isscalar(tid) && ~isempty(tid),...
'tid has to be numeric and scalar value');

% initialize
this.TID = tid;
this.CodeInfoData = codeInfoData;
this.AreDataElementsInitialized = false;
this.IsAUTOSAR = isAUTOSAR;

this.OperationName = operationName;

this.IsLegacyAUTOSARClientBlock = isLegacyAutosarOperation;
this.RTMTypeIdentifier = RTMTypeIdentifier;

% cache in, out and inout arg positions
codeInfoPrototype = this.CodeInfoData.Prototype;
args = rtw.connectivity.CodeInfoUtils.getArray(codeInfoPrototype.Arguments);
if ~isempty(args)
this.InArgPositionsInCode = find(arrayfun(@(x) strcmp(x.IOType, 'INPUT'), args));
this.OutArgPositionsInCode = find(arrayfun(@(x) strcmp(x.IOType, 'OUTPUT'), args));
this.InOutArgPositionsInCode = find(arrayfun(@(x) strcmp(x.IOType, 'INPUT_OUTPUT'), args));
this.VoidPointerTypedArgPositionsInCode = ...
arrayfun(@(atype)this.isaVoidPointer(atype),[args.Type]);
end

% initialize left-hand side argument position
if ~this.isLegacyAUTOSARClientBlock && ~isempty(codeInfoData.Prototype.Return)
this.ReturnArgPositionInCode = 0;
end
end

% return whether AUTOSAR is active
function isAUTOSAR = getIsAUTOSAR(this)
isAUTOSAR = this.IsAUTOSAR;
end

function opName = getOperationName(this)
opName = this.OperationName;
end

function isArgMultiInstanceRTM = isArgMultiInstanceRTM(this, ~, arg)
%Determine in the multi-instance case, whether the arg is the
%multi-instance RTM.
isArgMultiInstanceRTM = ...
arg.Type.isPointer && ...
~isempty(arg.Type.BaseType) && ...
~isempty(this.RTMTypeIdentifier) && ...
strcmp(arg.Type.BaseType.Identifier, this.RTMTypeIdentifier);
end

function skipSLProcessing = skipSLProcessingForArg(this, arg)
%Determine if the current argument is the RteInstance and
%therefore if we should skip processing it in the Sfunction
skipSLProcessing = this.IsAUTOSAR && ...
strcmp(arg.Type.Identifier, AUTOSAR.CSC.getRTEInstanceType());
end

% return the codeInfo data for the cs function
function codeInfoData = getCodeInfoData(this)
codeInfoData = this.CodeInfoData;
end

% return number of pure in args
function num = getNumInArgs(this)
num = length(this.InArgPositionsInCode);
end

% return number of pure out args
function num = getNumOutArgs(this)
num = length(this.OutArgPositionsInCode);
end

function num = getNumReturnArg(this)
num = length(this.ReturnArgPositionInCode);
end

% return number of inout args
function num = getNumInOutArgs(this)
num = length(this.InOutArgPositionsInCode);
end

function tid = getTID(this)
tid = this.TID;
end

% returns all the data interfaces for the args of this function
function dataInterfaces = getAllDataInterfaces(this)
dataInterfaces = arrayfun(@(x)x.DataInterface, this.ArgsDataInterfaces);
end

% returns all the CodeInfoData interfaces for the args of this function
function dataInterfaces = getAllCodeInfoDataInterfaces(this)
dataInterfaces = arrayfun(@(x)x.CodeInfoData, this.ArgsDataInterfaces);
end

% returns all the data elements for the args of this function
function dataElements = getAllDataElements(this)
dataElements = this.ArgsDataElements;
end

% return back the in args data elements (this includes the inout
% args)
function dataElements = getInputArgsDataElements(this)
argPositions = [this.InArgPositionsInCode ...
this.InOutArgPositionsInCode];
dataElements = this.getDataElementsForArgs(argPositions);
end

% return back the in args CodeInfoData (this includes the inout
% args)
function codeInfoData = getInputArgsDataInterfaces(this)
argPositions = [this.InArgPositionsInCode ...
this.InOutArgPositionsInCode];
codeInfoData = this.getCodeInfoDataInterfacesForArgs(argPositions);
end

% return back the out args data elements (this includes the inout
% args)
function dataElements = getOutputArgsDataElements(this)
argPositions = [this.OutArgPositionsInCode ...
this.InOutArgPositionsInCode ...
this.ReturnArgPositionInCode];
dataElements = this.getDataElementsForArgs(argPositions);
end

% return back the out args CodeInfoData (this includes the inout
% args)
function codeInfoData = getOutputArgsDataInterfaces(this)
argPositions = [this.OutArgPositionsInCode ...
this.InOutArgPositionsInCode ...
this.ReturnArgPositionInCode];
codeInfoData = this.getCodeInfoDataInterfacesForArgs(argPositions);
end

% returns the data interfaces for the args specified by argPositions
function dataInterfaces = getDataInterfacesForArgs(this, argPositions)
lArgsDataInterfaces = this.getArgsDataInterfacesForArgs(argPositions);
dataInterfaces = [lArgsDataInterfaces.DataInterface];
end

% returns the CodeInfo interfaces for the args specified by argPositions
function codeInfoData = getCodeInfoDataInterfacesForArgs(this, argPositions)
lArgsDataInterfaces = this.getArgsDataInterfacesForArgs(argPositions);
codeInfoData = [lArgsDataInterfaces.CodeInfoData];
end

% returns the data elements for the args specified by argPositions
function dataElements = getDataElementsForArgs(this, argPositions)
% This method always returns the elements in their original
% order from ArgsDataInterface.
%
% argPositions does NOT need to be sorted.
if isempty(this.ArgsDataElements)
dataElements = [];
else
lCodeArgPositions = arrayfun(@(x)x.getDataInterface.getCodeArgPosition, this.ArgsDataElements);
bFoundArgPosition = ismember(lCodeArgPositions, argPositions);
dataElements = this.ArgsDataElements(bFoundArgPosition);
end
end

% Given the position of this arg in code, return the position of
% this arg in the Simulink block prototype
function slArgPosition = getSlArgPosition(this, codeArgPosition)
dataElements = this.getDataElementsForArgs(codeArgPosition);
% All the data elements for this arg will share the same
% ArgIndex, so pick the first one
slArgPosition = dataElements(1).getDataInterface.getSlArgPosition;
end

function codeInfoArgs = getAllCodeInfoArgs(this)
codeInfoPrototype = this.getCodeInfoData.Prototype;
if this.isLegacyAUTOSARClientBlock
codeInfoArgs = rtw.connectivity.CodeInfoUtils.getArray(codeInfoPrototype.Arguments);
else
codeInfoArgs = [rtw.connectivity.CodeInfoUtils.getArray(codeInfoPrototype.Arguments), codeInfoPrototype.Return];
end
end

function isReturnArg = isReturnArg(this, codeInfoArg)
if this.isLegacyAUTOSARClientBlock
isReturnArg = false;
else
isReturnArg = isequal(this.getCodeInfoData.Prototype.Return, codeInfoArg);
end
end

function [hasReturnArg,codeInfoData] = hasReturnArg(this)
if this.isLegacyAUTOSARClientBlock
codeInfoData = [];
else
codeInfoData = this.getCodeInfoData.Prototype.Return;
end
hasReturnArg = ~isempty(codeInfoData);
end

% Given a codeInfo description of an argument, returns its position
% in the function prototype in code
function codeArgPosition = getCodeArgPosition(this, codeInfoArg)
codeArgPosition = [];
if this.isReturnArg(codeInfoArg)
codeArgPosition = 0;
else
args = rtw.connectivity.CodeInfoUtils.getArray(this.getCodeInfoData.Prototype.Arguments);
for codeInfoArgIdx = 1:length(args)
if isequal(codeInfoArg, args(codeInfoArgIdx))
codeArgPosition = codeInfoArgIdx;
break;
end
end
end
assert(~isempty(codeArgPosition),...
'could not find argument "%s" in codeInfo!', codeInfoArg.Name);
end

function isVoidPtr = isVoidPointerTypedArg(this,codeArgPosition)
if codeArgPosition>0
assert(codeArgPosition<=numel(this.VoidPointerTypedArgPositionsInCode));
isVoidPtr = this.VoidPointerTypedArgPositionsInCode(codeArgPosition);
else
% Return arg type is never void*
isVoidPtr = false;
end
end

function createDataElementsForIOArgs(this, silPILWrapperUtils)
if ~this.AreDataElementsInitialized
[this.ArgsDataElements, this.ArgsDataInterfaces] =...
rtw.pil.FunctionArgData.createDataElementsForIOArgs(...
this, silPILWrapperUtils);

this.AreDataElementsInitialized = true;
end
end

function targetFunctionName = getTargetFunctionName(this)
targetFunctionName = this.TargetFunctionName;
end

function isLegacy = isLegacyAUTOSARClientBlock(this)
isLegacy = this.IsLegacyAUTOSARClientBlock;
end

% return argument name as specified in CodeInfo
function argName = getCodeInfoArgumentName(this, codeInfoArg)
argName = codeInfoArg.Name;
if isempty(argName)
% workaround g1084776
assert(this.isReturnArg(codeInfoArg), ...
'argument name is empty in codeInfo!');
argName = 'ReturnArg';
end
end

function isQueued = isQueued(this)
isQueued = this.IsQueued;
end
end

methods (Access = private)
function argsDataInterfaces = getArgsDataInterfacesForArgs(this, argPositions)
% This method always returns the interfaces in their original
% order from ArgsDataInterface.
%
% argPositions does NOT need to be sorted.
if isempty(this.ArgsDataInterfaces)
% this.ArgsDataInterfaces could be an empty [], in which
% case we would not be able to dereference the
% CodeArgPosition.
%
% Return an empty struct with the necessary fields, so we
% don't need to repeat this check.
argsDataInterfaces = struct('DataInterface', {}, 'CodeInfoData', {});
else
bFoundArgPosition = ismember([this.ArgsDataInterfaces.CodeArgPosition], argPositions);
argsDataInterfaces = this.ArgsDataInterfaces(bFoundArgPosition);
end
end
end

methods (Access = private, Static)
function isVoidPtr = isaVoidPointer(aEmbeddedType)
isVoidPtr = ~isempty(aEmbeddedType) && aEmbeddedType.isPointer && ...
~isempty(aEmbeddedType.BaseType) && aEmbeddedType.BaseType.isVoid;
end
end
end