classdef StreamingData < rtw.pil.CodeInfoData  
% STREAMINGDATA is a generic data to be transferred between host and target
%(e.g., signal and state logging)

%   Copyright 2019-2021 The MathWorks, Inc.

    properties(Access = private)
        NameMapping;
        % IsNVBus must be initialized as empty and it will be set the
        % first that is queried
        IsNVBus = [];
    end

    properties(Access = protected)
        ParentDataInterfaces;
        % features
        BusLoggingSupported;
        ChildLoggingSupported;
    end

   methods 
       function this = StreamingData(codeInfoDataInterface, ...
                                   codeInfoDataInterfaceIdx, ...
                                   clientInterface, ...
                                   codeInfoUtils, ...
                                   codeInfoType)           
           this@rtw.pil.CodeInfoData(codeInfoDataInterface, ...
               codeInfoType, ...
               codeInfoDataInterfaceIdx, ...
               clientInterface, ...
               codeInfoUtils);
           this.NameMapping = [];
           this.BusLoggingSupported = coder.internal.connectivity.featureOn('BusLogging');
           this.ChildLoggingSupported = codeInfoUtils.ChildLoggingSupported;
       end   
       
       % write assignments from the target variable to the data transfer variable
       function writeOutputAssignment(this, dataElement, writer, targetInterface)
           if ~dataElement.isGetSetViaPointer
               % get assignment data info
               [dataType, identifier] = this.getAssignmentDataInfo(dataElement);
               % open any required loops
               dataElement.writeStructArrayHeader(writer);
               writer.wBlockStart('');
               this.writeAddress(dataElement, writer, targetInterface);
               % get storage info
               storageSize = dataElement.getNumElements;
               targetBaseTypeIdentifier = this.getTargetBaseTypeIdentifier(dataElement);
               for index = 0:storageSize - 1
                   dataExpression = dataElement.getAssignmentDataElementExpression(identifier, index);
                   % add the offset to the base address
                   indexStr = int2str(index);
                   expression = ['(((' targetBaseTypeIdentifier '*) dataAddress)[' indexStr '])'];
                   writer.writeLine([dataExpression ' = (' dataType ') ' expression ';']);
               end
               writer.wBlockEnd;
               % close any required loops
               dataElement.writeStructArrayTrailer(writer);
           end
       end
       
       function samplePeriod = getSamplePeriod(this)
           timing = this.getRTWDataInterface.Timing;
           % add TRIGGERED timing mode once g826111 is implemented
           switch (timing.TimingMode)
               case 'ASYNCHRONOUS'
                   samplePeriod = message('simulation_data_repository:sdr:AsyncSampleTime').getString();
               case 'INHERITED'
                   samplePeriod = message('simulation_data_repository:sdr:InheritedSampleTime').getString();
               case 'ONESHOT'
                   samplePeriod = message('simulation_data_repository:sdr:ParamSampleTime').getString();
               case {'INITIALIZE', 'RESET', 'RESETWITHINIT', 'TERMINATE'}
                   samplePeriod = message('simulation_data_repository:sdr:EventSampleTime').getString();
               otherwise
                   samplePeriod = timing.SamplePeriod;
                   if isempty(samplePeriod) || isnan(samplePeriod) || isinf(samplePeriod)
                       samplePeriod = 0;
                   end
                   samplePeriod = num2str(samplePeriod);
           end
           samplePeriod = ['"' samplePeriod '"'];
       end
       
       function res = getIsNVBus(this)
           % return true if this DataInterface is a non-virtual bus, false
           % otherwise
           if isempty(this.IsNVBus)
               % get all DataElements that are actually logged for this
               % DataInterface
               dataElements = this.getHostDataElements();
               dataElements = rtw.pil.LoggingUtils.filterOutUselessDataElements(dataElements);
               dataElements = this.getActualDataElements(dataElements);
               numElements = length(dataElements);
               % check if there is any DataElement which is part of a
               % structured object
               this.IsNVBus = false;
               for i=1:numElements
                   this.IsNVBus = dataElements(i).hasStructureElement();
                   if this.IsNVBus
                       break;
                   end
               end
           end
           res = this.IsNVBus;
       end
       
       function res = getUploadIdentifier(this)
           dataInterface = this.getRTWDataInterface;
           impl = dataInterface.Implementation;
           res = '';
           if rtw.connectivity.CodeInfoUtils.isa(impl, 'AutosarMemoryExpression')
               impl = impl.BaseRegion;
           elseif rtw.connectivity.CodeInfoUtils.isa(impl, 'AutosarSenderReceiver') || ...
                   rtw.connectivity.CodeInfoUtils.isa(impl, 'BasicAccessFunctionExpression')
               return;
           end
           basePath = '';
           for parentDataInterface = this.ParentDataInterfaces
               parentPath = rtw.pil.StreamingData.getUploadIdentifierInternal(...
                   parentDataInterface.Implementation);
               basePath = sprintf('%s%s.', basePath, parentPath);
           end
           currentPath = rtw.pil.StreamingData.getUploadIdentifierInternal(impl);
           res = [basePath currentPath];
           % we cannot use getExpression because the owner can still be
           % empty and setting the owner will lead to problems because the
           % variable wouldn't be created in xil_interface.c if required
       end
       
       function ret = getNameMapping(this)
           ret = this.NameMapping;
       end
       
       function setNameMapping(this, mapping)
           this.NameMapping = mapping;
       end

       function setParentDataInterfaces(this, parents)
           this.ParentDataInterfaces = parents;
       end
   end
   
   methods(Sealed)
       function checksum = getChecksum(LoggingDataInstances)
          checksum = CGXE.Utils.md5([]);
          % support array of LoggingData objects
          for loggingDataIdx = 1:length(LoggingDataInstances)
              loggingData = LoggingDataInstances(loggingDataIdx);
              checksum = loggingData.getChecksumItem(checksum);
          end           
       end
   end
   
   methods (Abstract, Access = public)
       checksum = getChecksumItem(dataInstance, checksum);
       decimation = getDecimation(this);
       maxPoints = getMaxPoints(this);
       blockPath = getEscapedBlockPath(this);
       portIdx = getPortIdx(this);
       propSigName = getPropagatedName(this);
       timeSeriesName = getEscapedTimeSeriesName(this);
       sid = getEscapedTimerSeriesSID(this);
       loggingName = getEscapedLoggingName(this);
       type = getType(this);
       type = getHostType(this);
       domain = getDomain(this);
       unit = getEscapedUnit(this);
       res = getBusSelectorIndex(this);
       res = getRegionLength(this);
       res = getRegionOffset(this);
       setRegionIndexes(this, index, length, offset);
       % get the DataElements that are actually logged - for NVBuses it can
       % be a subset
       dataElements = getActualDataElements(this);
       % for NVBuses, get the type of bus that is actually logged
       type = getRealTypeForNVBus(this);
   end

   methods (Static, Access = 'private')
       function res = getUploadIdentifierInternal(impl)
           res = '';
           while isprop(impl, 'BaseRegion')
               if isprop(impl, 'ElementIdentifier')
                   res = sprintf('%s%s.', res, impl.ElementIdentifier);
               end
               impl = impl.BaseRegion;
           end
           if ~isprop(impl, 'Identifier')
               return;
           end
           res = [res impl.Identifier];
       end
   end
   
   methods (Static, Access = 'protected')
       
       function isConst = isConstantReference(dataInterface)
           % return true if the dataInterface refers to a variable which is
           % part of a constant reference. This happens only with C++ class
           % and it is independent from internal visibility
           isConst = false;
           impl = dataInterface.Implementation;
           while(true)
               if isa(impl, 'coder.descriptor.ClassMethodExpression')
                   isConst = true;
                   break;
               end
               if ~isprop(impl, 'BaseRegion')
                   break;
               end
               impl = impl.BaseRegion;
           end
       end
       
       function escapedString = escapeString(inputString)
           % escape input string for use as a C string
           %
           % escape \'s, e.g. \ -> \\, \n -> \\n
           escapedString = strrep(inputString, '\', '\\');
           % replace newline chars with "\n"
           escapedString = strrep(escapedString, newline, '\n');           
           % escape "'s
           escapedString = strrep(escapedString, '"', '\"');
       end

       function expr = processCustomTokens(expr)
           % default to the first element of the array/matrix
           elementIndex = '[0]';
           expr = regexprep(expr, ...
               regexptranslate('escape', '[$(INDEX_TOKEN_0)]'), elementIndex, 'once');
       end

       function res = needToGetVariableAddress(lImpl)
           lPurePointer = lImpl.Type.isPointer && ~lImpl.Type.isMatrix;
           if (lPurePointer) || ...
                   (lImpl.Type.isMatrix && rtw.connectivity.CodeInfoUtils.isa(lImpl, 'AutosarMemoryExpression') && ...
                   lImpl.DataAccessMode ~= coder.descriptor.AutosarExpressionMode.StaticMemory)
               % the variable is a proper pointer to the memory area to
               % upload
               res = false;
           else
               res = true;
           end
       end
   end

   methods(Access = protected)
       function expr = getExtendedExpression(this, dataInterface, targetInterface)
           % generate a full expression for a DataInterface, including
           % parents

           % get expression for the current DataInterface
           impl = dataInterface.Implementation;
           targetInterface.setImplementationOwner(impl, 'PIL');
           expr = targetInterface.getExpression(impl);
           if ~isprop(impl, 'BaseRegion')
               % the element isn't a structure so we don't need to access
               % its parents
               return;
           end

           % add expression from parents
           numParents = length(this.ParentDataInterfaces);
           for i=numParents:-1:1
               % find first element in the BaseRegion chain
               while(isprop(impl, 'BaseRegion'))
                   impl = impl.BaseRegion;
               end
               % replace first variable in the chain with the whole parent
               % expression
               variableToReplace = impl.Identifier;
               assert(startsWith(expr, variableToReplace), 'The variable to replace is not in the expression');
               parentImpl = this.ParentDataInterfaces(i).Implementation;
               targetInterface.setImplementationOwner(parentImpl, 'PIL');
               assert(strcmp(impl.Type.Identifier, parentImpl.Type.Identifier), 'Different types');
               parentExpr = parentImpl.getExpression();
               newExpr = [parentExpr, expr(length(variableToReplace)+1:end)];
               expr = newExpr;
               % do the same with parent and grandparent
               impl = parentImpl;
           end
       end

       function sidChain = processSIDChain(this)
           % return a cell array with the parent SIDs
           if this.ChildLoggingSupported
               numParents = length(this.ParentDataInterfaces);
               sidChain = cell(1, numParents);
               for i=1:numParents
                   sidChain{i} = this.ParentDataInterfaces(i).SID;
               end
           else
               sidChain = cell(1, 0);
           end
       end

       function blockPathStr = processBlockPath(this, blockPath)
           % return a cell array with the full block path
           if this.ChildLoggingSupported
               blockPathStr = cell(1, blockPath.getLength());
               for i = 1:blockPath.getLength()
                   str = blockPath.getBlock(i);
                   str = rtw.pil.StreamingData.escapeString(str);
                   blockPathStr{i} = str;
               end
           else
               assert(blockPath.getLength == 1, 'multiple level block paths not supported');
               blockPathStr = {rtw.pil.StreamingData.escapeString(blockPath.getBlock(1))};
           end
       end

       function res = isPartOfGetter(~, impl)
           res = false;
           if isprop(impl, 'BaseRegion')
               impl = impl.BaseRegion;
           end
           while true
               res = isa(impl, 'coder.descriptor.ClassMethodExpression') && ~isempty(impl.Getter);
               if res
                   return;
               end
               if ~isprop(impl, 'BaseRegion')
                   return;
               end
           end
       end
   end
end
