classdef DataElement < handle
%DATAELEMENT - Contiguous data chunk to be transferred between host and target.

%   Copyright 2012-2020 The MathWorks, Inc.

    properties (SetAccess = 'private', GetAccess = 'private')
        % owning Data Interface
        DataInterface;
        % Coder/Embedded Type
        Type;         
        %
        % Contextual information
        %
        % Index of this DataElement in array owned by DataInterface
        Index; 
        % VarDimIndex of this DataElement in array owned by DataInterface
        VarDimIndex;
        % Array of ancestors representing the path through the type system
        % to this DataElement
        Ancestors; 
        %
        Dimensions;
        NumElements;
        SymbolicNumElements;
        HasSymbolicDims;
        SymbolicDims;
        DTypeId;
        HostByteSize;
        NumInstances;
        SymbolicNumInstances;
        IsStructElement;
        IsGetSetViaPointer;
        IsActive;
        StructArrayTrailerStack;
        IsAccessible;
    end
    
    properties (SetAccess = 'private', GetAccess = 'public')
        SimulinkType;
        HostCType;
        IsEnum;
        ReplacementVar;
    end
    
    properties (SetAccess = 'public', GetAccess = 'public')
        SkipForCommonAncestorProcessing;
        NumPrevSameAncestorElements;
    end
    
    methods
        function this = DataElement(DataInterface, ...
                                    Type, ...
                                    Index, ...
                                    VarDimIndex, ...
                                    Ancestors, ...
                                    ReplacementVar)
            assert(~Type.isStructure, ...
                   'Structure types should have been eliminated by higher level processing');                                
            this.DataInterface = DataInterface;
            this.Type = Type;
            this.Index = Index;
            this.VarDimIndex = VarDimIndex;
            this.Ancestors = Ancestors;
            if nargin==6
                this.ReplacementVar = ReplacementVar;
            end
            this.initDimensions;
            this.initNumElements;
            this.initSymbolicDims;
            this.initNumInstances;
            this.initSymbolicNumInstances;
            this.initIsStructElement;
            this.initIsEnum;            
            this.initIsGetSetViaPointer;
            this.initDtypeId;  
            this.initIsActive;
            this.initIsAccessible;
            this.SkipForCommonAncestorProcessing = false;
            this.NumPrevSameAncestorElements = 0;
            if ~isempty(this.DTypeId)
                % cannot initialize these without DTypeId
                this.HostByteSize = rtw.connectivity.TypeUtils.builtinHostByteSize(this.DTypeId);
                this.SimulinkType = rtw.connectivity.TypeUtils.codeInfoTypeIdToType(this.DTypeId);  
                this.HostCType = rtw.connectivity.TypeUtils.builtinToHostCType(this.DTypeId);
            end            
        end                
        
        function dataInterface = getDataInterface(this)
           dataInterface = this.DataInterface; 
        end
        
        function dtypeId = getDtypeId(this)            
            dtypeId = this.DTypeId;
        end
               
        % return the number of bytes
        function size = getByteSize(this)
            size = this.HostByteSize * this.NumElements;
        end                                            
        
        % return the total byte size of all instances
        function size = getTotalByteSize(this)
           size = this.getByteSize * this.NumInstances; 
        end
        
        % return the total mem unit size of all instances
        function size = getTotalMemUnitSize(this, memUnitBytes)
        % support array of data elements (this)!
            size = 0;
            for i=1:length(this)
                numInstances = this(i).NumInstances;
                size = size + this(i).getMemUnitSize(memUnitBytes) * numInstances;  
            end           
        end
        
        % return the total mem unit symbolic size of all instances
        function symbolicSize = getTotalMemUnitSymbolicSize(this, memUnitBytes)
        % support array of data elements (this)!
            size = 0;
            symbolicSize = '';
            for i=1:length(this)
                unitSize     = this(i).getMemUnitSize(memUnitBytes);
                numInstances = this(i).NumInstances;
                if Inf == unitSize || Inf == numInstances
                    unitSymbolicSize = ...
                        this(i).getMemUnitSymbolicSize(memUnitBytes);
                    symbNumInstances = this(i).SymbolicNumInstances;
                    
                    if strcmp('1', symbNumInstances)
                        if isempty(symbolicSize)
                            symbolicSize = ['(', unitSymbolicSize, ')'];
                        else
                            symbolicSize = sprintf( ...
                                '(%s + %s)', symbolicSize, unitSymbolicSize);
                        end
                    else
                        if isempty(symbolicSize)
                            symbolicSize = sprintf( ...
                                '(%s) * (%s)', ...
                                unitSymbolicSize, symbNumInstances);
                        else
                             symbolicSize = sprintf( ...
                                '(%s + (%s) * (%s))', symbolicSize, ...
                                 unitSymbolicSize, symbNumInstances);
                        end
                    end
                else
                    size = size + (unitSize * numInstances);  
                end
            end
            if isempty(symbolicSize)
                symbolicSize = int2str(size);
            elseif 0 < size
                symbolicSize = sprintf('(%s + %d)', symbolicSize, size);
            end
        end
        
        % return the total number of elements of all instances
        function totalNumElements = getTotalNumElements(this)
           totalNumElements = this.NumElements * this.NumInstances; 
        end
        
        % return the number of elements
        function numElements = getNumElements(this)                                    
            numElements = this.NumElements;            
        end      
        
        % return the symbolic number of elements
        function symbNumElements = getSymbolicNumElements(this)
            symbNumElements = this.SymbolicNumElements;
        end
        
        function symbolicDims = getSymbolicDimensions(this)
            symbolicDims = this.SymbolicDims; 
        end
        
        % return the symbolic number of elements
        function hasSymbolicDims = hasSymbolicDimensions(this)
            hasSymbolicDims = this.HasSymbolicDims;
        end
        
        function numInstances = getNumInstances(this)
            numInstances = this.NumInstances;
        end
        
        function symbNumInstances = getSymbolicNumInstances(this)
            symbNumInstances = this.SymbolicNumInstances;
        end
        
        function dimensions = getDimensions(this)
            dimensions = this.Dimensions;
        end
                
        function isVector = isVector(this)          
            isVector = false;
            % step backwards through ancestors to find any terminal matrixtype
            % associated with this DataElement
            for i=length(this.Ancestors):-1:1
                if this.Ancestors(i).IsStructure
                    % stop at structure boundary
                    break;
                elseif this.Ancestors(i).IsMatrix                    
                    % stop at matrix boundary
                    isVector = this.DataInterface.getClientInterface.isArray(this.Ancestors(i).Dimensions);
                    break;
                end                
            end                                                                                                    
        end                       
                       
        function isStructElement = isStructElement(this)
            isStructElement = this.IsStructElement;            
        end
        % returns accumulated index for variable signal size bus handling
        function index = getVarDimIndex(this)
            index = this.VarDimIndex;
        end                
        
        function isGetSetViaPointer = isGetSetViaPointer(this)
            isGetSetViaPointer = this.IsGetSetViaPointer;            
        end
        
        function isActive = isActive(this)
            isActive = this.IsActive;
        end
        
        function isAccessible = isAccessible(this)
            isAccessible = this.IsAccessible;
        end
        
        function shouldSkipEntry = shouldSkipTargetIOTableEntry(this)
            % flag that the target side IOTable entry is not needed for 
            % this element, data will be directly serialized/deserialized
            % in the buffer
            shouldSkipEntry = this.DataInterface.HasTargetSideOpaqueRegion;
        end
        
        function areAncestorsEqual = areAncestorsEqual(this, dataElement)            
            areAncestorsEqual = false;          
            % Get last structure ancestor index    
            Idx1 = this.getFinalStructAncestorIdx;
            Idx2 = dataElement.getFinalStructAncestorIdx;
            % Consecutive elements check is to ensure that dataElements
            % are the fields of one struct rather than fields of two
            % structs with the same type
           
            if (Idx2==Idx1 && (dataElement.Ancestors(Idx2).ElementIndex - this.Ancestors(Idx1).ElementIndex)==1)
                str1 = '';
                str2 = '';
                for j = 1:length(this.Ancestors)
                    if this.Ancestors(j).IsStructure
                        str1 = [str1, ',', this.Ancestors(j).TypeIdentifier]; %#ok<AGROW>
                        str2 = [str2, ',', dataElement.Ancestors(j).TypeIdentifier]; %#ok<AGROW>
                    end
                end
                
                if isequal(str1, str2) 
                    areAncestorsEqual = true;
                end
            end
        end
                
        % return a string (and optionally the value) representing the 
        % default initialization value for a type
        function [initString, initValue] = getDefaultInitialization(this, silPILWrapperUtils)
            assert(silPILWrapperUtils.getSILPILInterface.getInTheLoopType ~= rtw.pil.InTheLoopType.MATLABFcn, ...
                'getDefaultInitialization should not be called for MATLAB Coder');            
            isBuiltIn64BitAllowed = false;
            
            dataInterface = this.getDataInterface;
            if isequal(dataInterface.getType, dataInterface.getHostType)
                % no type separation; use this DataElement's type
                initializationType = this.Type;
            else   
                % With type separation, only the host data elements have
                % the fixed-point and enum information.
                % (i.e. both fixed-point and enum can be implemented as raw
                % integers in the generated code)
                %
                % Given a target data element, determine a corresponding
                % host type.
                %
                % Given we have made it past validation we currently
                % expect a 1-1 mapping between target and host data elements
                % (i.e. element structure must be the same).
                % In future, we may have to revisit this to support e.g. multi-word
                % fixed-point.
                hostDataElements = dataInterface.getHostDataElements;
                assert(length(dataInterface.getTargetDataElements) == length(hostDataElements), ...
                    'Number of host and target data elements do not match!');
                % get the corresponding host data element and its type
                hostDataElement = hostDataElements(this.Index);
                initializationType = hostDataElement.Type;
            end                       
            isFixedPoint = rtw.connectivity.CodeInfoTypeUtils.isFixedPoint(initializationType, isBuiltIn64BitAllowed);
            isEnum = rtw.connectivity.CodeInfoTypeUtils.isEnumType(initializationType);
            if isEnum
                % enumerated signals cannot be fixed-point
                assert(~isFixedPoint);
                % find the default index
                codeInfoUtils = silPILWrapperUtils.getCodeInfoUtils;
                initStringArray = codeInfoUtils.getArray(initializationType.Strings);
                initString = initStringArray{codeInfoUtils.getDefaultMember(initializationType)}; 
                initString = this.updateEnumStringForAutosar(this.DataInterface.getRTWDataInterface, initString);
                initValueArray = codeInfoUtils.getArray(initializationType.Values);
                initValue = initValueArray(codeInfoUtils.getDefaultMember(initializationType)); 
            else
                if isFixedPoint
                    % if we encounter fixed point types then there will
                    % be licenses available for the fixed point products
                    % (e.g. "fi" objects)
                    %
                    % calculate the stored value that represents real world
                    % value 0 using a "fi" object
                    initValue = 0;
                    signed =  initializationType.Signedness;
                    wordLength = initializationType.WordLength;
                    %
                    % Slope = SlopeAdjustmentFactor * 2^FixedExponent
                    %
                    slope = initializationType.Slope;
                    bias = initializationType.Bias;
                    fiObj = fi(initValue, signed, wordLength, slope, bias);
                    % get the stored integer value
                    initStoredValue = fiObj.int;
                    initString = int2str(initStoredValue);
                    initValue = initStoredValue;
                    if ~signed
                        % mark as an unsigned constant
                        initString = [initString 'U'];
                    end
                else
                    % regular data is initialized with 0
                    initString = '0';
                    initValue = 0;
                end
            end
        end
        
        % perform validation
        function validate(this, silPILWrapperUtils)
            if ~this.DataInterface.getSkipDataElementValidation            
                % check for opaque types at component boundary
                this.validateNoOpaqueTypes(silPILWrapperUtils);
                
                % Don't run subsequent validations if an earlier one has
                % already failed.
                if silPILWrapperUtils.ComponentBoundaryValidation.IsValid
                    % check for > 32/64-bit fixed-point I/O
                    this.validateFixedPointIOSize(silPILWrapperUtils);
                end
                
                % Need to re-test "IsValid", since it may have changed in
                % the previous validation call.
                if silPILWrapperUtils.ComponentBoundaryValidation.IsValid
                    % validate host / target data size
                    this.validateHostTargetDatatypeSize(silPILWrapperUtils);
                else
                    % Default value used if validation fails
                    this.SimulinkType = '';
                end
            end
        end                                     
        
        function type = getType(this)
           type = this.Type; 
        end                
                
        % returns index of this contiguous data chunk within set of all
        % chunks of the associated Data Interface
        function index = getIndex(this)
            index = this.Index;
        end       
        
        % get an expression for an element of assignment transfer data
        %
        % index element is only used for vector expressions
        function expression = getAssignmentDataElementExpression(this, identifier, index)
            if this.NumInstances == 1
                instancesStr = '';
            else
                instancesStr = '[instanceIdx]';
            end
            
            storageSize = this.NumElements;
            if storageSize > 1
                if ischar(index)
                    indexStr = sprintf('[ %s ]', index);
                else
                    indexStr = sprintf('[ %d ]', index);
                end
            else
                indexStr = '';
            end
            expression = [identifier instancesStr indexStr];
        end             
        
        % open loops required for accessing all instances of a
        % struct element within a struct array
        function writeStructArrayHeader(this, writer)                                    
            writer.wBlockStart('');           
            if ~strcmp(this.SymbolicNumInstances, '1')
                % maintain a linear iteration index
                writer.writeLine('uint32_T instanceIdx = 0;');
            end
            % record operations required in the trailer code
            this.StructArrayTrailerStack = {};
            level = 0;
            % process ancestors - open for loops
            loopSize = '1';
            isMatrix = false;
            for ancestorIdx=1:length(this.Ancestors)
                if this.Ancestors(ancestorIdx).IsStructure
                    if isMatrix
                        levelInstanceIndex = ['i' num2str(level)];
                        writer.writeLine(['uint32_T ' levelInstanceIndex ' = 0;']);
                        if this.DataInterface.DimsPreserved
                           % loop over each dimension
                           for dimIdx = 1:length(symbolicDims)
                               loopIndex = [levelInstanceIndex '_' num2str(dimIdx-1)];
                               writer.writeLine(['uint32_T ' loopIndex ';']);
                               writer.wBlockStart(['for (' ...
                                   loopIndex ' = 0; ' ...
                                   loopIndex ' < ' symbolicDims{dimIdx} '; ' ...
                                   loopIndex '++)']);                                 
                           end                                         
                           for dimIdx = 1:length(symbolicDims)
                               % close loops in the trailer code
                               this.StructArrayTrailerStack{end+1} = @()writer.wBlockEnd;
                           end
                           % increment the levelInstanceIndex at end of
                           % inner loop in the trailer code
                           this.StructArrayTrailerStack{end+1} = @()writer.wLine('%s++;', levelInstanceIndex);
                        else                        
                            writer.wBlockStart(['for (' ...
                                levelInstanceIndex ' = 0; ' ...
                                levelInstanceIndex ' < ' loopSize '; ' ...
                                levelInstanceIndex '++)']);              
                            % close loop in the trailer code
                            this.StructArrayTrailerStack{end+1} = @()writer.wBlockEnd;
                        end  
                        level = level + 1;
                        
                        % reset
                        loopSize = '1';                        
                        isMatrix = false;
                        symbolicDims = '';
                    end
                elseif this.Ancestors(ancestorIdx).IsMatrix
                    assert(strcmp(loopSize,'1'), 'loopSize must equal 1.');
                    assert(~isMatrix, 'isMatrix must not already be set');
                    loopSize = this.Ancestors(ancestorIdx).SymbolicNumElements;
                    dims = this.Ancestors(ancestorIdx).Dimensions;
                    symbolicDims = this.Ancestors(ancestorIdx).SymbolicDims;
                    isMatrix = this.DataInterface.getClientInterface.isArray(dims);
                elseif ancestorIdx == 1 && ...
                        this.Ancestors(ancestorIdx).IsPointer
                    % For top level pointers, extract dimensions from
                    % "Host" Type (rather than Implementation Type) to
                    % support pointer access to arrays.
                    assert(strcmp(loopSize,'1'), 'loopSize must equal 1.');
                    assert(~isMatrix, 'isMatrix must not already be set');
                    
                    hostType = this.DataInterface.getHostType;
                    if hostType.isMatrix
                        loopSize = hostType.SymbolicWidth;
                        dims = rtw.connectivity.CodeInfoUtils.getArray(hostType.Dimensions);
                        % pointer access to array uses flat dimensions
                        symbolicDims = {hostType.SymbolicWidth};
                        isMatrix = this.DataInterface.getClientInterface.isArray(dims);
                    end
                end
            end
        end
        
        % close loops required for accessing all instances of a
        % struct element within a struct array
        function writeStructArrayTrailer(this, writer)                       
            if this.NumInstances > 1
                % maintain a linear iteration index
                % increment deep in the loop body
                writer.writeLine('instanceIdx++;');
            end           
            % process the trailer stack (in reverse)
            for i=length(this.StructArrayTrailerStack):-1:1
                % evaluate the entry
                feval(this.StructArrayTrailerStack{i});
            end
            % clear the stack
            this.StructArrayTrailerStack = {};                        
            writer.wBlockEnd;
        end   
        
        % open loops for iterating over individual elements of a DataElement
        function [elementIndexVariables, linearIndexVariable] = writeElementLoopHeader(this, writer)
            storageSize = this.getNumElements;
            if storageSize > 1
                linearIndexVariable = 'xilLinearIdx';
                writer.wLine('uint32_T %s = 0;', linearIndexVariable);
                if this.DataInterface.DimsPreserved
                    % loop over each dimension
                    symbolicDims = this.getSymbolicDimensions;
                    numDims = length(symbolicDims);
                    elementIndexVariables = cell(1, numDims);
                    for dimIdx = 1:numDims
                        loopIndex = ['elementIdx_' num2str(dimIdx-1)];
                        writer.writeLine('uint32_T %s;', loopIndex);
                        writer.wBlockStart('for (%s = 0; %s < %s; %s++)', ...
                            loopIndex, ...
                            loopIndex, ...
                            symbolicDims{dimIdx}, ...
                            loopIndex);
                        elementIndexVariables{dimIdx} = loopIndex;
                    end
                else
                    symbSorageSize =  this.getSymbolicNumElements;
                    writer.wBlockStart( ...
                        'for (%s = 0; %s < %s; %s++)', ...
                        linearIndexVariable, linearIndexVariable, symbSorageSize, linearIndexVariable);
                    elementIndexVariables = linearIndexVariable;
                end
            else
                elementIndexVariables = '0';
                linearIndexVariable = '0'; 
            end
        end
        
        % close loops associated with writeElementLoopHeader
        function writeElementLoopTrailer(this, writer)
            storageSize = this.getNumElements;
            if storageSize > 1
                if this.DataInterface.DimsPreserved
                    % increment the linear index
                    linearIndexVariable = 'xilLinearIdx';
                    writer.wLine('%s++;', linearIndexVariable);
                    % close loops
                    symbolicDims = this.getSymbolicDimensions;
                    numDims = length(symbolicDims);
                    for dimIdx = 1:numDims
                        writer.wBlockEnd;
                    end
                else
                    % close loop
                    writer.wBlockEnd;
                end
            end
        end
        
        % get final struct element offset
        function [Offset, TypeIdentifier, IsComplex] = getFinalStructElementOffset(this)
            AncestorIdx = this.getFinalStructAncestorIdx;
            Offset = this.Ancestors(AncestorIdx).ElementIndex - this.NumPrevSameAncestorElements - 1;	
            TypeIdentifier = this.Ancestors(AncestorIdx).TypeIdentifier;
            IsComplex = this.Ancestors(AncestorIdx).IsComplex;
        end
        
        % Returns either scalar 0 or, if ND indexing is enabled, an 
        % array with N 0 elements where N is the number of dimensions of a
        % matrix type.
        %
        % The return value is suitable for the 'index' argument of
        % getElementExpression.
        function indices = getFirstElementIndices(this)
            if this.isVector && this.DataInterface.DimsPreserved
                symbolicDims = this.getSymbolicDimensions;
                indices = zeros(1, length(symbolicDims));                
            else
                indices = 0;
            end
        end
                        
        % returns an expression for accessing all instances of a struct
        % element within a struct array 
        %
        % index argument is only used for vector or array expressions
        function [expression, level] = getElementExpression(this, index, isPointerTypeSupported, isLeafPointerAllowed)
            if nargin == 2
               % isPointerTypeSupported enables special behaviour for MATLAB
               % Coder that is currently disabled
               isPointerTypeSupported = false;
               isLeafPointerAllowed = false;
            end            
            assert(nargin ~= 3, 'Must supply 4Th Argument (isLeafPointerAllowed)');            
            expression = '';            
            level = 0;
            % process ancestors
            isPointer = false;
            isMatrix = false;
            for ancestorIdx=1:length(this.Ancestors)
               if this.Ancestors(ancestorIdx).IsStructure
                   if isMatrix                       
                       levelInstanceIndex = ['i' num2str(level)];
                       if this.DataInterface.DimsPreserved
                           % loop over each dimension
                           for dimIdx = 1:length(symbolicDims)
                               loopIndex = [levelInstanceIndex '_' num2str(dimIdx-1)];
                               expression =  [expression '[' loopIndex ']'];  %#ok<AGROW>
                           end
                       else
                          expression =  [expression '[' levelInstanceIndex ']'];  %#ok<AGROW>
                       end
                       level = level + 1;
                       
                       % matrix access / pointer arithmetic complete
                       isPointer = false;
                       isMatrix = false;
                       symbolicDims = '';
                   end
                   if isPointer    
                      expression = [expression '->'];  %#ok<AGROW>
                      % pointer has been dereferenced
                      isPointer = false;
                   else
                      expression = [expression '.'];  %#ok<AGROW>
                   end
                   expression = [expression this.Ancestors(ancestorIdx).ElementIdentifier]; %#ok<AGROW>                   
               elseif this.Ancestors(ancestorIdx).IsMatrix
                   assert(~isMatrix, 'isMatrix must not already be set');
                   isArrSizeOne = rtw.pil.TargetInterface.isArrSizeOne(...
                       this.DataInterface.getRTWDataInterface.Implementation.Type);
                   isMatrix = isArrSizeOne || this.DataInterface.getClientInterface.isArray(...
                       this.Ancestors(ancestorIdx).Dimensions);
                   symbolicDims = this.Ancestors(ancestorIdx).SymbolicDims;
               elseif this.Ancestors(ancestorIdx).IsPointer                         
                   assert(~isPointer, 'isPointer must not already be set');
                   assert(isPointerTypeSupported, 'isPointerTypeSupported must be set.');
                   
                   % pointer must be dereferenced
                   isPointer = true;
                   
                   if ancestorIdx==1 && ~this.Ancestors(ancestorIdx).IsMatrix     
                       hostType = this.DataInterface.getHostType;
                       isMatrix = isprop(hostType,'Dimensions') && ...
                           this.DataInterface.getClientInterface.isArray(rtw.connectivity.CodeInfoUtils.getArray(hostType.Dimensions));
                       if isMatrix
                           % pointer access to array uses flat dimensions
                           symbolicDims = {hostType.SymbolicWidth};
                       else
                           % If top level element isn't really a matrix (as it only has 1 element),
                           % then reset isPointer flag.
                           isPointer = false;
                       end                       
                   end
               else
                   assert(false, 'Unexpected type of ancestor!');                   
               end                
            end     
            % complete the expression by processing any final matrix / 
            % pointer access
            if isMatrix  
                assert(~isempty(index), ...
                       'Index must be provided for matrix expression.');                
                % add index
                if this.DataInterface.DimsPreserved && ~isPointer
                    % index must be a cell array with an index for each
                    % dimension
                    numDims = length(this.getSymbolicDimensions);                    
                    assert(iscell(index) || isnumeric(index), ...
                        'Index must be a cell array or a numeric array');
                    assert(length(index) == numDims, ...
                        'Index array must have an index for each dimension');
                    indexStr = '';
                    for dimIdx = 1:length(this.getSymbolicDimensions)
                        if iscell(index)
                            currIndex = index{dimIdx};    
                        else
                            currIndex = index(dimIdx);
                        end          
                        if isnumeric(currIndex)
                            currIndexStr = int2str(currIndex);
                        else
                            currIndexStr = currIndex;
                        end                
                        indexStr = sprintf('%s[%s]', indexStr, currIndexStr);
                    end
                else
                    assert(~iscell(index), 'Index should not be a cell array.');
                    if isnumeric(index)
                        assert(isscalar(index), 'Index should be scalar.');
                        indexStr = int2str(index);
                    else
                        indexStr = index;
                    end                
                    indexStr = sprintf('[%s]', indexStr);
                end
                expression = [expression indexStr];
                % matrix / pointer arithmetic complete                
                isPointer = false;     
            else
                assert(~iscell(index), 'Index should not be a cell array.');
                assert((isnumeric(index) && (index == 0)) || (ischar(index) && index == '0'), ...
                       'Index must be 0 for non-matrix expression.');
            end
            
            if ~isLeafPointerAllowed
                % For now, assume we don't finish with a pointer!
                %
                % If this ever changes, we will have to export the isPointer
                % status and dereference appropriately.
                assert(~isPointer, 'getElementExpression cannot end with a pointer.');
            end
        end    
        
        % utility function to determine if this DataElement is a complex
        % real or imaginary element
        function [isComplex, ...
                  isReal, ...
                  symbolicDimensions] = getComplexInfo(this)
            isComplex = false;
            isReal = [];
            symbolicDimensions = [];
                                 
            % step backwards through ancestors to find any terminal matrixtype
            % associated with this DataElement                                    
            for i=length(this.Ancestors):-1:1
                if this.Ancestors(i).IsStructure
                    %Determine if current ancestor is complex
                    isAncestorComplex = this.Ancestors(i).IsComplex;
                    
                    if isAncestorComplex
                        %Latch isComplex output to true, once we've
                        %encountered any complex elements.
                        %NOTE: We can't have a complex element parented by
                        %another complex element
                        isComplex = true;
                        % base type has dimensions 1
                        symbolicDimensions = {'1'};
                    end
                    
                    if isAncestorComplex
                        switch this.Ancestors(i).ElementIndex
                            case 1
                                isReal = true;
                            case 2
                                isReal = false;
                            otherwise
                                assert(false, 'Unexpected ElementIndex: %d', this.Ancestors(i).ElementIndex);
                        end
                    else
                        % stop at non-complex structure boundary
                        break;
                    end                                                            
                elseif this.Ancestors(i).IsMatrix
                    if isComplex
                        symbolicDimensions = this.Ancestors(i).SymbolicDims;
                    end
                    % stop at matrix boundary
                    break;
                end                
            end  
        end                     
        
        % returns a byte offset for accessing all instances of a
        % struct element within a struct array
        function structArrayOffset = getStructArrayOffset(this, isHost)
            assert(this.IsStructElement, ...
                  'getStructArrayOffset can only be called for struct elements.');            
            level = 0;                        
            simDataCharPtrLoopOffset = '';
            simDataCharPtrInstanceOffset = '';
            isMatrix = false;
            for ancestorIdx=1:length(this.Ancestors)
                if this.Ancestors(ancestorIdx).IsStructure
                    isComplex = this.Ancestors(ancestorIdx).IsComplex;                    
                    if (isHost)
                        %Host Simulink
                        elementOffset = this.Ancestors(ancestorIdx).ElementIndex - 1;
                        if isComplex
                            % no padding of complex on the host
                            hostByteSize = this.HostByteSize * this.NumElements;
                            if ancestorIdx==this.getFinalStructAncestorIdx
                                elementOffset = ['offsetIdx*', num2str(hostByteSize)];
                            else
                                elementOffset = num2str(elementOffset * hostByteSize);
                            end
                            simDataCharPtrInstanceOffset = [simDataCharPtrInstanceOffset ...
                                ' + ' elementOffset];  %#ok<AGROW>
                        else
                            if ancestorIdx==this.getFinalStructAncestorIdx
                                elementOffset = 'offsetIdx';
                            else
                                elementOffset = num2str(elementOffset);
                            end
                            simDataCharPtrInstanceOffset = [simDataCharPtrInstanceOffset ...
                                ' +' newline '   dtaGetDataTypeElementOffset(' ...
                                'dta, bpath, ssGetDataTypeId(S, "' ...
                                this.Ancestors(ancestorIdx).TypeIdentifier ...
                                '"), ' elementOffset ...
                                ')']; %#ok<AGROW>
                        end
                    else
                        %Target
                        simDataCharPtrInstanceOffset = [simDataCharPtrInstanceOffset ...
                                ' + offsetof(' this.Ancestors(ancestorIdx).TypeIdentifier ...
                                ', ' this.Ancestors(ancestorIdx).ElementIdentifier ')']; %#ok<AGROW>
                    end
                    if isMatrix
                        % build offset strings
                        if (isHost)
                            %Simulink Host
                            if isComplex
                                simDataCharPtrLoopOffset = sprintf('%s + 2*i%d*%d', ...
                                    simDataCharPtrLoopOffset, ...
                                    level, ...
                                    hostByteSize);
                            else
                                simDataCharPtrLoopOffset = sprintf(['%s +\n   i%d*dtaGetDataTypeSize(' ...
                                    'dta, bpath, ssGetDataTypeId(S, "%s"))'], ...
                                    simDataCharPtrLoopOffset, ...
                                    level, ...
                                    this.Ancestors(ancestorIdx).TypeIdentifier);
                            end

                        else
                            %Target
                            simDataCharPtrLoopOffset = sprintf('%s + i%d*sizeof(%s)', ...
                                    simDataCharPtrLoopOffset, ...
                                    level, ...
                                    this.Ancestors(ancestorIdx).TypeIdentifier);                                                        
                        end
                        level = level + 1;
                        % reset
                        isMatrix = false;
                    end
                elseif this.Ancestors(ancestorIdx).IsMatrix
                    assert(~isMatrix, 'isMatrix must not already be set');
                    dims = this.Ancestors(ancestorIdx).Dimensions;
                    isMatrix = this.DataInterface.getClientInterface.isArray(dims);
                elseif ancestorIdx==1 && this.Ancestors(ancestorIdx).IsPointer
                    % if it is a top-level pointer argument, it might also be
                    % a matrix. Check the dimension on the Type
                    isMatrix = isprop(this.DataInterface.getHostType,'Dimensions') && ...
                        this.DataInterface.getClientInterface.isArray( ...
                        rtw.connectivity.CodeInfoUtils.getArray( ...
                            this.DataInterface.getHostType.Dimensions));
                end
            end                                      
            structArrayOffset = [simDataCharPtrLoopOffset simDataCharPtrInstanceOffset];
        end
        
        % returns information about the top-level of the struct array that
        % this struct element is part of
        function [typeIdentifier, numElements, hostElementSize] = getStructArrayTopLevelInfo(this)
            assert(this.IsStructElement, ...
                  'getStructArrayTopLevelInfo can only be called for struct elements.');                       
            % process ancestors
            loopSize.Numerical = 1;
            loopSize.Symbolic = '1';
            loopSize.SymbolicDims = {'1'};
            for ancestorIdx=1:length(this.Ancestors)
                if this.Ancestors(ancestorIdx).IsStructure
                    isComplex = this.Ancestors(ancestorIdx).IsComplex;
                    typeIdentifier = this.Ancestors(ancestorIdx).TypeIdentifier;
                    if isComplex
                        % no padding of complex on the host - multiply by two
                        % to handle both real and imaginary elements
                        hostElementSize.str = int2str(2 * this.HostByteSize);
                        hostElementSize.needsDTA = false;
                    else
                        hostElementSize.str = ['dtaGetDataTypeSize(dta, bpath, ssGetDataTypeId(S, "' ...
                            typeIdentifier '"))'];
                        hostElementSize.needsDTA = true;
                    end
                    % handle arrays
                    numElements = loopSize;
                    % only need to process top-level structure - we're done
                    break;
                elseif this.Ancestors(ancestorIdx).IsMatrix
                    assert(loopSize.Numerical == 1, 'loopSize must equal 1.');
                    loopSize.Symbolic = this.Ancestors(ancestorIdx).SymbolicNumElements;
                    loopSize.SymbolicDims = this.Ancestors(ancestorIdx).SymbolicDims;
                    loopSize.Numerical = prod(this.Ancestors(ancestorIdx).Dimensions);                    
                end
            end                                     
        end                
    end          
    
    methods (Access = 'private') 
        % return the number of target memory units per element
        function numMemUnits = getNumMemUnits(this, memUnitBytes)
            numMemUnits = this.HostByteSize;
            % convert to memory units if required
            if memUnitBytes > 1
                if numMemUnits > memUnitBytes
                    % make sure that datatype size is an exact multiple of the wordlength
                    if mod(numMemUnits, memUnitBytes) ~= 0
                        silPILWrapperUtils.ComponentBoundaryValidation.validationError('pil', ...
                            'MultiWordDataTypeNonWordSized', ...
                            this.SimulinkType, ...
                            int2str(memUnitBytes));
                    end
                    numMemUnits = numMemUnits / memUnitBytes;
                else
                    % datatype fits into a single word
                    numMemUnits = 1;
                end
            end
        end
        
        % return the number of target memory units
        function size = getMemUnitSize(this, memUnitBytes)
            numMemUnits = this.getNumMemUnits(memUnitBytes);
            size = numMemUnits * this.NumElements;
        end
        
        % return the symbolic number of target memory units
        function symbolicSize = getMemUnitSymbolicSize(this, memUnitBytes)
            numMemUnits = this.getNumMemUnits(memUnitBytes);
            if 1 == numMemUnits
                symbolicSize = this.SymbolicNumElements;
            else
                symbolicSize = ...
                    sprintf('%d * (%s)', numMemUnits, this.SymbolicNumElements);
            end
        end
        
        function validateHostTargetDatatypeSize(this, silPILWrapperUtils)
            % (we assume that...) if the Simulink stored value datatype
            % (e.g. int8, int16, int32, double) used to represent a
            % datainterface exists on the target then
            % the generated code will ALWAYS use that type to represent 
            % the datainterface.   Typically, all Simulink datatypes are
            % available on the target and therefore, for integer types at
            % least, the storage size between host and target is equal.
            %
            % Only when the datatype is unavailable on
            % the target will code generation choose a wider C datatype.
            % See Example 1 below.
            %
            % In some cases, if the datatype size (e.g. for double) is different on the
            % target then code generation does not have any knowledge of this!
            % However, for host / target transfer like PIL this is a very
            % important scenario. See Example 2 below.
            %
            % Currently, PIL only supports the case where the stored value
            % datatype size matches between host and target.   Given the initial assumption above,
            % we enforce that the Simulink datatype exists on the target and
            % is of the host size by calling a_getDatatypeSize to get the target details.
            % An error is thrown if an identical datatype does not exist on the
            % target.
            %
            % This area of PIL could be improved by having more explicit knowledge
            % of the target stored value datatype used by RTW.
            %
            % Example 1: An example of an architecture that may have
            % different host and target stored value datatypes (and sizes) is the TI
            % C2000 which does not have an 8-bit datatype, so code generation uses the
            % next largest standard sized container, 16-bit.   In the check
            % below, the size for the Simulink storage type, uint8, is
            % returned as 2 bytes and hence we have a mismatch with the host size (1 byte).
            %
            % Example 2: An example of an architecture that may have different host and
            % target stored value datatype sizes is the Freescale DSP563xx
            % which has a 24-bit single & double instead of the host 32-bit
            % and 64-bit sizes.   In the check below, the size for the Simulink
            % storage type, double, is returned as 3 bytes and hence we have a mismatch with the host
            % size.
            simulinkByteSize = this.HostByteSize;
            simulinkType = this.SimulinkType;
            % check boolean host / target consistency
            if strcmp(simulinkType, 'boolean')
                if silPILWrapperUtils.NeedToValidateBooleanRemapping
                    diString = this.DataInterface.getString(silPILWrapperUtils);
                    rtw.pil.Sfunction.validateBooleanRemapping(silPILWrapperUtils.getConfigInterface, ...
                        diString, ...
                        silPILWrapperUtils.getInfoStruct.designDataLocation);
                    % never repeat this check!
                    silPILWrapperUtils.NeedToValidateBooleanRemapping = false;
                end
            end
            
            % g2194335 we may need to deal with targetType ~= Simulink type for floating point types. i.e. size of double in model ~=
            % size on target. We should add a more generic is x-bit floating point supported if so. As we do not capture floating point support
            % we assume this check is fine.
            typeSupportedByTarget = coder.connectivity.PILCommunication.getIsTypeSupportedFroPIL(...
                                            silPILWrapperUtils.getTargetServices().getDataTypeService(), simulinkType);
            
            if (~typeSupportedByTarget)
                diString = this.DataInterface.getString(silPILWrapperUtils);
                silPILWrapperUtils.ComponentBoundaryValidation.validationError('pil', ...
                    'DataTypeUnsupported', ...
                    diString, ...
                    simulinkType, ...
                    int2str(simulinkByteSize), ...
                    '?');
            end            
        end         
                
        % fixed-point I/O size validation
        function validateFixedPointIOSize(this, silPILWrapperUtils)
            type =  this.Type;                                           
            if silPILWrapperUtils.getSILPILInterface.getInTheLoopType == ...
                       rtw.pil.InTheLoopType.MATLABFcn
                isBuiltIn64BitAllowed = true;
            else
                isBuiltIn64BitAllowed = false;
            end                                        
            isFixedPoint = rtw.connectivity.CodeInfoTypeUtils.isFixedPoint(type, isBuiltIn64BitAllowed);
            isBuiltIn64Bit = false;
            if ~isFixedPoint                 
                dtypeId = this.DTypeId;
                if (dtypeId == rtw.connectivity.CodeInfoTypeEnum.TypeInt64) || ...
                    (dtypeId == rtw.connectivity.CodeInfoTypeEnum.TypeUInt64)
                    isBuiltIn64Bit = true;
                end
            end                                    
                                                
            if isFixedPoint || isBuiltIn64Bit               
                assert(type.isNumeric, 'Base type is not numerictype');
                wordlength = type.WordLength;                                                                                                 
                % check for 64-bit chunk size in MATLAB/Simulink                
                hostWordLengths = silPILWrapperUtils.getClientInterface.getLongLongInfo;
                
                if (hostWordLengths.LongNumBits == 64) || ...
                        (hostWordLengths.LongLongMode && ...
                        (hostWordLengths.LongLongNumBits == 64))
                    maxSimulinkChunk = 64;
                    
                else
                    maxSimulinkChunk = 32;
                end                
                % check for 64-bit chunk size in code generation
                if (silPILWrapperUtils.getParam('TargetBitPerLong') == 64) || ...
                        (strcmp(silPILWrapperUtils.getParam('TargetLongLongMode'), 'on') ...
                        && (silPILWrapperUtils.getParam('TargetBitPerLongLong') == 64))
                    maxCodeGenChunk = 64;
                else
                    maxCodeGenChunk = 32;
                end                
                % check for supported word length
                maxWordLength = min(maxSimulinkChunk, maxCodeGenChunk);

                if wordlength > maxWordLength
                    % Don't allow Multi-Word I/O data
                    diString = this.DataInterface.getString(silPILWrapperUtils);
                    silPILWrapperUtils.ComponentBoundaryValidation.validationError('pil', ...
                        'UnsupportedFixedPointIOSize', ...
                        maxSimulinkChunk, ...
                        maxCodeGenChunk, ...
                        maxWordLength, ...
                        diString, ...
                        wordlength, ...
                        maxWordLength);
                end

                % additional check for invalid regions such as 33..40 on
                % the C6000 which has an unsupported 40-bit long...                
                bitPerLong = silPILWrapperUtils.getParam('TargetBitPerLong');
                if (maxCodeGenChunk == 64) && ...
                        (bitPerLong < 64) && ...
                        (wordlength >= 33) && ...
                        (wordlength <= bitPerLong)                        
                    diString = this.DataInterface.getString(silPILWrapperUtils);
                    silPILWrapperUtils.ComponentBoundaryValidation.validationError('pil', ...
                        'UnsupportedFixedPointLong', ...
                        diString, ...
                        wordlength, ...
                        bitPerLong, ...
                        bitPerLong + 1);                                   
                end                
            end
        end
       
        % opaque type validation
        function validateNoOpaqueTypes(this, silPILWrapperUtils)
            type =  this.Type;
            if type.isOpaque
                diString = this.DataInterface.getString(silPILWrapperUtils);
                silPILWrapperUtils.ComponentBoundaryValidation.validationError('pil', ...
                    'UnsupportedOpaqueType', ...
                    diString, ...
                    type.Identifier);
            end
        end                
        
        function initDimensions(this)
            % start with base type, which has dimensions 1
            dimensions = 1;   
            % step backwards through ancestors to find any terminal matrixtype
            % associated with this DataElement
            for i=length(this.Ancestors):-1:1
                if this.Ancestors(i).IsStructure
                    % stop at structure boundary
                    break;
                elseif this.Ancestors(i).IsMatrix
                    dimensions = rtw.connectivity.CodeInfoUtils.getArray(this.Ancestors(i).Dimensions);
                    % stop at matrix boundary
                    break;
                elseif i == 1 && this.Ancestors(i).IsPointer
                    hostType = this.DataInterface.getHostType;
                    if hostType.isMatrix
                        dimensions = rtw.connectivity.CodeInfoUtils.getArray(hostType.Dimensions);
                        break;
                    end                    
                end
            end            
            this.Dimensions = dimensions;
        end
        
        % return the number of elements
        function initNumElements(this)                                    
            % calculate the number of data elements               
            dimensions = this.Dimensions;
            if ~isscalar(dimensions)
                dimensions = prod(dimensions);
            end
            this.NumElements = dimensions;
        end                    
               
        function initSymbolicDims(this)
            % start with base type, which has no symbolic dimensions
            hasSymbolicDims = false;            
            symbolicWidth = '1';
            symbolicDims = {'1'};
            % step backwards through ancestors to find any terminal matrixtype
            % associated with this DataElement
            for i=length(this.Ancestors):-1:1
                if this.Ancestors(i).IsStructure
                    % stop at structure boundary
                    break;
                elseif this.Ancestors(i).IsMatrix
                    hasSymbolicDims = this.Ancestors(i).HasSymbolicDims;
                    symbolicWidth = this.Ancestors(i).SymbolicNumElements;
                    symbolicDims = this.Ancestors(i).SymbolicDims;
                    % stop at matrix boundary
                    break;
                elseif i == 1 && this.Ancestors(i).IsPointer
                    hostType=this.DataInterface.getHostType;
                    if hostType.isMatrix
                        hasSymbolicDims = hostType.HasSymbolicDimensions;                        
                        symbolicWidth = hostType.SymbolicWidth;
                        symbolicDims =  rtw.connectivity.CodeInfoUtils.getArray(hostType.SymbolicDimensions);
                        break;
                    end                    
                    % extract any symbolic width / dims from the type
                    symbolicWidth = rtw.connectivity.CodeInfoTypeUtils.getSymbolicWidth(hostType);
                    symbolicDims = rtw.connectivity.CodeInfoTypeUtils.getSymbolicDimensions(hostType);
                end
            end            
            this.HasSymbolicDims = hasSymbolicDims;
            this.SymbolicNumElements = symbolicWidth;
            this.SymbolicDims = symbolicDims;
        end
        
        
        function initDtypeId(this)   
            % cannot determine a data type id for opaque types
            if ~this.Type.isOpaque
                % Data elements created for "host" types in CodeInfo may be
                % e.g. 120-bit fixed-point rather than the multiword
                % "target" types. In such cases, we cannot determine a
                % DTypeId, but it is still useful to be able to create a
                % DataElement object, so we just leave DTypeId empty rather
                % than throwing an exception here.
                throwErrorForMultiWordFix = false;
                this.DTypeId = rtw.connectivity.CodeInfoTypeUtils.getCodeInfoStoredValueDTypeId(this.Type, ...
                    throwErrorForMultiWordFix);
            end
        end
        
        function initNumInstances(this)
            % calculate the number of instances of this data
            % which depends on the struct dimensions in the Ancestors
            numInstances = 1;            
            loopSize = 1;
            for ancestorIdx=1:length(this.Ancestors)
                if this.Ancestors(ancestorIdx).IsStructure
                    numInstances = numInstances * loopSize;
                    % reset
                    loopSize = 1;
                elseif this.Ancestors(ancestorIdx).IsMatrix
                    assert(loopSize == 1, 'loopSize must equal 1.');
                    dims = this.Ancestors(ancestorIdx).Dimensions;
                    loopSize = prod(rtw.connectivity.CodeInfoUtils.getArray(dims));
                elseif ancestorIdx == 1 && this.Ancestors(ancestorIdx).IsPointer
                    hostType=this.DataInterface.getHostType;
                    if hostType.isMatrix
                        loopSize = prod(rtw.connectivity.CodeInfoUtils.getArray(hostType.Dimensions));
                    end
                end
            end
            this.NumInstances = numInstances;
        end
        
        function initSymbolicNumInstances(this)
            % calculate the number of instances of this data
            % which depends on the struct dimensions in the Ancestors
            symbNumInstances = '1';            
            loopSize = '1';
            for ancestorIdx=1:length(this.Ancestors)
                if this.Ancestors(ancestorIdx).IsStructure
                    if ~strcmp('1', loopSize)
                        if strcmp('1', symbNumInstances)
                            symbNumInstances = loopSize;
                        else
                            symbNumInstances = ...
                                sprintf('(%s)*(%s)', symbNumInstances, loopSize);
                        end
                        % reset
                        loopSize = '1';
                    end                    
                elseif this.Ancestors(ancestorIdx).IsMatrix
                    assert(strcmp(loopSize,'1'), 'loopSize must equal 1.');
                    loopSize = this.Ancestors(ancestorIdx).SymbolicNumElements;
                elseif ancestorIdx == 1 && this.Ancestors(ancestorIdx).IsPointer
                    hostType=this.DataInterface.getHostType;
                    if hostType.isMatrix
                        loopSize = hostType.SymbolicWidth;
                    end
                end
            end
            this.SymbolicNumInstances = symbNumInstances;
        end
        
        function initIsStructElement(this)
            % is this DataElement part of a bus or struct
            isStructElement = false;
            % search through ancestors for structure
            for ancestorIdx=1:length(this.Ancestors)
                if this.Ancestors(ancestorIdx).IsStructure
                    isStructElement = true;
                    break;
                end
            end
            this.IsStructElement = isStructElement;
        end
        
        function initIsEnum(this)
            this.IsEnum = rtw.connectivity.CodeInfoTypeUtils.isEnumType(this.Type);
        end
        
        function initIsGetSetViaPointer(this)
            % determines whether the data should be
            % accessed via pointer operations or via assignment.
            isEnumWithoutStorageType = this.IsEnum && isempty(this.Type.StorageType);
            isBitFieldMember = this.DataInterface.isBitFieldMember;
            
            lImplementation = this.DataInterface.getRTWDataInterface.Implementation;
            if rtw.connectivity.CodeInfoUtils.isa(lImplementation, 'ArrayExpression')
                lImplementation = lImplementation.BaseRegion;
            end
            isCSCOther = rtw.connectivity.CodeInfoUtils.isa(lImplementation, 'CustomVariable') && strcmp(lImplementation.Layout, 'other');
            if isEnumWithoutStorageType || isBitFieldMember || isCSCOther
                this.IsGetSetViaPointer = false;                                   
            else
                this.IsGetSetViaPointer = true;
            end
        end
        
        % get final Structure ancestor index 
        function AncestorIdx = getFinalStructAncestorIdx(this)
            AncestorIdx = [];
            for i = length(this.Ancestors):-1:1
                if this.Ancestors(i).IsStructure
                    AncestorIdx = i;
                    break;
                end
            end
            assert(~isempty(AncestorIdx), 'There is not struct typed ancestor.');
        end
        
        function initIsActive(this)
            % determines if the DataElement is under an active
            % VariantControl or not 
            dataInterfaceObj = this.getDataInterface;
            if dataInterfaceObj.IsProtocolData
                % protocol data always active
                this.IsActive = true;
            else
                interface = dataInterfaceObj.getRTWDataInterface;
                this.IsActive = rtw.pil.CodeInfoData.isInterfaceActive(interface);
            end
        end
        function initIsAccessible(this)
            interface = this.getDataInterface.getRTWDataInterface;
            this.IsAccessible = rtw.connectivity.CodeInfoUtils.isDataInterfaceAccessible(interface);
        end
    end
    
    methods (Static, Access='private')
        function enumString = updateEnumStringForAutosar(dataInterface, originalEnumString)
            % UPDATEENUMSTRINGFORAUTOSAR Convert an enum string to AUTOSAR style if necessary
            % If DATAINTERFACE indicates that this is an AUTOSAR enum
            % interface, we need to change the enum string to match AUTOSAR
            % style.
            % Specifically, DataAccessModes of ModeSend and ModeReceive
            % need their enum strings prefixed with
            % Rte_MODE_[MDGNAME]_[ORIGINALENUMSTRING]
            diImpl = dataInterface.Implementation;
            enumString = originalEnumString;
            if ~rtw.connectivity.CodeInfoUtils.isa(diImpl, 'AutosarSenderReceiver')
                return;
            end
            
            if strcmp(diImpl.DataAccessMode, 'ModeSend') || ...
                    strcmp(diImpl.DataAccessMode, 'ModeReceive')
                enumString = sprintf('RTE_MODE_%s_%s', diImpl.Type.Name, originalEnumString);
            end
        end
    end
end

