function varargout = mdl_generate_inportinfo(modelH, testcomp, storeOnTestComponent, strictBusErrors)
% Function: mdl_generate_inportinfo ================================================================
% Abstract:
%   This function resolves the compiled Inport and Outport attributes of the modelH.
%   modelH must be in compiled state when you invoke this utility. It
%   stores the following info about the model on the testComponent, if
%   storeOnTestComponent is true:
%
%   Case 1: {Inport,Outport} i is defined by the bus object 'bus':
%
%                           signal_1
%               sub_bus  [------------
%            [===========[  signal_2
%       bus  [  signal_3 [------------
%    >=======[----------
%            [  signal_4
%            [----------
%
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).BlockPath = ...;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).SignalName = ...;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).IsStructBus = 1;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).IsVirtualBus = 1;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).IsStruct = 0;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).Dimensions = ...; Used to differentiate between bus and bus array
%   if strictBusErrors == false
%       testcomp.mdlFlatIOInfo.{Inport,Outport}(i).CompiledBusType = get_param(portH,'CompiledBusType')
%       testcomp.mdlFlatIOInfo.{Inport,Outport}(i).SignalHierarchy = get_param(portH,'SignalHierarchy')
%   else
%       testcomp.mdlFlatIOInfo.{Inport,Outport}(i).CompiledBusType = []
%       testcomp.mdlFlatIOInfo.{Inport,Outport}(i).SignalHierarchy = []
%   end
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).SampleTime = .. Compiled sample time of Inport i
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).SampleTimeStr = .. Compiled sample time string
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(1).DataType = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(1).Complexity = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(1).Dimensions = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(1).SampleTime = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(1).BusObjPath = 'bus.sub_bus'
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(1).SignalPath = 'In.elem(1)_name.elem(1)_name'
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(1).Used = true
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(2).DataType = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(2).Complexity = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(2).Dimensions = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(2).SampleTime = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(2).BusObjPath = 'bus.sub_bus'
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(2).SignalPath = 'In.elem(1)_name.elem(2)_name'
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(2).Used = true
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(3).DataType = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(3).Complexity = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(3).Dimensions = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(3).SampleTime = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(3).BusObjPath = 'bus'
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(3).SignalPath = 'In.elem(2)_name'
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(3).Used = true
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(4).DataType = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(4).Complexity = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(4).Dimensions = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(4).SampleTime = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(4).BusObjPath = 'bus'
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(4).SignalPath = 'In.elem(3)_name'
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo(4).Used = true
%
%   Important: We need to store testcomp.SampleTime because Bus Objects may
%   inherit sample times from the Inport or Outport.
%
%   Case 2: {Inport,Outport} i is NOT defined by a bus object
%
%      signal_1
%   >------------
%
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).BlockPath = ...;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).SignalName = ...;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).IsStructBus = 0;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).IsVirtualBus = 0;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).IsStruct = 0;
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).SampleTime = .. Compiled sample time of Inport i
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).SampleTimeStr = .. Compiled sample time string
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).Dimensions = ...; This is redundant. It is the same as compiledInfo.Dimensions below
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo.DataType = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo.Complexity = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo.Dimensions = ...
%   testcomp.mdlFlatIOInfo.{Inport,Outport}(i).compiledInfo.SignalPath = 'In'
%
%   If storeOnTestComponent=false, then it returns mdlFlatIOInfo structure
%   mentioned above.
%
%   If strictBusErrors=true, then the model gives error messages when
%   StrictBusMsg is set to error

%   Copyright 2008-2023 The MathWorks, Inc.


    if nargin<3
        storeOnTestComponent = false;
    end

    if nargin<4
        strictBusErrors = false;
    end

    if storeOnTestComponent
        % If there is test component attached to the model, then obtain the
        % whether model errors when StrictBusMsg is set to error. This
        % information is obtained when we compile the model for coverage.
        strictBusErrors = testcomp.analysisInfo.strictBusErros;
        runningForDvirSim = testcomp.recordDvirSim;

        % Only during translation, this function is called with
        % storeOnTestComponent=true. We want to explode complex signals
        % only during translation.
        toExplodeComplexSignals = true;
    else
        runningForDvirSim = false;
        toExplodeComplexSignals = false;
    end

    [inPortsInfo, outPortsInfo] = GetModelInOutPorts(modelH);

    InputCache = genPortCache(inPortsInfo, strictBusErrors, false, toExplodeComplexSignals);

    [lastmsg, lastid] = lastwarn;

    warningId = 'Simulink:blocks:StrictMsgIsSetToNonStrict';
    warningstatus = warning('query',warningId);
    warning('off',warningId);

    OutputCache = genPortCache(outPortsInfo, strictBusErrors, runningForDvirSim, toExplodeComplexSignals);

    warning(warningstatus.state,warningId);

    lastwarn(lastmsg,lastid);

    if storeOnTestComponent
        if ~isfield(testcomp.mdlFlatIOInfo, 'Parameter')
            testcomp.mdlFlatIOInfo.Parameter = [];
            testcomp.mdlFlatIOInfo.ParamNameMap = [];
        end
        testcomp.mdlFlatIOInfo.Inport = InputCache;
        testcomp.mdlFlatIOInfo.Outport = OutputCache;
        testcomp.createableSimData = checkInputForFixedPoint(InputCache);
    else
        mdlFlatIOInfo.Inport = InputCache;
        mdlFlatIOInfo.Outport = OutputCache;
        mdlFlatIOInfo.Parameter = [];
        mdlFlatIOInfo.ParamNameMap = [];
        varargout{1} = mdlFlatIOInfo;
    end
end

function [inPortsInfo, outPortsInfo] = GetModelInOutPorts(modelH)
    inPortsInfo = [];
    outPortsInfo = [];

    inportBlksH = find_system(modelH, ...
                              'SearchDepth',1,...
                              'LookUnderMasks','all', 'FollowLinks','on',...
                              'BlockType','Inport');

    outportBlksH = find_system(modelH, ...
                               'SearchDepth',1,...
                               'LookUnderMasks','all', 'FollowLinks','on',...
                               'BlockType','Outport');

    inPortsInfo.numofports = length(inportBlksH);
    inPortsInfo.blksH = inportBlksH;

    outPortsInfo.numofports = length(outportBlksH);
    outPortsInfo.blksH = outportBlksH;
end

function portCache = genPortCache(portsInfo, strictBusErrors, runningForDvirSim, toExplodeComplexSignals)
    % Calculate the portAttributes for each entry in portsInfo

    % portCache is an array of structures. It contains one entry for each
    % portNumber

    % Step 1: Pre-allocation for speed
    if isempty(portsInfo.blksH)
        portCache = [];
        return;
    else
        portNumbers = unique(get_param(portsInfo.blksH, 'Port'));
        defaultStruct = Sldv.IOUtils.PortAttributes.createDefaultStruct();
        portCache(1:length(portNumbers)) = defaultStruct;
    end

    % 'portAttribsMap' maps portNumbers to indices in the portCache array
    
    % Given a port number, we can use portAttribsMap to get its
    % corresponding entry in 'portCache'
    portAttribsMap = containers.Map('KeyType','double','ValueType','int32');
    for idx=1:portsInfo.numofports
        % We need to create input port entries only for the yet
        % ungrouped signals

        % Step 2: Gather the block details
        currentBlock.blockH = portsInfo.blksH(idx);
        
        isBusElementPort = isBusElem(portsInfo.blksH(idx));
        currentBlock.portNumber = str2double(get_param(portsInfo.blksH(idx), 'Port'));
        % Step 3: calculate the port Attributes
        if isBusElementPort && strcmp(get_param(currentBlock.blockH, 'BlockType'),'Inport') && portAttribsMap.isKey(currentBlock.portNumber)
            % Case 1: For In Bus element ports for which portAttributes are already calculated
            
            % Logic: For In bus element ports, the data type (Bus Definition)is already mentioned
            % So, given a block handle for one of the leaf signals of an In
            % bus element port('myBus.signal1'), we can gather all the
            % details about the top level bus('myBus').
            continue;
        elseif isBusElementPort && ~strcmp(get_param(currentBlock.blockH, 'BlockType'),'Inport') && isPortCacheEntryAlreadyCalculated(currentBlock)
            % Case 2: For out bus element ports for which the
            % portAttributes are already calculated.
            
            % Logic: For out bus element ports where we already have
            % gathered information about one leaf signal, we simply retrieve
            % the information we have gathered, append the information for
            % the current out bus element and put it back at the same index
            % in the array 'portCache'
            existingPortAttributes = portCache(portAttribsMap(currentBlock.portNumber)); % retrieve the existing entry
            currentPortCacheIdx = portAttribsMap(currentBlock.portNumber);
        else
            % Case 3: We end up here for non-bus element ports or when we encounter a bus element port with that port number for the first time

            % Logic: In this case, we create an entry for this
            % 'portAttributes' structure in our 'portCache' array and calculate the index where it will be stored in the 'portCache' array.   
            existingPortAttributes = [];                    % Since there is no existing entry for this port number in 'portCache' array
            currentPortCacheIdx = portAttribsMap.Count + 1; % Index at which this entry can be stored in the 'portCache' array
            portAttribsMap(currentBlock.portNumber) = currentPortCacheIdx;
        end
        if isBusElementPort && strcmp(get_param(currentBlock.blockH,'BlockType'),'Outport')
            currentPortCache = appendToBusElemPortAttributes(existingPortAttributes, currentBlock.blockH, strictBusErrors,...
                                                        runningForDvirSim, toExplodeComplexSignals);
        else
            currentPortCache = util_getIOportBus(currentBlock.blockH, strictBusErrors, runningForDvirSim, toExplodeComplexSignals);
        end

        portCache(currentPortCacheIdx) = currentPortCache;
    end

    function retValue = isPortCacheEntryAlreadyCalculated(currentBlock)
        % Returns true if we already have an entry in portCache array of
        % structures for a block.
        
        % For example if the model has two out bus element ports, myBus.signal1 and myBus.signal2
        % Both share the same port number. So, there would be only one
        % entry for it in portCache. 
        retValue = portAttribsMap.isKey(currentBlock.portNumber);
    end
end

function createableSimData = checkInputForFixedPoint(InputCache)
    createableSimData = true;
    inputhasFixPnt = false;
    for i=1:length(InputCache)
        dtStrCell = {InputCache(i).compiledInfo.DataType};
        for k=1:numel(dtStrCell)
            dtStr = dtStrCell{k};
            inputhasFixPnt = fixed.internal.type.isNameOfTraditionalFixedPointType(dtStr) || ...
                strncmp(dtStr, 'fixdt',5) || ...
                strncmp(dtStr, 'numerictype',11);
            %Need a new case here?
            if inputhasFixPnt
                break;
            end
        end
        if inputhasFixPnt
            break;
        end
    end
    if inputhasFixPnt
        createableSimData = exist('fi','file')==2;
    end
end

% LocalWords:  ungrouped Attribs
