classdef (Hidden = true) AtomicSubsystemCodeDescriptorUtils < coder.connectivity.SubsystemCodeDescriptorUtils
    %ATOMICSUBSYSTEMCODEDESCRIPTORUTILS provides CodeDescriptor extensions and
    %utilities tailored towards atomic subsystem components.
    %
    %   This is an undocumented class. Its methods and properties are likely to
    %   change without warning from one release to the next.
    
    %   Copyright 2020-2021 The MathWorks, Inc.
    
    methods (Access = 'protected')
        
        function dispString = getParameterInterfaceString_Hook(~, ~, origString)
            % nothing to do for Atomic subsystem
            dispString = origString;
        end
        
        
        function sid = getSIDForSubsystemInterface(this)
            % get the subsystem SID in the original model,
            % TODO: CTDSPI-1512 it should be loaded as the harness is open, 
            % what happens if we are using an external harness?
            h = get_param(this.ComponentForDiagnostics, 'handle');
            sid = Simulink.ID.getSID(h);
        end
        
        function updateTimingProperties(this)
            % Update subsystem timing properties so bring them in line with
            % regular component expectations
            
            componentHasExpInports = ~isempty(this.getExpInportsFromCodeDescriptor());
            componentInterface = this.CodeInfo.getComponentInterface();
            componentIsExportFunctions = this.isExportFunctions_Implementation( ...
                componentHasExpInports, componentInterface.OutputFunctions.toArray());
            isFcnCallSubsystem = this.isFunctionCallSubsystem();
            if isFcnCallSubsystem && ~isempty(this.getOutputTasksForTimingMode('PERIODIC'))
                % The function call subsystem can have a periodic or
                % triggered sample time and code descriptor contains the
                % periodic rate used by the parent.
                % The test harness can trigger the subsystem at different
                % rate. To avoid mismatches with normal mode, replace the
                % periodic rate with an asynchronous one.
                newTiming = this.createTimingInterface(this.getMF0ModelInstance(), 'ASYNCHRONOUS');
                newTiming.SamplePeriod = -1;
                newTiming.SampleOffset = -2;
                newTiming.Priority = 41;
                newTiming.TaskingMode = 'EXPLICIT_TASKING';
                this.CodeInfo.replaceTiming('PERIODIC', newTiming);
            elseif ~isFcnCallSubsystem && componentIsExportFunctions
                % Export functions models have subsystem interfaces with
                % Asynchronous timing interfaces.
                % Unless the subsystem is a function call subsystem, it is not
                % asynchronous and can be considered sample time independent
                % for testing
                newTiming = this.createTimingInterface(this.getMF0ModelInstance(), 'INHERITED');
                this.CodeInfo.replaceTiming('ASYNCHRONOUS', newTiming);
            end
            
            % Update timing for Inports 
            mf0InportInfo = this.ConfigInterface.getMF0ModelInfo().inports.toArray();
            this.CodeInfo.replaceInportsTiming(mf0InportInfo, this.getMF0ModelInstance());
        end
        
        function validateInportImplementations(this)
            
            inports = this.CodeInfo.Inports;
            implementations = [inports(1:end).Implementation];
            
            numImplementations = length(implementations);
            
            for idx=1:numImplementations-1
                
                for jdx=idx+1:numImplementations
                    % Check if an implementation is equal to another
                    if implementations(idx) == implementations(jdx)
                        % Throw warning
                        warning = MSLDiagnostic(message( ...
                            'PIL:pil_subsystem:MatchingInportImplementation', ...
                            this.ComponentForDiagnostics));
                        warning.reportAsWarning();
                        return;
                    end
                end
                
            end
            
        end

        function validateTimingProperties(this)
            % call super-class method first
            validateTimingProperties@coder.connectivity.SubsystemCodeDescriptorUtils(this);
            
            %Check for multi-rate systems and the presents of RTBs
            if this.isMultiRate
                errId = 'PIL:pil_subsystem:UnsupportedMultiRateRateTransitions';
                mf0Contents = this.ConfigInterface.getMF0ModelInfo();
                if ~isempty(mf0Contents.periodicEventPortUnsupportedBlockInfo.deterministicRTBs.toArray)
                    e = MSLException(message(errId, this.ComponentForDiagnostics));
                    e.throw;
                end
                
                infoStruct = this.ConfigInterface.getInfoStruct();
                if isfield(infoStruct.modelInterface, 'ActualRateInteraction') && ...
                        any(infoStruct.modelInterface.ActualRateInteraction, 'all')
                    e = MSLException(message(errId, this.ComponentForDiagnostics));
                    e.throw;
                end
            end
        end
        
        function lFoundCaller = getServerCallPointFromSubModel(~, ~, ~) %#ok<STOUT>
            % When SLFunctions are supported we might have to look for a
            % caller in child subsystems
            assert(false, 'Simulink functions are not supported');
        end
        
        function expInports = getExpInportsForComponentUnderTest(this)
            % create an expInport struct for the hidden model
            expInports = [];
            
            % if the ss has function call port
            if this.isFunctionCallSubsystem()
                expInports = struct('PortType', 'fcn_call', ...
                    'IsLatched', 0, ...
                    'EnableFcn', [], ...
                    'DisableFcn', [], ...
                    'Index', 1, ... force to match with the first outputfunction
                    'FcnType', 'Output');
                assert(length(this.CodeInfo.OutputFunctions), 1, 'Only one output function is expected');
            end
        end
        
        function isFncCall = isFunctionCallSubsystem(this)
            mf0InportInfo = this.ConfigInterface.getMF0ModelInfo().inports.toArray();
            isFncCall = any(arrayfun(@(x) x.isFunctionCallInitiator, mf0InportInfo));
        end
        
        function val = canUseDirectReadsAndWrites(~, ~)
            % do not use direct read/writes, review when AUTOSAR is also
            % active
            val = false;
        end
    end
    
    methods(Access = public)
        function res = needsExportFunctionWrapper(~, infoStruct)
            res = isfield(infoStruct.modelInterface, 'FcnCallInputs') && ...
                ~isempty(infoStruct.modelInterface.FcnCallInputs);
        end
        
        function inportIdx = getExportFunctionsInportIndex(~, codeInfoIndex)
            % no information on exportFunctionInports, return codeInfoIndex
            inportIdx = codeInfoIndex;
        end
    end
end
