classdef (Hidden = true) ISubsysAdaptor < rtw.connectivity.CIAdaptor
% ISubysAdaptor - Common interface for subsystem adaptor classes.
%
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2023 The MathWorks, Inc.

    properties (Access = public)
        UsesNonFiniteUtils (1, 1) logical = false;
    end

    methods(Access = public)
        function this = ISubsysAdaptor(aComponentInterface, codeDesc, componentSID)
            arguments
                aComponentInterface (1,1) coder.descriptor.ComponentInterface
                codeDesc = [];
                componentSID = [];
            end
            this = this@rtw.connectivity.CIAdaptor(aComponentInterface, codeDesc, componentSID);
        end

        function modelObj = getModelVariable(this)
           % Expose Component Interface getModelVariable method for
           % retrieving the top-model object
           modelObj = this.CodeDescObj.getModelVariable();
        end
    end
   
    methods(Access = public, Abstract)
        set = getSILUnsupportedPortsSet(this);
        replaceTiming(this, timingToReplace, newTiming);
        replaceInportsTiming(this, inportsInfo, mf0Model, componentForDiagnostics);
    end

    methods(Access = public)
        function filterParameters(~, ~)
            % No-op by default
        end
    end

    methods(Static, Access = protected)
        function doReplaceInportsTiming( ...
            timingProperties, ...
            inports, ...
            inportsInfo, ...
            componentForDiagnostics, ...
            replaceRateStrategy)

            % If any inports, replace inports timing with a new timing that matches either the
            % PERIODIC rate used by that port in the temporary model, or use the one returned by the
            % missingRateStrategy.

            if numel(inports) == 0
                return;
            end

            timingToReplace = {...
                'ONESHOT', ...    % connected to constant blocks
                'INITIALIZE', ... % connected to Initialize Function blocks
                'RESET'};         % connected to Reset Function blocks

            for timingIdx = 1:length(timingToReplace)
                currTimingToReplace = timingToReplace{timingIdx};

                % Remove timing from the TimingProperties unless
                % ONESHOT as it may be associated to outports as well,
                % see corner case in tATS_ConstInportOneShotOnlyRate
                if ~strcmp(currTimingToReplace, 'ONESHOT')
                    needsReplacing = arrayfun(@(x) strcmp(x.TimingMode, currTimingToReplace), ...
                        timingProperties.toArray());
                    idxToReplace = find(needsReplacing);
                    for idx = length(idxToReplace):-1:1
                        timingProperties.removeAt(idxToReplace(idx));
                    end
                end

                % Replace inports timing
                for idx = 1:numel(inports)
                    currPort = inports(idx);
                    % Deal with UNION timing at inports, use a function
                    % handle to seamlessly assign a new timing to UNION
                    % and regular timings
                    isUnion = strcmp(currPort.Timing.TimingMode, 'UNION');
                    if isUnion
                        currPortTimings = currPort.Timing.UnionTimingInfo.toArray();
                        assignTiming = @assignUnionTiming;
                    else
                        currPortTimings = currPort.Timing;
                        assignTiming = @assignTiming;
                    end

                    % Make sure we have the same number of port rates 
                    % in mf0 and codeDescriptor, if not, there is a
                    % mismatch in sample time propagation between the
                    % temporary model (mf0) and the subsystem (codeDesc)
                    rateInfos = inportsInfo(idx).rateInfos.toArray();
                    if isUnion || ...
                            length(rateInfos) ~= length(currPortTimings)
                        errId = 'PIL:pil_subsystem:UnsupportedBranchedInport';
                        error = MSLException(errId, componentForDiagnostics);
                        error.throw();
                    end
                    for tIdx = 1:length(currPortTimings)
                        if strcmp(currPortTimings(tIdx).TimingMode, currTimingToReplace)
                            newRate = replaceRateStrategy(rateInfos(tIdx));
                            currPort.Timing = assignTiming(currPort.Timing, tIdx, newRate);
                        end
                    end
                end
            end
        end

        function doReplaceTiming(timingToReplace, interfaces, newInterface)
            % replace timing in Functions or IO ports

            for idx = 1:numel(interfaces)
                currInterface = interfaces(idx);
                if strcmp(currInterface.Timing.TimingMode, timingToReplace)

                    currInterface.Timing = newInterface;

                    if isprop(currInterface, 'DirectReads')
                        for drIdx = 1:currInterface.DirectReads.Size()
                            currInterface.DirectReads(drIdx).Timing = newInterface;
                        end
                        for dwIdx = 1:currInterface.DirectWrites.Size()
                            currInterface.DirectWrites(dwIdx).Timing = newInterface;
                        end
                    end
                end
            end
        end
   end

end

function timing = assignUnionTiming(timing, idx, newTiming)
    % assign a new timing to an element of UnionTimingInfo and
    % return the timing
    timing.UnionTimingInfo(idx) = newTiming;
end

function timing = assignTiming(~, ~, newTiming)
    % return the new timing
    timing = newTiming; 
end