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

%   Copyright 2010-2019 The MathWorks, Inc.

   properties (SetAccess = 'private', GetAccess = 'private')                
        % SignalLoggingInfo object describing the signal logging config
        SigInfo;        
        SigSID;
        SigName;
        SigOffset;
        SigLength;
        
        UseMF0Logging;
   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.UseMF0Logging = codeInfoUtils.UseMF0Logging;
           assert(sigInfo.LoggingInfo.DataLogging, ...
                  'SignalLoggingInfo must be configured for DataLogging.');
       end   
       
       function type = getType(this)
           % for LoggingData, only process the DataInterface type, not the
           % Implementation type
           if this.UseMF0Logging
               type = this.getRTWDataInterface.Implementation.CodeType;
           else
               type = this.getRTWDataInterface.Type;
           end
       end     
       
       % override getHostType to disable type separation which is not
       % supported for LoggingData
       function type = getHostType(this)
           type = this.getRTWDataInterface.Type;
       end  
                    
       function domain = getDomain(~)
           domain = '';
       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 sid = getEscapedTimerSeriesSID(this)
           if this.UseMF0Logging
               sid = this.SigSID;
           else
               sid = this.getRTWDataInterface.SID;
           end
       end
       
       function timeSeriesName = getEscapedTimeSeriesName(this)
           % use graphical name - this string is already correctly escaped
           if this.UseMF0Logging
               timeSeriesName = this.SigName;
           else
               timeSeriesName = this.getRTWDataInterface.GraphicalName;
           end
       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 blockPath = getEscapedBlockPath(this)
          assert(this.SigInfo.BlockPath.getLength == 1, 'multiple level block paths not supported');          
          blockPath = this.SigInfo.BlockPath.getBlock(1);
          blockPath = rtw.pil.StreamingData.escapeString(blockPath);
       end
       
       function portIdx = getPortIdx(this)
          portIdx = this.SigInfo.OutputPortIndex;
       end                                                      
       
       function storageSize = getSymbolicNumElements(this, dataElement)
           if this.UseMF0Logging && this.SigLength > 0 && ~dataElement.isStructElement
               storageSize = num2str(this.SigLength);
           else
               storageSize = dataElement.getSymbolicNumElements;
           end
       end
       
       function symbolicDims = getSymbolicDimensions(this, dataElement)
            if this.UseMF0Logging && this.SigLength > 0 && ~dataElement.isStructElement
                symbolicDims = {num2str(this.SigLength)};
            else
                symbolicDims = dataElement.getSymbolicDimensions;
            end
       end
       
       function setBaseRegion(this, offset, length)
           this.SigOffset = offset;
           this.SigLength = length;
       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;
          if this.UseMF0Logging
              writer.wComment(['Logging address access: ' escapedLoggingNameComment]);
              rtw.pil.OwnerAccessor.setOwner(dataInterface.Implementation, 'PIL');
              if dataInterface.Implementation.Type.isPointer && ~dataInterface.Implementation.Type.isMatrix
                  % the variable is a proper pointer to the memory area to
                  % upload
                  writer.wLine(['void * dataAddress = (void*)(' targetInterface.getExpression(dataInterface.Implementation) ');']);
              else
                  writer.wLine(['void * dataAddress = (void*)(&' targetInterface.getExpression(dataInterface.Implementation) ');']);
              end
          else
              writer.wComment(['C API address access: ' escapedLoggingNameComment]);
              writer.wLine(['void * dataAddress = ' targetInterface.getExpression(dataInterface.Implementation) ';']);
          end
          if this.UseMF0Logging && 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
              % add offset
              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
