classdef StateLoggingData < rtw.pil.StreamingData
    %STATELOGGINGDATA is a state data transferred from the target to the
    %host
    
    %   Copyright 2019-2021 The MathWorks, Inc.
    
    properties (SetAccess = 'private', GetAccess = 'private')
        % Block path of the state
        blockPath;
        % if true, the state is uploaded when the simulation step ends.
        % Otherwise, before its execution
        postUpload;
    end
    
    methods
        function this = StateLoggingData(lBlockPath, codeInfoDataInterface, ...
                codeInfoDataInterfaceIdx, clientInterface, codeInfoUtils, ...
                postUpload)
            this@rtw.pil.StreamingData(codeInfoDataInterface, ...
                codeInfoDataInterfaceIdx, ...
                clientInterface, ...
                codeInfoUtils, ...
                'statelog');
            this.blockPath = lBlockPath;
            this.postUpload = postUpload;
        end
        
        function checksum = getChecksumItem(dataInstance, checksum)
          checksum = CGXE.Utils.md5(checksum, dataInstance.getDataInterfaceIdx);
          checksum = CGXE.Utils.md5(checksum, dataInstance.getEscapedLoggingName);
        end
        
        function domain = getDomain(~)
            domain = 'state';
        end
        
        function unit = getEscapedUnit(this)
            unit = this.getRTWDataInterface.Unit;
            unit = rtw.pil.StreamingData.escapeString(unit);
        end
        
        function res = uploadAfterUpdate(this)
            res = this.postUpload;
        end
        
        function loggingName = getEscapedLoggingName(this)
            % check state logging name
            loggingName = this.getRTWDataInterface.GraphicalName;
        end
        
        function sids = getEscapedTimerSeriesSID(this)
            sids = this.processSIDChain();
            sids{end+1} = this.getRTWDataInterface.SID;
        end
        
        function timeSeriesName = getEscapedTimeSeriesName(this)
            % use graphical name - this string is already correctly escaped
            timeSeriesName = this.getRTWDataInterface.GraphicalName;
        end
        
        function propSigName = getPropagatedName(~)
            propSigName = '';
        end
        
        function maxPoints = getMaxPoints(~)
            maxPoints = 0;
        end
        
        function decimation = getDecimation(~)
            decimation = 1;
        end
        
        function blockPathStr = getEscapedBlockPath(this)
            blockPathStr = this.processBlockPath(this.blockPath);
        end
        
        function portIdx = getPortIdx(~)
            portIdx = 0;
        end

        function type = getType(this)
            % only process the Implementation type (not the DataInterface
            % type)
            type = this.getRTWDataInterface.Implementation.CodeType;
        end     
        
        % override getHostType to disable type separation which is not
        % supported for LoggingData
        function type = getHostType(this)
            type = this.getRTWDataInterface.Type;
        end  

       function storageSize = getSymbolicNumElements(~, dataElement)
           storageSize = dataElement.getSymbolicNumElements;
       end
       
       function symbolicDims = getSymbolicDimensions(~, dataElement)
           symbolicDims = dataElement.getSymbolicDimensions;
       end
       
       function res = getBusSelectorIndex(~)
           % with state logging, a variable is always generated for the
           % state so, the DataInterface refers the correct type always
           res = -1;
       end
       
       function res = getRegionLength(~)
           % with state logging, a variable is always generated for the
           % state so, the DataInterface refers the correct type always
           res = -1;
       end
       
       function res = getRegionOffset(~)
           % with state logging, a variable is always generated for the
           % state so, the DataInterface refers the correct type always
           res = -1;
       end
       
       function setRegionIndexes(this, index, length, offset) %#ok<INUSD>
           % with state logging, a variable is always generated for the
           % state so, the DataInterface refers the correct type always
           assert(false, 'This method should never be called for states');
       end
       
       function type = getRealTypeForNVBus(this)
           % with state logging, a variable is always generated for the
           % state so, the DataInterface refers the correct type always
           type = this.getRTWDataInterface().Type;
       end
       
       function dataElements = getActualDataElements(this, dataElements) %#ok<INUSL>
           % with state logging, a variable is always generated for the
           % state so, the DataInterface refers the correct type always
       end
    end
    
    methods (Access = 'protected')
        function writeAddress(this, dataElement, writer, targetInterface)
            % escape *'s which could incorrectly close the comment
            escapedLoggingNameComment = strrep(this.getEscapedLoggingName, '*', '-*-');
            writer.wComment(['State Logging address access: ' escapedLoggingNameComment]);
            dataInterface = this.getRTWDataInterface;
            impl = dataInterface.Implementation;
            expr = this.getExtendedExpression(dataInterface, targetInterface);
            if rtw.pil.StreamingData.needToGetVariableAddress(impl)
                expr = ['&' expr];
            end
            if rtw.connectivity.CodeInfoUtils.isa(impl, 'CustomVariable')
                expr = rtw.pil.StreamingData.processCustomTokens(expr);
            end
            if rtw.pil.StreamingData.isConstantReference(dataInterface)
                % if the state is from a getter which returns a constant
                % reference to the DWork, cast its constantness away
                if dataInterface.Implementation.Type.isMatrix
                    accessElementCode = '[0]';
                    targetBaseTypeIdentifier = dataInterface.Implementation.CodeType.BaseType.Identifier;
                else
                    accessElementCode = '';
                    targetBaseTypeIdentifier = dataInterface.Implementation.CodeType.Identifier;
                end
                expr = ['const_cast<' targetBaseTypeIdentifier '*>(' expr accessElementCode ')'];
            end
            writer.wLine('void * dataAddress = (void*)(%s);', expr);
            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.wComment('dereference pointer');
                writer.wLine('dataAddress = (void *) (*((%s**) dataAddress));', targetBaseTypeIdentifier);
            end
            if dataElement.isStructElement
                % add offset
                isHost = false;
                byteOffset = dataElement.getStructArrayOffset(isHost);
                if ~isempty(byteOffset)
                    writer.wComment('apply struct offset');
                    writer.wLine('dataAddress = ((char *) dataAddress)%s;', byteOffset);
                end
            end
        end
    end
end
