classdef (Hidden) DebuggerBreakpointRtIOStream < rtw.connectivity.RtIOStream
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2020-2023 The MathWorks, Inc.
    
    properties (Access=private)
        DebuggerService;
        
        ReadBufferVariable;
        WriteBufferVariable;
        ReadSizeAvailableVariable;
        
        WriteBreakpoint;
        ReadBreakpoint;
        
        DataRead = [];
        
        IsTargetBufferProcessingRequired = false;
        
        ConvertToMemUnitType;
        ByteEndianness;
        SizeTNumberOfMemUnits;
        SizeTMATLABType;
    end

    methods
        
        function setApplicationService(this, debuggerService, options)            
            arguments
                this
                debuggerService (1, 1) coder.connectivity.targetservices.IDebuggerAutomationRtIOStreamApplicationService
                options.AddressGranularity (1, 1) double = 8;
                options.SizeTNumberOfBits (1,1) double = 64;
                options.ByteEndianness (1,1) string = "LittleEndian";
            end
            
            this.DebuggerService = debuggerService;
            this.ReadBufferVariable = this.DebuggerService.getReadBufferVariable();
            this.WriteBufferVariable = this.DebuggerService.getWriteBufferVariable();
            this.ReadSizeAvailableVariable = this.DebuggerService.getReadSizeAvailableVariable();
            
            this.WriteBreakpoint = this.DebuggerService.getWriteBreakpoint();
            this.ReadBreakpoint = this.DebuggerService.getReadBreakpoint();
            
            this.ConvertToMemUnitType = eval(sprintf('@uint%d', options.AddressGranularity));
            this.ByteEndianness = options.ByteEndianness;
            
            % Assert that we can represent a size_t payload as a stream of
            % memunits, which is required by the rtiostream implementation            
            assert(mod(options.SizeTNumberOfBits, options.AddressGranularity) == 0, ...
                'size_t for the target must be divisible by the address granularity');
            
            this.SizeTNumberOfMemUnits = uint32(options.SizeTNumberOfBits / options.AddressGranularity);
            this.SizeTMATLABType = sprintf('uint%d', options.SizeTNumberOfBits);
        end
        
        function sizeSent = send(this, data)            
            
            % Wait to pause at the breakpoint where we will write data to
            % the target
            this.waitForBreakpointHit(this.WriteBreakpoint);
            
            % Write data to the target
            sizeSent = this.DebuggerService.writeToMemoryStream(this.WriteBufferVariable, uint64(data));
            this.IsTargetBufferProcessingRequired = true;                  
            
            % Process the data straight away, no need to wait.
            this.processTargetBuffers();   
        end
        
        function data = recv(this, receiveSize)
            
            % If there's no data available from the last read then we need
            % to grab more from the target
            if isempty(this.DataRead)
                
                % Make sure the data buffers have been processed before
                % next read
                this.processTargetBuffers();
                
                % Wait until we've hit the receive breakpoint
                this.waitForBreakpointHit(this.ReadBreakpoint);                
                
                % Read the new data to the buffer
                this.DataRead = this.readData();
            end
            
            dataAvailable = length(this.DataRead);
            
            % If there's more data available than requested, just return
            % the requested amount. If not send all the available data.
            if receiveSize < dataAvailable                
                
               data = this.DataRead(1:receiveSize);
               this.DataRead = this.DataRead(receiveSize+1:end);               
            else
                
               data = this.DataRead;
               this.DataRead = [];
            end            
            
        end
        
        function close(~)
            % No op
        end              
    end
    
    methods (Access = 'protected')
        function streamID = openHook(~, ~)
            % No op. Return XIL app ID of 1.
            streamID = 1;
        end
    end
    
    methods (Access = private)
        
        function dataRead = readData(this)            
            
            dataRead = [];                
            
            % read the payload from a size_t on target
            readSizeAvailableFlagSize = this.SizeTNumberOfMemUnits;
            dataAvail = this.DebuggerService.readFromMemoryStream(this.ReadSizeAvailableVariable, readSizeAvailableFlagSize);
            
            if this.ByteEndianness == "BigEndian"
                dataAvail = flip(dataAvail);
            end
            
            payload = typecast(uint8(dataAvail), this.SizeTMATLABType);
            
            if ((payload) > 0)
                dataRead = this.DebuggerService.readFromMemoryStream(this.ReadBufferVariable, payload);
            end
                
            this.IsTargetBufferProcessingRequired = true;
            
            dataRead = this.ConvertToMemUnitType(dataRead);
        end
        
        function waitForBreakpointHit(this, breakpoint)
            waitCounterSecs = this.DebuggerService.getBreakpointPollingWaitTime();
            pauseTime = this.DebuggerService.getBreakpointPollingInterval();
            while (~this.DebuggerService.stoppedAtBreakpoint(breakpoint))
                
                if (waitCounterSecs <= 0)
                    error(message('Connectivity:target:TimeoutWaitingForBreakpoint'));
                end
                
                pause(pauseTime)
                waitCounterSecs = waitCounterSecs - pauseTime;
            end
        end 
        
        function processTargetBuffers(this)
            
            if this.IsTargetBufferProcessingRequired
                this.DebuggerService.continueExecution();
            end
            
            this.IsTargetBufferProcessingRequired = false;
        end
    end
end
