classdef (Hidden = true) MATLABTargetInterface < rtw.pil.TargetInterface   
%MATLABTARGETINTERFACE creates a standalone MATLAB target interface file
%
%   MATLABTARGETINTERFACE creates a standalone MATLAB target interface file
%   defined by CodeInfo
%
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2016-2023 The MathWorks, Inc.

% properties
    properties (SetAccess = 'private', GetAccess = 'private')
         UseCoderDataMarshalling = false; % Switch to defer to MATLAB Coder Serialization
        interfaceDataList = {}; %Need to maintain the order of the arguments,
                                %so use a simple cell array. A map would be
                                %sorted. But the function arguments must
                                %maintain their canonical order.
        InstrFcnInEntryPoints = []; % instrumentation functions to be
                                    % called in xilPreEntryPoint (first
                                    % item) and in xilPostEntryPoint
                                    % (second item)
    end      
    
    methods (Access = 'protected')
        function checkIOReadOnly(~, ~, ~)
        % Relax checks. SIL always defines IO variables in MLCoder workflow
        % as non-const. It is OK to pass a non-const variable to a function
        % which expects a const argument.
        end

        function writeSectionStorage(this)
            if this.SILPILWrapperUtils.isStackProfilingEnabled
                this.writer.wLine('static uint32_T stackInitDone = 0;');
                this.writer.wLine('static uint32_T stackInitInProgress = 0;');
            end
        end
        
        function writeSectionIncludes(this)
            
            writeSectionIncludes@rtw.pil.TargetInterface(this);
                        
                % Include serialize/de-serialize function definitions
                fcnName = this.SILPILWrapperUtils.getSILPILInterface().getCodeName();
                this.writer.wLine('#include "_coder_%s_target.h"', fcnName)
                targetInterfaceHeader = 'xil_target_interface.h';
                this.writeXILTargetInterfaceHeader(targetInterfaceHeader);
                this.writer.wLine('#include "%s"',targetInterfaceHeader);
                this.writer.newLine;
        end
        
        function writeSectionXILIOData(~)
            %Override Super Class function
            end
        
        function writeSectionExternalInitialization(~)  
            %Override Super Class function
        end
        
        function writeSectionInitBody(this)
                initFun = this.codeInfo.InitializeFunctions;

                if ~isempty(initFun)
                    isExecutionProfilingEnabled = this.SILPILWrapperUtils.isProfilingEnabled;
                    if isExecutionProfilingEnabled
                        % if profiling is active, the overhead can be filtered
                        this.writeSectionOverheadConfiguration();
                    end

                    this.writer.wComment('Invoke Initialization Function');
                    this.writer.wBlockStart('if (xilFcnId == 0)');                
                    % call initialize function
                    if isExecutionProfilingEnabled
                        
                        [sectionIdForFunction, instrFcn] =...
                            this.getProfilingIdentifier(initFun);
                        
                        if isempty(this.InstrFcnInEntryPoints)
                            this.InstrFcnInEntryPoints.startFcn = instrFcn{1};
                            this.InstrFcnInEntryPoints.endFcn = instrFcn{2};
                        else
                            assert(strcmp(this.InstrFcnInEntryPoints.startFcn, instrFcn{1}) && ...
                                strcmp(this.InstrFcnInEntryPoints.endFcn, instrFcn{2}), ...
                                'Only TASK_TIME_PROBEs are supported for task-level instrumentation');
                        end
                    else
                        sectionIdForFunction = 0;
                    end
                    
                    % call deferred initialize function                
                    this.writer.wLine('XILTarget_initialize(%dU);',sectionIdForFunction);
                    
                    this.writer.wBlockMiddle('else');
                    this.writer.writeLine('errorCode = XIL_INTERFACE_UNKNOWN_FCNID;');
                    this.writer.wBlockEnd('}');
                end
            end
        
        function writeSectionInitDataProcessingCase(~, ~, ~, ~, ~)
            %Override Super Class function
        end       
        
        % write the xilIOData table variable
        function writePILIODataTable(~, ~, ~)
            %Override Super Class function
        end
        
        function writeSectionOutputBody(this)
                this.writer.writeCellLines({'/* Single In-the-Loop Component */';
                    'if (xilFcnId != 0) {';
                    '   return XIL_INTERFACE_UNKNOWN_FCNID;';
                    '}';});
                
                outputTasks = this.getOutputTasks;
                isExecutionProfilingEnabled = this.SILPILWrapperUtils.isProfilingEnabled;
                
                this.writer.wBlockStart('switch(xilTID)');
                
                for taskIdx = 1:length(outputTasks)
                    outputTask = outputTasks(taskIdx);
                    
                    this.writer.writeLine(['case ' int2str(outputTask.id) ':']);
                    this.writer.incIndent;
                    if isExecutionProfilingEnabled

                        [sectionIdForFunction, instrFcn] =...
                            this.getProfilingIdentifier(...
                                outputTask.codeInfoData);
                        
                        if isempty(this.InstrFcnInEntryPoints)
                            this.InstrFcnInEntryPoints.startFcn = instrFcn{1};
                            this.InstrFcnInEntryPoints.endFcn = instrFcn{2};
                        else
                            assert(strcmp(this.InstrFcnInEntryPoints.startFcn, instrFcn{1}) && ...
                                strcmp(this.InstrFcnInEntryPoints.endFcn, instrFcn{2}), ...
                                'Only TASK_TIME_PROBEs are supported for task-level instrumentation');
                        end
                    else
                        sectionIdForFunction = 0;
                    end
                 
                    % call output function
                    infoSt = this.SILPILWrapperUtils.getSILPILInterface.getInfoStruct();
                    fcnName = infoSt.xilEntryPoints.(outputTask.codeInfoData.Prototype.Name);
                    this.writer.wBlockStart('if ( %s(%dU) != XIL_PROCESSDATA_SUCCESS )',fcnName,sectionIdForFunction);
                    this.writer.wLine('return XIL_INTERFACE_COMMS_FAILURE;');
                    this.writer.wBlockEnd(); %if <fcn>() != XIL_PROCESSDATA_SUCCESS
                    
                    this.writer.writeLine('break;');
                    this.writer.decIndent;
                end
                
                % generate default
                this.writer.writeCellLines({'default:';
                    '   return XIL_INTERFACE_UNKNOWN_TID;'});
                this.writer.wBlockEnd(''); %switch(xilTID)                
                
                if this.SILPILWrapperUtils.AreEventCallbacksRequired
                    this.writer.writeLine('StepCompletedEvent();');
                    this.writer.newLine;
                end

                this.writer.writeLine('return XIL_INTERFACE_SUCCESS;');
            end
        
        function writeOutputBody(this)
            
            writeOutputBody@rtw.pil.TargetInterface(this);
            
            this.writeSectionPrePostEntryPoints();
            
        end
        
        function writeSectionTerminateBody(this)

            termFun = this.codeInfo.TerminateFunctions;
            if ~isempty(termFun)
                isExecutionProfilingEnabled = this.SILPILWrapperUtils.isProfilingEnabled;
                if isExecutionProfilingEnabled
                    
                    [sectionIdForFunction, instrFcn] =...
                        this.getProfilingIdentifier(termFun);
                    
                    if isempty(this.InstrFcnInEntryPoints)
                        this.InstrFcnInEntryPoints.startFcn = instrFcn{1};
                        this.InstrFcnInEntryPoints.endFcn = instrFcn{2};
                    else
                        assert(strcmp(this.InstrFcnInEntryPoints.startFcn, instrFcn{1}) && ...
                            strcmp(this.InstrFcnInEntryPoints.endFcn, instrFcn{2}), ...
                            'Only TASK_TIME_PROBEs are supported for task-level instrumentation');
                    end
                else
                    sectionIdForFunction = 0;
                end
                
                % call deferred terminate function
                this.writer.wLine('XILTarget_terminate(%dU);',sectionIdForFunction);

                if this.SILPILWrapperUtils.AreEventCallbacksRequired
                    this.writer.newLine;
                    this.writer.writeLine('TerminateEvent();');
                end
            end
        end
        
        function indices = getParamIndicesForStorage(~)
            % default implementation, overwritten by subclasses
            indices = [];
        end
        
        function writePreStepCalls(~, ~)
            %Override Super Class function - No Concept of this in MATLAB
            %Coder world. And Simulink Coder version needs feature
            %switches.
        end
    end
    methods (Access = 'private')

        function writeSectionPrePostEntryPoints(this)
            isTimeProfilingEnabled = this.SILPILWrapperUtils.isTimeProfilingEnabled;
            isStackProfilingEnabled = this.SILPILWrapperUtils.isStackProfilingEnabled;
            isProfilingEnabled = isTimeProfilingEnabled || isStackProfilingEnabled;

            this.writer.wBlockStart('void xilPreEntryPoint(uint32_T entryPointID)');
            if isTimeProfilingEnabled
                assert(~isempty(this.InstrFcnInEntryPoints), ...
                    'There must be at least an entry point');
                this.writer.writeLine('%s(entryPointID);', ...
                    this.InstrFcnInEntryPoints.startFcn);
            elseif isStackProfilingEnabled
                assert(~isempty(this.InstrFcnInEntryPoints), ...
                    'There must be at least an entry point');
                reg = this.SILPILWrapperUtils.getProfilingStackTaskRegistry();
                assert(~isempty(reg), 'There must be a registry');
                fcnName = this.SILPILWrapperUtils.getSILPILInterface().getCodeName();
                [sectionId, instrMethods] = reg.insertOffsetStackProbe(fcnName);

                this.writer.wBlockStart('if (stackInitDone == 0)');
                this.writer.writeLine('%s(%d);', instrMethods{1}, sectionId);
                this.writer.wBlockStart('if (stackInitInProgress == 0)');
                this.writer.writeLine('stackInitInProgress = 1;');
                this.writer.writeLine('xilPreEntryPoint(entryPointID);');
                this.writer.writeLine('stackInitInProgress = 0;');
                this.writer.writeLine('stackInitDone = 1;');
                this.writer.wBlockEnd();
                this.writer.wBlockEnd();

                % make sure the offset is computed before taking a valid
                % sample
                this.writer.wBlockStart('if (stackInitInProgress == 0)');
                this.writer.writeLine('%s(entryPointID);', ...
                    this.InstrFcnInEntryPoints.startFcn);
                this.writer.wBlockEnd();
            else
                this.writer.wLine('UNUSED_PARAMETER(entryPointID);');
            end

            this.writer.wBlockEnd(); %Pre Entry Point function

            this.writer.wBlockStart('XIL_INTERFACE_ERROR_CODE xilPostEntryPoint(uint32_T entryPointID)');
            this.writer.wLine('MemUnit_T responseId = XIL_RESPONSE_OUTPUT_DATA;');

            if isProfilingEnabled
                this.writer.writeLine('%s(entryPointID);', ...
                    this.InstrFcnInEntryPoints.endFcn);
            else
                this.writer.wLine('UNUSED_PARAMETER(entryPointID);');
            end

            % Need to send the response id before sending back data
            this.writer.wBlockStart('if(xilWriteData(&responseId, sizeof(responseId)) != XIL_DATA_STREAM_SUCCESS)');
            this.writer.wLine('return XIL_INTERFACE_COMMS_FAILURE;');
            this.writer.wBlockEnd(); %If send response Id.

            this.writer.wLine('return XIL_INTERFACE_SUCCESS;');
            this.writer.wBlockEnd(); %Post Entry Point function

            termFun = this.codeInfo.TerminateFunctions;
            if ~isempty(termFun)
                %If there is a terminate function we need to a
                %different Post Hook function
                this.writer.wBlockStart('XIL_INTERFACE_ERROR_CODE xilPostTerminatePoint(uint32_T entryPointID)');

                if isProfilingEnabled
                    this.writer.writeLine('%s(entryPointID);', ...
                        this.InstrFcnInEntryPoints.endFcn);
                else
                    this.writer.wLine('UNUSED_PARAMETER(entryPointID);');
                end

                this.writer.wLine('return XIL_INTERFACE_SUCCESS;');
                this.writer.wBlockEnd(); %Post Terminate Point function
            end
        end
        
        function writeXILTargetInterfaceHeader(this, targetInterfaceHeader)
            % Write Host Interface header file
            headerPath = fileparts(this.writerOutputPath);
            hWriter = coder.internal.connectivity.TargetInterfaceHeaderWriter(headerPath, ...
                targetInterfaceHeader);
            append = false;            
            callCBeautifier = true;
            obfuscateCode = this.SILPILWrapperUtils.shouldObfuscateXILWrapperFiles;
            hWriter.writeOutput(append, callCBeautifier, obfuscateCode);
        end
    end
end
