classdef (Hidden = true) LoggingUtils
    %LOGGINGUTILS contains methods to support signal and state logging for
    %SIL/PIL simulations
    
    % Copyright 2020 The MathWorks, Inc.

    methods(Static)
        
        function [dataInterface, idReuse] = filterUnneededBufferUpload(dataInterface)
            numLoggingData = length(dataInterface);
            expressions = cell(1, numLoggingData);
            idReuse = zeros(1, numLoggingData);
            toRemove = false(1, numLoggingData);
            
            for i = 1:numLoggingData
                logData = dataInterface(i);
                if logData.getIsNVBus()
                    expr = logData.getUploadIdentifier();
                    expressions{i} = expr;
                    if isempty(expr)
                        idReuse(i) = i;
                        toRemove(i) = false;
                    else
                        id = find(strcmp(expressions, expr));
                        idReuse(i) = id(1);
                        toRemove(i) = idReuse(i) ~= i;
                    end
                else
                    idReuse(i) = i;
                end
            end
            dataInterface(toRemove) = [];
        end
        
        function [sigInfoSID, sigInfo] = updateSIDs(silPILWrapperUtils, dataInterface, sigInfoSID, sigInfo)
            if silPILWrapperUtils.isLoggingFromTransformedModel()
                assert(sigInfo.BlockPath.getLength == 1, 'multiple level block paths not supported');
                portUrl = extractAfter(sigInfoSID, '#');
                sigInfoSID = sprintf('%s#%s', dataInterface.SID, portUrl);
                sigInfo.BlockPath = Simulink.BlockPath(getfullname(dataInterface.SID)).getBlock(1);
            end
        end
        
        function [isStandardSignal, dataElements, numDataElements] = ...
                processDataElements(dataInterface, dataElements)
            preFiltering = length(dataElements);
            % trim complex DataElements (only the real part is processed)
            dataElements = rtw.pil.LoggingUtils.filterOutUselessDataElements(dataElements);
            midFiltering = length(dataElements);
            % remove dataElements that are uploaded but not streamed to
            % SDI (bus selector and selector blocks with non-virtual
            % buses)
            dataElements = dataInterface.getActualDataElements(dataElements);
            postFiltering = length(dataElements);
            assert(~isempty(dataElements), 'No DataElements selected');
            numDataElements = length(dataElements);
            if dataInterface.getIsNVBus()
                % by default, a standard signal has a single DataElement
                isStandardSignal = numDataElements == 1;
                % however, if the signal is a non-virtual bus, check that
                % this isn't the edge case of a bus with a single field (in
                % other words, the single DataElement isn't due to filtering
                % - bus selector and selector objects - but it is the
                % original structure. In this case, it needs to be treated 
                % as a bus regardless its size. This is why we check the 
                % values of midFiltering and postFiltering, if they are the
                % same, no filtering was applied and its dimension was
                % already 1.
                % However, it may happen that a bus selector picks a signal
                % from a bus with only a signal and it should be treated as
                % standard signal. It is the output of a bus selector if
                % getBusSelectorIndex is not -1. For states, a proper
                % variable is always generated so, getBusSelectorIndex is
                % always -1 (they are processed as buses)
                if isStandardSignal && midFiltering == postFiltering && ...
                        dataInterface.getBusSelectorIndex() == -1
                    isStandardSignal = false;
                end
            else
                % it is a standard signal if it contains only a single
                % DataElements and:
                % - it was already one before the filtering
                % - or, it was already a complex signal (two DataElements
                %   after the filtering and then one after trimming complex
                %   types
                isStandardSignal = numDataElements == 1 && ...
                    (preFiltering == 1 || midFiltering == 1);
                assert(isStandardSignal, 'It isn''t a bus but it looks like that');
            end
        end
        
        function dataElements = filterOutUselessDataElements(dataElements)
            % for complex numbers, two DataElements are provided but we 
            % process the real part only. Remove the imag part to 
            % avoid confusion with NVBuses, so we don't process a complex 
            % signal as bus
            numDataElements = length(dataElements);
            toRemove = false(1, numDataElements);
            for i=1:numDataElements
                [isComplex, isReal] = dataElements(i).getComplexInfo();
                if isComplex && ~isReal
                    toRemove(i) = true;
                end
            end
            dataElements(toRemove) = [];
        end
        
        function computeOffset(writer, varName, dataInterface, isBusLoggingSupported)
            % this method generates to code to access the memory area to
            % log in a bigger data structure
            if isBusLoggingSupported && dataInterface.getIsNVBus()
                busIndex = dataInterface.getBusSelectorIndex();
                regionOffset = dataInterface.getRegionOffset();
                if ~(busIndex > 0 || regionOffset > 0)
                    % the entire bus is uploaded, no need the compute
                    % offsets
                    writer.wLine('*%s = 0;', varName);
                    return;
                end
                % if busIndex isn't -1, the entire bus is uploaded and then
                % we pass to SDI only the memory area we want to log. 
                % With buses, it may happen that the busIndex is -1 but the
                % offset is specified. We still upload the entire bus and 
                % log only part of it (this doesn't happen for array of 
                % built-in types)
                type = dataInterface.getRTWDataInterface().Type;
                if busIndex == -1
                    % this is a array of buses and we log only few of them
                    % based on regionLength and regionOffset
                    fieldPath = rtw.pil.LoggingUtils.addFinalOffsetPath(type);
                else
                    % we are logging part of a standard structure, find the
                    % the logged field
                    initIndex = rtw.pil.LoggingUtils.computeInitialSearchIndex(type);
                    [fieldPath, ~, found] = rtw.pil.LoggingUtils.findOffsetPathInternal(...
                        type, initIndex, [], busIndex);
                    assert(found, 'Field not found');
                end
                
                code = '0';
                needDTA = false;
                % generate the code to compute the offset since the
                % structure root until the field (not included)
                for i=length(fieldPath)-1:-1:1
                    step = fieldPath{i};
                    assert(~strcmp(step.type, 'leaf'), 'not possible to have a leaf within the hierarchy');
                    if step.index == 0
                        continue;
                    end
                    [codeStep, needDTA] = rtw.pil.LoggingUtils.processFieldOffset(...
                        step.type, step.Identifier, step.index, needDTA);
                    if ~isempty(codeStep)
                        code = sprintf('%s + %s', code, codeStep);
                    end
                end
                % generate the final offset to reach the field (this step
                % involves regionOffset)
                if regionOffset > 0
                    step = fieldPath{end};
                    [codeStep, needDTA] = rtw.pil.LoggingUtils.processFieldOffset(...
                        step.type, step.Identifier, regionOffset, needDTA);
                    if ~isempty(codeStep)
                        code = sprintf('%s + %s', code, codeStep);
                    end
                end
                % emit code to compute the offset
                if needDTA
                    writer.wBlockStart('');
                    writer.wLine('slDataTypeAccess * dta = ssGetDataTypeAccess(S);');
                    writer.wLine('const char * bpath = ssGetPath(S);');
                end
                writer.wLine('*%s = %s;', varName, code);
                if needDTA
                    writer.wBlockEnd('');
                end
            else
                % it is a standard signal, no need to add offset
                writer.wLine('*%s = 0;', varName);
            end
        end
        
        function writeSectionInitStringFields(writer, dataInterface, pWorkIdx)
            dataElements = dataInterface.getHostDataElements();
            isStringFieldPresent = false;
            for dataElement = dataElements
                if dataElement.getType().isChar()
                    isStringFieldPresent = true;
                    break;
                end
            end
            if isStringFieldPresent
                tmpVarName = 'tmpVarMemory';
                varOffset = 'addr';
                varOffsetIdx = 'offsetIdx';
                writer.wBlockStart('');
                writer.wLine('slDataTypeAccess * dta = ssGetDataTypeAccess(S);');
                writer.wLine('const char * bpath = ssGetPath(S);');
                writer.wLine('int %s;', varOffsetIdx);
                writer.wLine('uint8_T *%s = (uint8_T *) ssGetPWorkValue(S, %s);', tmpVarName, pWorkIdx);
                
                for dataElement = dataElements
                    if dataElement.getDtypeId ~= rtw.connectivity.CodeInfoTypeEnum.TypeChar
                        continue;
                    end
                    idStart = dataElement.getFinalNVStructElementOffset();
                    idStop = dataElement.NumPrevSameAncestorElements;
                    for idx = idStart:(idStart+idStop)
                        dataElement.writeOffsetHeader(writer);
                        writer.wLine('uint8_T *%s;', varOffset);
                        writer.wLine('%s = %d;', varOffsetIdx, idx);
                        writer.wLine('%s = %s %s;', varOffset, tmpVarName, dataElement.getStructArrayOffset(true));
                        writer.wLine('suInitializeSILStringOutput(%s);', varOffset);
                        dataElement.writeOffsetTrailer(writer);
                    end
                end
                
                writer.wBlockEnd;
            end
        end
        
        function leaves = findSubTree(dataInterface)
            % given a type, this method returns an array of boolean to
            % select the DataElements that are actually logged for this
            % structure. The DataElements depend on the bus selector index
            type = dataInterface.getRTWDataInterface().Type;
            initIndex = rtw.pil.LoggingUtils.computeInitialSearchIndex(type);
            [~, leaves] = rtw.pil.LoggingUtils.findSubTreeInternal(...
                type, initIndex, dataInterface.getBusSelectorIndex(), [], false);
        end
        
        function [type, found] = findSubTreeType(dataInterface)
            % given a type, return the correct type of the logged sub-tree 
            % specified in bus selector index
            % note, this method deals with the host type only
            type = dataInterface.getRTWDataInterface().Type;
            busSelectorIndex = dataInterface.getBusSelectorIndex();
            assert(busSelectorIndex >= 0, 'this is a standard signal');
            if busSelectorIndex == 0
                % if it is a bus array, it is the first element. Otherwise,
                % it is the entire bus.
                % in the first case, the method setRegionIndexes will
                % automatically peak the first item
                found = true;
            else
                initIndex = rtw.pil.LoggingUtils.computeInitialSearchIndex(type);
                [type, ~, found] = rtw.pil.LoggingUtils.findSubTreeTypeInternal(...
                    type, initIndex, busSelectorIndex);
            end
            assert(found, 'The sub-type indexed by the Bus Selector Index was not found');
        end
        
        function [whoIsTheParent, childName, childType, childDim, ...
                      childDims, leafOwner, leafName] = ...
                          extractNVBusStruct(dataInterface)
            % return main information about the components forming a bus
            % this code configures the host only
            realType = dataInterface.getRealTypeForNVBus();
            nameMap = dataInterface.getNameMapping();
            % create intermediate tree
            [root, numNodes, numLeaves] = rtw.pil.LoggingUtils.createBusTree(realType, dataInterface);
            % merge signal names with the tree
            if ~isempty(nameMap)
                root = rtw.pil.LoggingUtils.mergeSignalNameInTree(root, nameMap);
            end
            % visit the tree to extract nodes
            whoIsTheParent = zeros(1, numNodes);
            whoIsTheParent(1) = -1;
            childName = cell(1, numNodes);
            childType = cell(1, numNodes);
            childDim = zeros(1, numNodes);
            childDims = cell(1, numNodes);
            [whoIsTheParent, childName, childType, childDim, childDims] = ...
                rtw.pil.LoggingUtils.visitNode(root, whoIsTheParent, ...
                    childName, childType, childDim, childDims, -1, 1);
            % visit the tree to extract leaves
            leafOwner = zeros(1, numLeaves);
            leafName = cell(1, numLeaves);
            [leafOwner, leafName] = rtw.pil.LoggingUtils.visitLeaf(root, leafOwner, leafName, 0, 1);
        end
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % writing methods
        function writeSectionInitAQDataType(writer, wrapperUtils, codeInfoUtils, type, dtHAssignment)
            [storageId, namedObject] = rtw.connectivity.CodeInfoTypeUtils.getCodeInfoStoredValueDTypeId(type);
            
            % For 64-bit types ('TypeInt64' and 'TypeUInt64'), the  built-in
            % types are not used (use fixed-data types) - Simulink specific
            isBuiltIn64BitAllowed = false;
            isFixedPoint = rtw.connectivity.CodeInfoTypeUtils.isFixedPoint(type,...
                isBuiltIn64BitAllowed);
            
            % aliases to fixed-point types are handled in the else branch
            if ~isempty(namedObject) && (type.isEnum || ~isFixedPoint)
                % register datatype with named object - enum or alias (only
                % aliases to built-in types are registered here)
                if type.isEnum
                    % in case namedObject is an alias to enum, find the base
                    % type
                    baseType = rtw.connectivity.CodeInfoTypeUtils.findEnumBaseType(...
                        wrapperUtils.getInfoStruct.designDataLocation, namedObject);
                    assert(~isempty(baseType), ...
                        'Not possible to find neither the enum nor the alias');
                    enumValues = codeInfoUtils.getArray(type.Values);
                    enumLabels = codeInfoUtils.getArray(type.Strings);
                    assert(length(enumValues)==length(enumLabels), 'Wrong enum type');
                    writer.wLine(['const char_T * enumLabels[' ...
                        int2str(length(enumLabels)) '];']);
                    writer.wLine(['int32_T enumValues[' ...
                        int2str(length(enumValues)) '];']);
                    for j=1:length(enumLabels)
                        writer.wLine(['enumLabels[' int2str(j-1) '] = "'...
                            enumLabels{j} '";']);
                        writer.wLine(['enumValues[' int2str(j-1) '] = '...
                            int2str(enumValues(j)) ';']);
                    end
                    storageType = rtw.pil.LoggingUtils.extractEnumStorageType(type);
                    writer.wLine([dtHAssignment ' = xilSimulinkUtilsCreateEnumSignal('...
                        '"' baseType '", "' storageType '", ' ...
                        int2str(length(enumValues)) ', enumLabels, enumValues);']);
                elseif strcmp(storageId, 'TypeInt64')
                    writer.wLine('%s = xilSimulinkUtilsCreate64BitInteger(true);', dtHAssignment);
                elseif strcmp(storageId, 'TypeUInt64')
                    writer.wLine('%s = xilSimulinkUtilsCreate64BitInteger(false);', dtHAssignment);
                else
                    builtInType = rtw.connectivity.CodeInfoTypeUtils.getSdiBuiltInDataType(storageId);
                    writer.wLine([dtHAssignment ' = xilSimulinkUtilsCreateAliasSignal('...
                        '"' namedObject '", ' builtInType ');']);
                end
            else
                if isFixedPoint
                    % NOTE: if there is no fixed point license available
                    % when creating the component then Simulink will
                    % allow the use of doubles everywhere in place of fixed
                    % point types and we won't even enter this branch.
                    baseType = rtw.connectivity.CodeInfoTypeUtils.getBaseType(type);
                    assert(baseType.isNumeric, 'Base type is not numerictype');
                    
                    % see fitools/export/include/fitools/numerictype.hpp
                    if baseType.isScaledDouble
                        numericType = 1;
                    elseif baseType.isDouble
                        numericType = 2;
                    elseif baseType.isSingle
                        numericType = 3;
                    elseif baseType.isBoolean
                        numericType = 4;
                    elseif baseType.isFixed
                        numericType = 0;
                    else
                        assert(false, 'Not supported numeric type');
                    end
                    if endsWith(baseType.DataTypeMode, ': slope and bias scaling')
                        writer.wLine([dtHAssignment ' = xilSimulinkUtilsCreateFixedPointSlopeBiasSignal('...
                            int2str(numericType) ', '...
                            int2str(baseType.Signed) ', '...
                            int2str(baseType.WordLength) ', '...
                            rtw.connectivity.CodeInfoUtils.double2str(baseType.SlopeAdjustmentFactor) ', '...
                            int2str(baseType.FixedExponent) ', '...
                            rtw.connectivity.CodeInfoUtils.double2str(baseType.Bias) ');']);
                    elseif endsWith(baseType.DataTypeMode, ': binary point scaling')
                        writer.wLine([dtHAssignment ' = xilSimulinkUtilsCreateFixedPointBinaryScalingSignal('...
                            int2str(numericType) ', '...
                            int2str(baseType.Signed) ', '...
                            int2str(baseType.WordLength) ', '...
                            int2str(baseType.FractionLength) ');']);
                    else
                        assert(false, 'Data type %s not supported', ...
                            class(baseType));
                    end
                elseif type.isChar
                    writer.wLine('%s = xilSimulinkUtilsCreateStringSignal();', dtHAssignment);
                elseif type.isHalf
                    writer.wLine('%s = xilSimulinkUtilsCreateHalfPrecisionSignal();', dtHAssignment);
                elseif strcmp(storageId, 'TypeInt64')
                    writer.wLine('%s = xilSimulinkUtilsCreate64BitInteger(true);', dtHAssignment);
                elseif strcmp(storageId, 'TypeUInt64')
                    writer.wLine('%s = xilSimulinkUtilsCreate64BitInteger(false);', dtHAssignment);
                else
                    % built-in type
                    builtInType = rtw.connectivity.CodeInfoTypeUtils.getSdiBuiltInDataType(storageId);
                    writer.wLine([dtHAssignment ' = xilSimulinkUtilsCreateBuiltInSignal(' builtInType ');']);
                end
            end
        end
        
        function writeSectionNodeSizeInNVBus(writer, varName, childType)
            for i=1:length(childType)
                writer.wLine(['%s[%d] = dtaGetDataTypeSize('...
                    'dta, bpath, ssGetDataTypeId(S, "%s"));'], varName, i-1, childType{i});
            end
        end
        
        function writeSectionFieldsInNVBus(writer, varOffset, dataElements)
            numDataElements = length(dataElements);
            
            localIdx = 0;
            for i=1:numDataElements
                dataElement = dataElements(i);
                isComplex = dataElement.getComplexInfo;
                % for complex data, the real part has
                % SkipForCommonAncestorProcessing equal to false but it
                % is one which must be processed (the imag part was
                % removed at the beginning of this function)
                if dataElement.SkipForCommonAncestorProcessing && ~isComplex
                    continue;
                end
                [idStart, ~, ~, index] = dataElement.getFinalNVStructElementOffset();
                idStop = dataElement.NumPrevSameAncestorElements;
                for idx = idStart:(idStart+idStop)
                    dataElement.writeOffsetHeader(writer);
                    writer.wLine('offsetIdx = %d;', idx);
                    writer.wLine('%s[%d] = 0 %s;', varOffset, ...
                        localIdx, dataElement.getStructArrayOffset(true));
                    if isComplex
                        writer.wLine('offsetIdx = %d;', index - 1);
                    end
                    localIdx = localIdx + 1;
                    dataElement.writeOffsetTrailer(writer);
                end
            end
        end
    end
    
    methods(Static, Access=private)
        function storageType = extractEnumStorageType(type)
            storageType = 'int32';
            if isprop(type, 'StorageType') && ...
                    ~isempty(type.StorageType) && ...
                    isa(type.StorageType, "coder.descriptor.types.Integer")
                storageType = type.StorageType.Name;
            end
        end

        function [whoIsTheParent, childName, childType, childDim, childDims] = ...
                visitStructureElements(fieldName, current, ...
                parentId, whoIsTheParent, childName, childType, childDim, childDims)
            % visit the hierarchy tree to extract dimension, name and type
            % of the current struct node. Then, move to the children nodes
            whoIsTheParent(end+1) = parentId;
            childName{end+1} = fieldName;
            childType{end+1} = current.Type;
            childDim(end+1) = length(current.Dim);
            childDims{end+1} = current.Dim;
            keys = current.map.keys;
            for i = 1:length(keys)
                key = keys{i};
                [whoIsTheParent, childName, childType, childDim, childDims] = ...
                    rtw.pil.LoggingUtils.visitStructureElements(key, current.map(key), ...
                    current.ID, whoIsTheParent, childName, childType, childDim, childDims);
            end
        end
        
        function [root, numNodes, numLeaves] = createBusTree(type, dataInterface)
            if type.isMatrix && type.BaseType.isStructure
                numDims = length(type.Dimensions);
                dim = cell(1, numDims);
                sdiDim = cell(1, numDims);
                for i=1:numDims
                    dim{i} = num2str(type.Dimensions(i));
                end
                % override dimension for the top element if we are logging
                % only part of bus field
                if numDims == 1 && ...
                        dataInterface.getBusSelectorIndex() > 0 && ...
                        dataInterface.getRegionLength() > 0 && ...
                        type.Dimensions(1) > dataInterface.getRegionLength()
                    sdiDim{1} = num2str(dataInterface.getRegionLength());
                else
                    sdiDim = dim;
                end
                type = type.BaseType;
            else
                dim = {'1'};
                sdiDim = dim;
            end
            root.ID = 0;
            root.Type = type.Identifier;
            root.CodeName = type.Identifier;
            root.ModelName = root.CodeName;
            root.NodeId = 0;
            root.Children = [];
            root.Dim = dim;
            root.SDIDim = sdiDim;
            % generate tree
            [root.Children, totalNum, numNodes, numLeaves] = ...
                rtw.pil.LoggingUtils.processChildren(type.Elements, 1, 1, 0);
            assert(totalNum==(numNodes+numLeaves));
        end
        
        function [children, counter, numNodes, numLeaves] = processChildren(elements, counter, numNodes, numLeaves)
            children = [];
            for element = elements
                node.ID = counter;
                node.CodeName = element.Identifier;
                node.ModelName = node.CodeName;
                % we need to distinguish between Dim and SDIDim due to 
                % selector objects. For their outputs, the entire memory
                % block is uploaded (this is why we need Dim) and then
                % RegionOffset and RegionLength tell us the sub area to
                % take. SDIDim is used to configure the SDI signal
                % correctly becuase it must know the right size
                node.Dim = {'1'};
                node.SDIDim = {'1'};
                counter = counter + 1;
                if element.Type.isStructure
                    node.NodeId = numNodes;
                    node.Type = element.Type.Identifier;
                    numNodes = numNodes + 1;
                    [node.Children, counter, numNodes, numLeaves] = ...
                        rtw.pil.LoggingUtils.processChildren(...
                            element.Type.Elements, counter, numNodes, numLeaves);
                elseif  element.Type.isMatrix && element.Type.BaseType.isStructure
                    dim = cell(1, length(element.Type.Dimensions));
                    for i=1:length(element.Type.Dimensions)
                        dim{i} = num2str(element.Type.Dimensions(i));
                    end
                    node.Dim = dim;
                    node.SDIDim = dim;
                    node.Type = element.Type.BaseType.Identifier;
                    node.NodeId = numNodes;
                    numNodes = numNodes + 1;
                    [node.Children, counter, numNodes, numLeaves] = ...
                        rtw.pil.LoggingUtils.processChildren(...
                        element.Type.BaseType.Elements, counter, numNodes, numLeaves);
                else
                    node.Children = [];
                    node.Type = element.Type.Identifier;
                    node.NodeId = numLeaves;
                    numLeaves = numLeaves + 1;
                end
                if isempty(children)
                    children = node;
                else
                    children(end+1) = node; %#ok<AGROW>
                end
            end
        end
        
        function [whoIsTheParent, childName, childType, childDim, childDims, idx] = ...
                visitNode(node, whoIsTheParent, childName, childType, childDim, childDims, parentId, idx)
            if isempty(node.Children)
                return;
            end
            whoIsTheParent(idx) = parentId;
            childName{idx} = node.ModelName;
            childType{idx} = node.Type;
            childDim(idx) = length(node.SDIDim);
            childDims{idx} = node.SDIDim;
            idx = idx + 1;
            for i = 1:length(node.Children)
                [whoIsTheParent, childName, childType, childDim, childDims, idx] = ...
                    rtw.pil.LoggingUtils.visitNode(node.Children(i), ...
                        whoIsTheParent, childName, childType, childDim, childDims, node.NodeId, idx);
            end
        end
        
        function [leafOwner, leafName, idx] = visitLeaf(node, leafOwner, leafName, parentNodeId, idx)
            if isempty(node.Children)
                % it is a leaf
                leafOwner(idx) = parentNodeId;
                leafName{idx} = node.ModelName;
                idx = idx + 1;
            else
                % move into node children
                for i = 1:length(node.Children)
                    [leafOwner, leafName, idx] = ...
                        rtw.pil.LoggingUtils.visitLeaf(...
                            node.Children(i), leafOwner, leafName, node.NodeId, idx);
                end
            end
        end
        
        function node = mergeSignalNameInTree(node, nameMap)
            node.ModelName = nameMap.SDISignalName;
            assert(length(node.Children)==nameMap.Children.Size);
            for i = 1:nameMap.Children.Size
                node.Children(i) = rtw.pil.LoggingUtils.mergeSignalNameInTree(node.Children(i), nameMap.Children(i));
            end
        end
        
        function [currentIndex, leaves] = findSubTreeInternal(type, currentIndex, wantedIndex, leaves, isParentIn)
            if currentIndex == wantedIndex
                isParentIn = true;
            end
            if rtw.pil.LoggingUtils.isTheNodeALeaf(type)
                % it is a leave, add it if its parent is selected
                leaves(end+1) = isParentIn;
                return;
            end
            if isempty(type.Identifier)
                % array of buses
                numLeaves = length(leaves);
                for i=1:length(type.Dimensions)
                    for j=1:type.Dimensions(i)
                        % calling this method recursively, will add the
                        % same leaves for each element of the bus array
                        % multiple times.
                        % I.e., suppose a bus with two fields, an array of
                        % two of those buses will have four leaves (this is
                        % how Simulink works). However, dataElements aren't
                        % duplicated. So, we must keep a single copy for 
                        % each of them and OR their values.
                        % Suppose, we log only the second field of the 
                        % second instance: we want as output [false true]
                        % to select only the second DataElement and them
                        % other code will peak only the second element of
                        % the bus array.
                        % We loop over the array items, the first time we
                        % get [false false] and the second time [false
                        % true]. The OR is required to select the
                        % DataElement we need
                        if numLeaves > 0
                            copyLeaves = leaves(1:numLeaves);
                        else
                            copyLeaves = [];
                        end
                        currentIndex = currentIndex + 1;
                        [currentIndex, copyLeaves] = ...
                            rtw.pil.LoggingUtils.findSubTreeInternal(...
                                type.BaseType, currentIndex, wantedIndex, copyLeaves, isParentIn);
                        if j == 1 && i == 1
                            % this is the first version for the shared
                            % DataElements: keep it as it is
                            leaves = copyLeaves;
                        else
                            for z = (numLeaves+1):length(copyLeaves)
                                leaves(z) = leaves(z) | copyLeaves(z);
                            end
                        end
                    end
                end
            else
                for i=1:length(type.Elements)
                    currentIndex = currentIndex + 1;
                    [currentIndex, leaves] = ...
                        rtw.pil.LoggingUtils.findSubTreeInternal(...
                            type.Elements(i).Type, currentIndex, wantedIndex, leaves, isParentIn);
                end
            end
        end
        
        function [type, currentIndex, found] = findSubTreeTypeInternal(type, currentIndex, wantedIndex)
            if currentIndex == wantedIndex
                % we found the node with the specific Bus Selector Index:
                % stop the recursive branch
                found = true;
                return;
            end
            if rtw.pil.LoggingUtils.isTheNodeALeaf(type)
                % this is a leaf (neither an array nor a structure), but
                % nothing was found: stop the recursive search for this 
                % branch
                found = false;
                return;
            end
            if isempty(type.Identifier)
                % array of buses, loop over its elements
                for i=1:length(type.Dimensions)
                    for j=1:type.Dimensions(i)
                        currentIndex = currentIndex + 1;
                        [retType, currentIndex, found] = ...
                            rtw.pil.LoggingUtils.findSubTreeTypeInternal(...
                                type.BaseType, currentIndex, wantedIndex);
                        if found
                            type = retType;
                            return;
                        end
                    end
                end
            else
                % structure: process all elements/fields
                for i=1:length(type.Elements)
                    currentIndex = currentIndex + 1;
                    [retType, currentIndex, found] = ...
                        rtw.pil.LoggingUtils.findSubTreeTypeInternal(...
                            type.Elements(i).Type, currentIndex, wantedIndex);
                    if found
                        type = retType;
                        return;
                    end
                end
            end
        end
        
        function path = addFinalOffsetPath(type)
            % this method adds the step in the path list to compute the
            % proper offset
            if type.isStructure
                % structure
                t = 'struct';
                identifier = type.Identifier;
            elseif isempty(type.Identifier)
                if type.BaseType.isStructure
                    % case to support array of buses
                    t = 'structArray';
                else
                    % standard array
                    t = 'array';
                end
                identifier = type.BaseType.Identifier;
            else
                % simple field
                t = 'leaf';
                identifier = type.Identifier;
            end
            path = {struct('Identifier', identifier, 'type', t)};
        end
        
        function [path, currentIndex, found] = findOffsetPathInternal(type, currentIndex, path, wantedIndex)
            % construct the path to compute the exact offset
            if wantedIndex == -1 || currentIndex == wantedIndex
                % we found the node with the specific Bus Selector Index
                % regionOffset isn't required here, it will be dealt with 
                % in the caller if required 
                path = rtw.pil.LoggingUtils.addFinalOffsetPath(type);
                found = true;
                return;
            end
            if rtw.pil.LoggingUtils.isTheNodeALeaf(type)
                % if this is a leaf (neither an array nor a structure),
                % stop search for this branch
                found = false;
                return;
            end
            if isempty(type.Identifier)
                % array, loop over its items
                if length(type.Dimensions) == 1
                    sizeRow = 1;
                    sizeCln = type.Dimensions(1);
                elseif length(type.Dimensions) == 2
                    sizeRow = type.Dimensions(1);
                    sizeCln = type.Dimensions(2);
                end
                for i=1:sizeRow
                    for j=1:sizeCln
                        currentIndex = currentIndex + 1;
                        [path, currentIndex, found] = ...
                            rtw.pil.LoggingUtils.findOffsetPathInternal(...
                                type.BaseType, currentIndex, path, wantedIndex);
                        if found
                            if type.BaseType.isStructure
                                pathType = 'structArray';
                            else
                                pathType = 'array';
                            end
                            path = [{struct('Identifier', type.BaseType.Identifier, ...
                                            'type', pathType, ...
                                            'index', (i-1)*sizeRow + j - 1)} path]; %#ok<AGROW>
                            return;
                        end
                    end
                end
            else
                % structure: process all elements/fields
                for i=1:length(type.Elements)
                    currentIndex = currentIndex + 1;
                    [path, currentIndex, found] = ...
                        rtw.pil.LoggingUtils.findOffsetPathInternal(...
                            type.Elements(i).Type, currentIndex, path, wantedIndex);
                    if found
                        path = [{struct('Identifier', type.Identifier, ...
                                        'type', 'struct', ...
                                        'index', i-1)} path]; %#ok<AGROW>
                        return;
                    end
                end
            end
        end
        
        function [code, needDTA] = processFieldOffset(type, identifier, index, needDTA)
            switch(type)
                case 'struct'
                    % write code to reach a field in the struct
                    needDTA = true;
                    code = sprintf('dtaGetDataTypeElementOffset(dta, bpath, ssGetDataTypeId(S, "%s"), %d)', identifier, index);
                case 'structArray'
                    % write code to reach an item in the array of bus
                    needDTA = true;
                    code = sprintf('dtaGetDataTypeSize(dta, bpath, ssGetDataTypeId(S, "%s")) * %d', identifier, index);
                case 'array'
                    % write code to reach an item in the array
                    code = sprintf('sizeof(%s) * %d', identifier, index);
                case 'leaf'
                    % no action, we already have the field
                    code = '';
            end
        end
        
        function initIndex = computeInitialSearchIndex(type)
            if isempty(type.Identifier)
                % this is an array of buses. The first bus in the array has
                % index 0 so we need to deal with the fictious matrix
                % parent that is added
                initIndex = -1;
            else
                % this is a standard strcture and 0 is the struct root. If
                % the entire struct was logged, the busSelectorIndex would
                % be -1
                initIndex = 0;
            end
        end
        
        function res = isTheNodeALeaf(type)
            res = ~type.isStructure && (~isempty(type.Identifier) || ~type.BaseType.isStructure);
        end
        
    end
end
