classdef LoggingData < rtw.pil.StreamingData
%LOGGINGDATA - signal data to be transferred from the target to the host

%   Copyright 2010-2024 The MathWorks, Inc.

   properties (SetAccess = 'private', GetAccess = 'private')                
        % SignalLoggingInfo object describing the signal logging config
        SigInfo;        
        SigSID;
        SigName;
        SigOffset;
        SigLength;
        BusSelectorIndex;
        DataElementMask;
        RealHostType;
   end
   
   methods 
       function this = LoggingData(sigInfo, ...
                                   codeInfoDataInterface, ...
                                   codeInfoDataInterfaceIdx, ...
                                   clientInterface, ...
                                   codeInfoUtils, ...
                                   sigInfoSID, ...
                                   sigInfoName)           
           this@rtw.pil.StreamingData(codeInfoDataInterface, ...
               codeInfoDataInterfaceIdx, ...
               clientInterface, ...
               codeInfoUtils, ...
               'siglog');
           this.SigInfo = sigInfo;
           this.SigSID = sigInfoSID;
           this.SigName = sigInfoName;
           this.SigOffset = 0;
           this.SigLength = 0;
           this.BusSelectorIndex = 0;
           this.DataElementMask = [];
           this.RealHostType = [];
           assert(sigInfo.LoggingInfo.DataLogging, ...
                  'SignalLoggingInfo must be configured for DataLogging.');
       end   
       
       function res = getRealTypeForNVBus(this)
           res = this.RealHostType;
       end
       
       function type = getType(this)
           % this method returns the type to be processed on the target
           % side.
           % if BusSelectorIndex is greater than 0, the entire bus is still
           % uploaded and we decide the part to log on the host side later
           % So, BusSelectorIndex, RegionLength and RegionOffset aren't
           % considered here
           implementations = rtw.connectivity.CodeInfoUtils. ...
                getImplElementsIfCollection(this.getRTWDataInterface().Implementation);
           type = implementations(1).CodeType;
       end    
       
       % override getHostType to disable type separation which is not
       % supported for LoggingData
       function type = getHostType(this)
           % this method returns the type to be processed on the host side.
           % if BusSelectorIndex is greater than 0, the entire bus is still
           % uploaded and we decide the part to log on the host side later
           % So, BusSelectorIndex, RegionLength and RegionOffset aren't
           % considered here
           type = this.getRTWDataInterface.Type;
       end  
                    
       function domain = getDomain(~)
           domain = '';
       end
       
       function unit = getEscapedUnit(this)
           unit = this.getRTWDataInterface.Unit;
           unit = rtw.pil.StreamingData.escapeString(unit); 
       end
       
       function checksum = getChecksumItem(dataInstance, checksum)          
          checksum = CGXE.Utils.md5(checksum, dataInstance.getDataInterfaceIdx);
             % add sigInfo LoggingInfo
          checksum = CGXE.Utils.md5(checksum, dataInstance.SigInfo.LoggingInfo.NameMode);
          checksum = CGXE.Utils.md5(checksum, dataInstance.SigInfo.LoggingInfo.LoggingName);
          checksum = CGXE.Utils.md5(checksum, dataInstance.SigInfo.LoggingInfo.DecimateData);
          checksum = CGXE.Utils.md5(checksum, dataInstance.SigInfo.LoggingInfo.Decimation);
          checksum = CGXE.Utils.md5(checksum, dataInstance.SigInfo.LoggingInfo.LimitDataPoints);
          checksum = CGXE.Utils.md5(checksum, dataInstance.SigInfo.LoggingInfo.MaxPoints);
          checksum = CGXE.Utils.md5(checksum, dataInstance.SigInfo.PropagatedName); 
       end
       
       function loggingName = getEscapedLoggingName(this)                                
           % check signal logging NameMode
           nameMode = this.SigInfo.LoggingInfo.NameMode;
           assert(islogical(nameMode), 'LoggingInfo.NameMode should be logical.');           
           if nameMode
               % use LoggingName
               loggingName = this.SigInfo.LoggingInfo.LoggingName;
               loggingName = rtw.pil.StreamingData.escapeString(loggingName);               
           else               
               % non-custom logging name defers to time series name
               loggingName = this.getEscapedTimeSeriesName;
           end                             
       end
       
       function sids = getEscapedTimerSeriesSID(this)
           sids = this.processSIDChain();
           sids{end+1} = this.SigSID;
       end
       
       function timeSeriesName = getEscapedTimeSeriesName(this)
           % use graphical name - this string is already correctly escaped
           timeSeriesName = this.SigName;
       end
       
       function propSigName = getPropagatedName(this)
           propSigName = this.SigInfo.PropagatedName;
       end
       
       function maxPoints = getMaxPoints(this)                      
           % configure max points
           limitDataPoints = this.SigInfo.LoggingInfo.LimitDataPoints;
           assert(islogical(limitDataPoints), 'LoggingInfo.LimitDataPoints should be logical.');
           if limitDataPoints
               maxPoints = this.SigInfo.LoggingInfo.MaxPoints;
           else
               maxPoints = 0;
           end
       end
       
       function decimation = getDecimation(this)
           % configure decimation
           decimateData = this.SigInfo.LoggingInfo.DecimateData;
           assert(islogical(decimateData), 'LoggingInfo.DecimateData should be logical.');
           if decimateData
               decimation = this.SigInfo.LoggingInfo.Decimation;
           else
               decimation = 1;
           end
       end                
       
       function blockPathStr = getEscapedBlockPath(this)
           blockPathStr = this.processBlockPath(this.SigInfo.BlockPath);
       end
       
       function portIdx = getPortIdx(this)
          portIdx = this.SigInfo.OutputPortIndex;
       end                                                      
       
       function storageSize = getSymbolicNumElements(this, dataElement)
           if this.SigLength > 0 && ~dataElement.isStructElement && ...
                   (~this.BusLoggingSupported || ~this.getIsNVBus())
               % the dimension can be overridden here only when it isn't a
               % bus. I.e., when we select part of a simple array
               % With buses, we always upload the entire bus and then we
               % select the part to log on the host side
               storageSize = num2str(this.SigLength);
           else
               storageSize = dataElement.getSymbolicNumElements;
           end
       end
       
       function symbolicDims = getSymbolicDimensions(this, dataElement)
            if this.SigLength > 0 && ~dataElement.isStructElement && ...
                    (~this.BusLoggingSupported || ~this.getIsNVBus())
                % the dimension can be overridden here only when it isn't a
                % bus. I.e., when we select part of a simple array
                % With buses, we always upload the entire bus and then we
                % select the part to log on the host side
                symbolicDims = {num2str(this.SigLength)};
            else
                symbolicDims = dataElement.getSymbolicDimensions;
            end
       end
       
       function setRegionIndexes(this, index, regionLength, regionOffset)
           % process indexes to make correct assumptions about the logged
           % elements
           this.BusSelectorIndex = index;
           if index > -1
               % this DataInterface logs part of a non-virtual bus but its
               % type still points to the entire struct.
               % This happens because a variable isn't generated for this
               % signal and the generated code makes the signal point to
               % fields in the original structure
               
               % detect the DataElements that are actually logged
               this.DataElementMask = rtw.pil.LoggingUtils.findSubTree(this);
               % find the type of the logged portion of bus
               this.RealHostType = rtw.pil.LoggingUtils.findSubTreeType(this);
           else
               % all DataElements are logged and the logged type matches
               % the DataInterface type
               this.DataElementMask = [];
               this.RealHostType = this.getRTWDataInterface().Type;
           end
           % ignore the region length if
           % - the main type isn't an array (not empty identifier)
           % - multi dimensional array (matrix - RegionLength applies to
           %   arrays only) and it isn't a char array (i.e., string)
           % - the region length is equal to the entire single dimension
           if ~isempty(this.RealHostType.Identifier) || ... 
                   isa(this.RealHostType, 'coder.descriptor.types.Complex') || ... % array of complex numbers
                   isa(this.RealHostType, 'coder.descriptor.types.Pointer') || ... % array of pointers
                   length(this.RealHostType.Dimensions) > 1 || ...
                   (length(this.RealHostType.Dimensions) == 1 && this.RealHostType.BaseType.isChar) || ...
                   (length(this.RealHostType.Dimensions) == 1 && this.RealHostType.Dimensions(1) == regionLength)
               regionLength = -1;
           end
           if regionLength == 1 && this.BusLoggingSupported && ...
                   this.getIsNVBus() && isempty(this.RealHostType.Identifier)
               % if this is an array of non-virtual buses (the identifier
               % is empty) and the regionLength is 1, it means that only an
               % element (which is a non virtual bus) in the array is
               % logged. So, we need to update RealHostType to make it
               % point to the nvbus rather than the array of nvbuses
               this.RealHostType = this.RealHostType.BaseType;
           end
           this.SigOffset = regionOffset;
           this.SigLength = regionLength;
       end

       function res = getBusSelectorIndex(this)
           res = this.BusSelectorIndex;
       end
       
       function res = getRegionLength(this)
           res = this.SigLength;
       end
       
       function res = getRegionOffset(this)
           res = this.SigOffset;
       end
       
       function dataElements = getActualDataElements(this, dataElements)
           if ~isempty(this.DataElementMask)
               assert(length(dataElements), length(this.DataElementMask));
               dataElements(~this.DataElementMask) = [];
           end
       end
   end
   
   methods (Access = 'protected')
       function writeAddress(this, dataElement, writer, targetInterface)    
          % escape *'s which could incorrectly close the comment           
          escapedLoggingNameComment = strrep(this.getEscapedLoggingName, '*', '-*-');           
          dataInterface = this.getRTWDataInterface;
          writer.wComment(['Logging address access: ' escapedLoggingNameComment]);
          expr = this.getExtendedExpression(dataInterface, targetInterface);
          if rtw.connectivity.CodeInfoUtils.isa(dataInterface.Implementation, 'CustomVariable')
              expr = rtw.pil.StreamingData.processCustomTokens(expr);
          end
          if rtw.pil.StreamingData.needToGetVariableAddress(...
                 dataInterface.Implementation)
              writer.wLine('void * dataAddress = (void*)(& %s);', expr);
          else
              writer.wLine('void * dataAddress = (void*)(%s);', expr);
          end
          if this.SigOffset > 0 && ~dataElement.isStructElement
              targetBaseTypeIdentifier = this.getTargetBaseTypeIdentifier(dataElement);
              writer.wLine('/* apply offset */');
              writer.wLine('dataAddress = (void*)((%s*)dataAddress + %d);', targetBaseTypeIdentifier, this.SigOffset);
          end
          % determine if the data is a pointer, in which case the C-API
          % will contain the address of that pointer...
          if dataInterface.Type.isPointer
              targetBaseTypeIdentifier = this.getTargetBaseTypeIdentifier(dataElement);
              % Don't want the address of the pointer variable (double
              % indirection).
              % Dereference it to get the address of the target variable.
              writer.wLine('/* dereference pointer */');
              writer.wLine(['dataAddress = (void *) (*((' targetBaseTypeIdentifier '**) dataAddress));']);
          end          
          if dataElement.isStructElement && ...
                 ~(rtw.connectivity.CodeInfoUtils.isa(dataInterface.Type, 'types.Class') && ...
                       strcmp(dataInterface.Type.Identifier, 'std::string'))
              % add offset
              % g2671235: don't apply offset for std::strings. They look
              % like structured elements but no offsetof is needed to access
              % the internal c_str
              isHost = false;
              byteOffset = dataElement.getStructArrayOffset(isHost);
              if ~isempty(byteOffset)
                  writer.wLine('/* apply struct offset */');
                  writer.wLine(['dataAddress = ((char *) dataAddress) ' byteOffset ';']);
              end
          end           
       end
   end
end
