classdef CaptureProbeTarget < coder.profile.TargetInterface
    % CAPTUREPROBETARGET 

    % Copyright 2019 The MathWorks, Inc.

    properties(Access=private)
        XCPEventID;
        
        CaptureFcnName = '';
    end

    properties(Constant, Access=public)
        StartInternalMethod = 'captureModeStart';
        EndInternalMethod = 'captureModeEnd';
    end
    
    methods
        function this = CaptureProbeTarget (captureFcnName)
            this = this@coder.profile.TargetInterface();
            this.CaptureFcnName = captureFcnName;
        end
        
        function targetWriteProbeExportedFcnPrototypes(this, writer, ~)
            lCodeInstrSizeCDataType = ...
                coder.profile.ExecTimeConfig.getCodeInstrNumElsCDataType...
                (this.TargetWordSize);
            
            writer.wComment('Request upload of metrics evaluated on target');
            writer.writeLine('void %s(%s sectionId);', ...
                this.CaptureFcnName, lCodeInstrSizeCDataType);
            
            writer.wComment('Update methods');
            writer.writeLine('void %s(%s sectionId);', ...
                coder.profile.CaptureProbeTarget.StartInternalMethod, ...
                lCodeInstrSizeCDataType);
            writer.writeLine('void %s(%s sectionId);', ...
                coder.profile.CaptureProbeTarget.EndInternalMethod, ...
                lCodeInstrSizeCDataType);
            
            writer.newLine;
        end
        
        function targetWriteSectionFcns(this, writer, targetCollectDataFcnName, executed)
            if ~executed && ~isempty(this.ProfilingTimer)

                lTimerType = this.ProfilingTimer.getTimerCType;
                nSections = this.GlobalRegistry.TotalTimeProbe;
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('#include "%s"', this.GlobalRegistry.XCPMainHeaderFile);
                    writer.newLine;
                end
                
                writer.writeLine('#define MAX_EXECUTION_SECTION_ID %d', nSections);
                writer.newLine;
                
                % this should be written in the opposite way: discard the
                % elements you don't want
                mapIds = this.GlobalRegistry.getTimeProbeRangeIds();
                [~, idx] = sort(mapIds(:, 2));
                mapIds = mapIds(idx,:);
                
                dim = size(mapIds);
                nElements = dim(1);
                mappingRule = '';
                localCounter = mapIds(1,2);
                for i = 1:nElements
                    data = mapIds(i,:);
                    numProbes = data(1);
                    offset = data(2);
                    checkPart = ['(X) > ' num2str(offset) ' && (X) <= ' num2str(numProbes + offset)];
                    if offset == localCounter
                        trueCase = 'X';
                    else
                        trueCase = ['(X) - ' num2str(offset - localCounter)];
                    end
                    mappingRule = [mappingRule '((' checkPart ') ? (' trueCase ') : ']; %#ok<AGROW>
                    localCounter = localCounter + numProbes;
                end
                mappingRule = [mappingRule ' 0'];
                for i = 1:nElements
                    mappingRule = [mappingRule ')']; %#ok<AGROW>
                end
                writer.writeLine(['#define MAP_CAPTURE_IDS(X) ' mappingRule]);
                
                writer.writeLine('static %s xsd_xil_last_start_time[MAX_EXECUTION_SECTION_ID] = {0};', ...
                                 lTimerType);
                lCodeInstrSizeCDataType = ...
                    coder.profile.ExecTimeConfig.getCodeInstrNumElsCDataType...
                    (this.TargetWordSize);
                
                writer.newLine;
                if ~this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('static %s xsd_xil_capture_mode_max[MAX_EXECUTION_SECTION_ID] = {0};', lTimerType);
                    writer.writeLine('static %s xsd_xil_capture_mode_avg[MAX_EXECUTION_SECTION_ID] = {0};', lTimerType);
                    writer.writeLine('static %s xsd_xil_capture_mode_calls[MAX_EXECUTION_SECTION_ID] = {0};', lTimerType);
                    writer.newLine;
                end

                uploadPrototype = sprintf('void %s(%s sectionId)', ...
                    this.CaptureFcnName, lCodeInstrSizeCDataType);
                writer.wBlockStart(uploadPrototype);
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('writeIDsInProfilingBuffer(sectionId, sectionId+1, sectionId+2);');
                else
                    writer.writeLine...
                        (['%s((void *)(&xsd_xil_capture_mode_max), (%s) ' ...
                        '(MAX_EXECUTION_SECTION_ID * sizeof(%s))'...
                        ', sectionId);'], ...
                        targetCollectDataFcnName, ...
                        lCodeInstrSizeCDataType, ...
                        lTimerType);
                    writer.writeLine...
                        (['%s((void *)(&xsd_xil_capture_mode_avg), (%s) ' ...
                        '(MAX_EXECUTION_SECTION_ID * sizeof(%s))'...
                        ', sectionId + 1);'], ...
                        targetCollectDataFcnName, ...
                        lCodeInstrSizeCDataType, ...
                        lTimerType);
                    writer.writeLine...
                        (['%s((void *)(&xsd_xil_capture_mode_calls), (%s) ' ...
                        '(MAX_EXECUTION_SECTION_ID * sizeof(%s))'...
                        ', sectionId + 2);'], ...
                        targetCollectDataFcnName, ...
                        lCodeInstrSizeCDataType, ...
                        lTimerType);
                end
                writer.wBlockEnd;

                if this.SingleThreadTiming || ~this.GlobalRegistry.RequireXCPSupport
                    timerValueExpr = 'xsd_xil_timer_corrected';
                else
                    timerValueExpr =  this.ProfilingTimer.getReadTimerExpression;
                end
                startMethod = sprintf('void %s(%s sectionId)', ...
                    coder.profile.CaptureProbeTarget.StartInternalMethod, ...
                    lCodeInstrSizeCDataType);
                writer.wBlockStart(startMethod);
                writer.writeLine(sprintf('%s mappedId = MAP_CAPTURE_IDS(sectionId);', lCodeInstrSizeCDataType));
                writer.wBlockStart('if (mappedId > 0)');
                writer.writeLine('xsd_xil_last_start_time[mappedId - 1] = %s;', timerValueExpr);
                writer.wBlockEnd;
                writer.wBlockEnd;
                
                endMethod = sprintf('void %s(%s sectionId)', ...
                    coder.profile.CaptureProbeTarget.EndInternalMethod, ...
                    lCodeInstrSizeCDataType);
                writer.wBlockStart(endMethod);
                writer.writeLine(sprintf('%s mappedId = MAP_CAPTURE_IDS(sectionId);', lCodeInstrSizeCDataType));
                writer.writeLine(sprintf('%s* pCaptureValue;', lTimerType));
                writer.writeLine('%s turnaroundTime;', lTimerType);
                writer.wBlockStart('if (mappedId > 0)');
                writer.writeLine('mappedId = mappedId - 1;');
                writer.wComment('Update maximum execution');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('pCaptureValue = getMaxElementInProfilingBuffer(mappedId);');
                else
                    writer.writeLine('pCaptureValue = &(xsd_xil_capture_mode_max[mappedId]);');
                end
                if strcmp(this.GlobalRegistry.ProfilingTimer.getCountDirection, 'down') && ...
                        this.GlobalRegistry.RequireXCPSupport
                    % for SIL/PIL, the timer freeze/unfreeze methods
                    % already take care of decrementing counters
                    writer.writeLine('turnaroundTime = xsd_xil_last_start_time[mappedId] - %s;', timerValueExpr);
                else
                    writer.writeLine('turnaroundTime = %s - xsd_xil_last_start_time[mappedId];', timerValueExpr);
                end
                writer.wBlockStart('if (turnaroundTime > *pCaptureValue)');
                writer.writeLine('*pCaptureValue = turnaroundTime;');
                writer.wBlockEnd;
                writer.wComment('Try to update to total execution counter');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('pCaptureValue = getAvgElementInProfilingBuffer(mappedId);');
                else
                    writer.writeLine('pCaptureValue = &(xsd_xil_capture_mode_avg[mappedId]);');
                end
                writer.wBlockStart('if ((*pCaptureValue + turnaroundTime) > *pCaptureValue)');
                writer.writeLine('*pCaptureValue = *pCaptureValue + turnaroundTime;');
                writer.wComment('Update total number of calls');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('pCaptureValue = getCallsElementInProfilingBuffer(mappedId);');
                else
                    writer.writeLine('pCaptureValue = &(xsd_xil_capture_mode_calls[mappedId]);');
                end
                writer.writeLine('*pCaptureValue = *pCaptureValue + 1;');
                writer.wBlockEnd;
                writer.wBlockEnd;
                writer.wBlockEnd;
                
                writer.newLine;
            end
        end

        function targetWriteInit(~, ~)
        end

        function targetWriteXCPInitAndValidate(this, eventID)
            this.XCPEventID = eventID;
        end

        function targetWriteXCPManagementSource(this, writer, ~, ~, addHeaders)
            lBufferName = this.GlobalRegistry.XCPBufferConfig.BufferName;
            lCodeInstrSizeCDataType = coder.profile.ExecTimeConfig.getCodeInstrNumElsCDataType(this.TargetWordSize);
            
            writer.wLine('#include "rtwtypes.h"');            
            writer.wLine('#include "%s"', this.GlobalRegistry.XCPMainHeaderFile);
            for i=1:length(addHeaders)
                [~, fname, fext] = fileparts(addHeaders{i});
                writer.wLine('#include "%s%s"', fname, fext);
            end
            
            % define area
            writer.wLine('#define XCP_PROFILING_EVENT %d', this.XCPEventID);
            
            nSections = this.GlobalRegistry.TotalTimeProbe;
            writer.wLine('#define MAX_EXECUTION_SECTION_ID %d', nSections);
            lBufferSize = sprintf('(sizeof(%s)*3 + sizeof(%s)*MAX_EXECUTION_SECTION_ID*3)', ...
                this.IdentifierCDataType, this.ProfilingTimer.getTimerCType);
            writer.wLine('#define XCP_PROFILING_BUFFER_SIZE %s', lBufferSize);
            
            % global variables
            writer.writeLine('uint8_T %s[XCP_PROFILING_BUFFER_SIZE] = {0};', lBufferName);
            
            writer.wBlockStart('XcpErrorCode uploadOnTargetProfilingData(void)');
            str = [this.CaptureFcnName '_', this.GlobalRegistry.Model];
            str = coder.profile.private.createProbeIdentifier(str, coder.profile.CaptureProbeComponentRegistry.MaximumIdLength);
            writer.writeLine('%s(1);', str);
            writer.writeLine('return xcpEvent(XCP_PROFILING_EVENT);');
            writer.wBlockEnd;
            
            lTimerType = this.ProfilingTimer.getTimerCType;
            writer.wBlockStart('%s* getMaxElementInProfilingBuffer(%s idx)', ...
                lTimerType, lCodeInstrSizeCDataType);
            pos = sprintf('sizeof(%s)', lCodeInstrSizeCDataType);
            writer.writeLine('%s* addr = (%s*)(%s + %s);', lTimerType, ...
                lTimerType, lBufferName, pos);
            writer.writeLine('return addr+idx;');
            writer.wBlockEnd;
            
            writer.wBlockStart('%s* getAvgElementInProfilingBuffer(%s idx)', ...
                lTimerType, lCodeInstrSizeCDataType);
            pos = sprintf('sizeof(%s)+(sizeof(%s) + sizeof(%s)*MAX_EXECUTION_SECTION_ID)', ...
                lCodeInstrSizeCDataType, lCodeInstrSizeCDataType, lTimerType);
            writer.writeLine('%s* addr = (%s*)(%s + %s);', ...
                lTimerType, lTimerType, lBufferName, pos);
            writer.writeLine('return addr+idx;');
            writer.wBlockEnd;
            
            writer.wBlockStart('%s* getCallsElementInProfilingBuffer(%s idx)', ...
                lTimerType, lCodeInstrSizeCDataType);
            pos = sprintf('sizeof(%s)+(sizeof(%s) + sizeof(%s)*MAX_EXECUTION_SECTION_ID)*2', ...
                lCodeInstrSizeCDataType, lCodeInstrSizeCDataType, lTimerType);
            writer.writeLine('%s* addr = (%s*)(%s + %s);', ...
                lTimerType, lTimerType, lBufferName, pos);
            writer.writeLine('return addr+idx;');
            writer.wBlockEnd;
            
            writer.wBlockStart('void writeIDsInProfilingBuffer(%s idxMax, %s idxAvg, %s idxCalls)', ...
                lCodeInstrSizeCDataType, lCodeInstrSizeCDataType, lCodeInstrSizeCDataType);
            writer.writeLine('%s* addr = (%s*)(%s);', ...
                lCodeInstrSizeCDataType, lCodeInstrSizeCDataType, lBufferName);
            writer.writeLine('*addr = idxMax;');
            pos = sprintf('(sizeof(%s) + sizeof(%s)*MAX_EXECUTION_SECTION_ID)', lCodeInstrSizeCDataType, lTimerType);
            writer.writeLine('addr = (%s*)(%s + %s);', lCodeInstrSizeCDataType, lBufferName, pos);
            writer.writeLine('*addr = idxAvg;');
            pos = sprintf('(sizeof(%s)*2 + sizeof(%s)*MAX_EXECUTION_SECTION_ID*2)', lCodeInstrSizeCDataType, lTimerType);
            writer.writeLine('addr = (%s*)(%s + %s);', lCodeInstrSizeCDataType, lBufferName, pos);
            writer.writeLine('*addr = idxCalls;');
            writer.wBlockEnd;
        end
        
        function targetWriteXCPManagementHeader(this, writer)
            writer.writeLine('#ifndef XCP_PROFILING_H');
            writer.writeLine('#define XCP_PROFILING_H');
            % this must be removed with new XCP API
            writer.wComment('Include main XCP header with XcpErrorCode type definition');
            writer.writeLine('#include "xcp.h"');
            % this file contains the prototypes of the instrumentation API
            writer.wComment('Include main instrumentation header');
            [~, headerFile, extFile] = fileparts(this.GlobalRegistry.HeaderFileTargetInterface);
            writer.writeLine('#include "%s%s"', headerFile, extFile);
            % prototypes for the methods implemented in xcp_profiling.c
            writer.wComment('Upload profiling data collected on the target');
            writer.writeLine('XcpErrorCode uploadOnTargetProfilingData(void);');
            
            writer.wComment('Methods to get element address in the profiling buffer');
            lTimerType = this.ProfilingTimer.getTimerCType;
            lCodeInstrSizeCDataType = coder.profile.ExecTimeConfig.getCodeInstrNumElsCDataType(this.TargetWordSize);
            writer.writeLine('%s* getMaxElementInProfilingBuffer(%s idx);', lTimerType, lCodeInstrSizeCDataType);
            writer.writeLine('%s* getAvgElementInProfilingBuffer(%s idx);', lTimerType, lCodeInstrSizeCDataType);
            writer.writeLine('%s* getCallsElementInProfilingBuffer(%s idx);', lTimerType, lCodeInstrSizeCDataType);
            
            writer.writeLine('void writeIDsInProfilingBuffer(%s idxMax, %s idxAvg, %s idxCalls);', ...
                lCodeInstrSizeCDataType, lCodeInstrSizeCDataType, lCodeInstrSizeCDataType);
            writer.writeLine('#endif');
        end
        
        function invocation = targetWriteTerminateCall(this)
            if this.GlobalRegistry.RequireXCPSupport
                invocation = '';
            else
                str = [this.CaptureFcnName '_', this.GlobalRegistry.Model];
                str = coder.profile.private.createProbeIdentifier(str, ...
                    coder.profile.CaptureProbeComponentRegistry.MaximumIdLength);
                invocation = sprintf('%s(1);', str);
            end
        end
    end
end
