classdef CaptureProbeTarget < coder.profile.TargetInterface
    % CAPTUREPROBETARGET 

    % Copyright 2019-2022 The MathWorks, Inc.

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

    properties(Constant, Access=private)
        InitMethodFcnName = 'captureModeInitLocalData';
    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
                    [~, ~, hdrExt] = fileparts(this.GlobalRegistry.HeaderFileTargetInterface);
                    writer.writeLine('#include "%s"', this.GlobalRegistry.getXCPMainHeaderFile(hdrExt));
                    writer.newLine;
                else
                    writer.writeLine('#include <string.h>');
                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);
                else
                    % this array avoids the profiling of recursive
                    % functions. Since the start events override the same
                    % variable, all the end events would use the same start
                    % time which is wrong
                    writer.writeLine('static %s xsd_xil_capture_mode_nested[MAX_EXECUTION_SECTION_ID] = {0};', lTimerType);
                end
                writer.newLine;

                uploadPrototype = sprintf('void %s(%s sectionId)', ...
                    this.CaptureFcnName, lCodeInstrSizeCDataType);
                writer.wBlockStart(uploadPrototype);
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('UNUSED_PARAMETER(sectionId);');
                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;

                % write init data
                if ~this.GlobalRegistry.RequireXCPSupport
                    initMethod = sprintf('void %s()', this.InitMethodFcnName);
                    writer.wBlockStart(initMethod);
                    writer.writeLine('memset((void*)&xsd_xil_last_start_time[0], 0, sizeof(%s) * MAX_EXECUTION_SECTION_ID);', lTimerType);
                    writer.writeLine('memset((void*)&xsd_xil_capture_mode_max[0], 0, sizeof(%s) * MAX_EXECUTION_SECTION_ID);', lTimerType);
                    writer.writeLine('memset((void*)&xsd_xil_capture_mode_avg[0], 0, sizeof(%s) * MAX_EXECUTION_SECTION_ID);', lTimerType);
                    writer.writeLine('memset((void*)&xsd_xil_capture_mode_calls[0], 0, sizeof(%s) * MAX_EXECUTION_SECTION_ID);', lTimerType);
                    writer.wBlockEnd;
                end

                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);
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('xsd_xil_capture_mode_nested[mappedId - 1] = 1;');
                end
                writer.wBlockEnd;
                writer.wBlockEnd;
                
                endMethod = sprintf('void %s(%s sectionId)', ...
                    coder.profile.CaptureProbeTarget.EndInternalMethod, ...
                    lCodeInstrSizeCDataType);
                writer.wBlockStart(endMethod);
                writer.writeLine('uint8_T* addr;');
                writer.writeLine('%s captureValue;', lTimerType);
                writer.writeLine('%s mappedId = MAP_CAPTURE_IDS(sectionId);', lCodeInstrSizeCDataType);
                writer.writeLine('%s turnaroundTime;', lTimerType);
                writer.wBlockStart('if (mappedId > 0)');
                writer.writeLine('mappedId = mappedId - 1;');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.wBlockStart('if (xsd_xil_capture_mode_nested[mappedId] != 1)');
                    writer.writeLine('xsd_xil_capture_mode_nested[mappedId] = 0;');
                    writer.writeLine('return;');
                    writer.wBlockEnd;
                    writer.writeLine('xsd_xil_capture_mode_nested[mappedId] = 0;');
                end
                writer.wComment('Update maximum execution');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('addr = (uint8_T*)getMaxElementInProfilingBuffer(mappedId);');
                    writer.writeLine('XCP_MEMCPY((void*)&captureValue, (void*)addr, sizeof(%s));', lTimerType);
                else
                    writer.writeLine('captureValue = 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 > captureValue)');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('XCP_MEMCPY((void*)addr, (void*)&turnaroundTime, sizeof(%s));', lTimerType);
                else
                    writer.writeLine('xsd_xil_capture_mode_max[mappedId] = turnaroundTime;');
                end
                writer.wBlockEnd;
                writer.wComment('Try to update to total execution counter');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('addr = (uint8_T*)getAvgElementInProfilingBuffer(mappedId);');
                    writer.writeLine('XCP_MEMCPY((void*)&captureValue, (void*)addr, sizeof(%s));', lTimerType);
                else
                    writer.writeLine('captureValue = xsd_xil_capture_mode_avg[mappedId];');
                end
                writer.wBlockStart('if ((captureValue + turnaroundTime) >= captureValue)');
                writer.writeLine('captureValue = captureValue + turnaroundTime;');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('XCP_MEMCPY((void*)addr, (void*)&captureValue, sizeof(%s));', lTimerType);
                else
                    writer.writeLine('xsd_xil_capture_mode_avg[mappedId] = captureValue;');
                end
                writer.wComment('Update total number of calls');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('addr = (uint8_T*)getCallsElementInProfilingBuffer(mappedId);');
                    writer.writeLine('XCP_MEMCPY((void*)&captureValue, (void*)addr, sizeof(%s));', lTimerType);
                else
                    writer.writeLine('captureValue = xsd_xil_capture_mode_calls[mappedId];');
                end
                writer.writeLine('captureValue = captureValue + 1;');
                if this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('XCP_MEMCPY((void*)addr, (void*)&captureValue, sizeof(%s));', lTimerType);
                else
                    writer.writeLine('xsd_xil_capture_mode_calls[mappedId] = captureValue;');
                end
                writer.wBlockEnd;
                writer.wBlockEnd;
                writer.wBlockEnd;
                
                writer.newLine;
            end
        end
 
        function targetWriteInit(~, ~)
        end
        
        function invocation = targetWriteInitCall(this)
            if this.GlobalRegistry.RequireXCPSupport
                invocation = '';
            else
                invocation = sprintf('%s();', this.InitMethodFcnName);
            end
        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
