function [InputPortInfo, OutputPortInfo, flatInfo] = ...
        generateIOportInfo(model, parameterSettings)
    %

    %   Copyright 2008-2021 The MathWorks, Inc.

    if nargin<2
        parameterSettings.('StrictBusMsg') = ...
            struct('newvalue','ErrorLevel1','originalvalue','');
    end
    isSubsystem = false;
    if model ~= bdroot(model)
        isSubsystem = true;
    end
    if ~isSubsystem
        [inportCompInfo, outportCompInfo, modelCompileInfo] = ...
            Sldv.DataUtils.getModelCompiledIOInfo(model, parameterSettings);
        flatInfo.SampleTimes = modelCompileInfo.sampleTime;
        flatInfo.ModelSampleTimesDetails = modelCompileInfo.modelSampleDetails;
    else
        % For Subsystem logging. Model should be in compiled state
        mdlFlatIOInfo = sldvshareprivate('mdl_generate_inportinfo',model);
        SampleTimes = get_param(model,'CompiledSampleTime');
        if iscell(SampleTimes)
            flatInfo.SampleTimes = SampleTimes{1}(1);
        else
            flatInfo.SampleTimes = SampleTimes(1);
        end
        inportCompInfo = mdlFlatIOInfo.Inport;
        outportCompInfo = mdlFlatIOInfo.Outport;
    end

    flatInfo.InportCompInfo = inportCompInfo;
    flatInfo.OutportCompInfo = outportCompInfo;

    numInPorts  = length(inportCompInfo);
    InputPortInfo = cell(1, numInPorts);
    for idx = 1:numInPorts
        InputPortInfo{idx} = genPortInfo(inportCompInfo(idx),false);
    end
    % Add dimension info for all sub-buses on inports. This is to
    % differentiate buses from bus arrays
    if ~isSubsystem
        InputPortInfo = addBusDimsToPortInfo(InputPortInfo, model);
    end

    numOutPorts  = length(outportCompInfo);
    OutputPortInfo = cell(1, numOutPorts);
    for idx = 1:numOutPorts
        OutputPortInfo{idx} = genPortInfo(outportCompInfo(idx),true);
    end
    % Add dimension info for all sub-buses on outports. This is to
    % differentiate buses from bus arrays
    if ~isSubsystem
        OutputPortInfo = addBusDimsToPortInfo(OutputPortInfo, model);
    end
end

function signalNames = getSignalNames(portInfoData, leafSignalPaths)
% If leafSignalPaths{i} is 'a.b.c' where 'a.b' is the port name,
% signalNames{i} will consists of two array elements: (1) 'a.b' and (2) 'c'
% If leafSignalPaths{i} is 'a.b' where 'a' is the port name,
% signalNames{i} will consists of two array elements: (1) 'a' and (2) 'b'
    elemsInBlockPath = regexp(portInfoData.BlockPath, '/', 'split');
    blockNameInModel = elemsInBlockPath{end};
    if ~isempty(strfind(blockNameInModel,'.'))
        blockNameInModelWithUnderscore = strrep(blockNameInModel, '.', '_');
        tempLeafSignalPaths = strrep(leafSignalPaths, blockNameInModel, blockNameInModelWithUnderscore);
        signalNames = sldvshareprivate('util_get_signal_parts',tempLeafSignalPaths);
        for i=1:length(signalNames)
            signalNames{i} = strrep(signalNames{i}, blockNameInModelWithUnderscore, blockNameInModel);
        end
    else
        signalNames = sldvshareprivate('util_get_signal_parts',leafSignalPaths);
    end
end

function portInfo = genPortInfo(portInfoData, isOutport)
    if ~portInfoData.IsStructBus
        if ~portInfoData.IsStruct
            leafportInfo = getLeafPortInfo(portInfoData,1);
            if isOutport && portInfoData.IsVirtualBus
                rootBusInfo = struct('BlockPath',leafportInfo.BlockPath,...
                                     'SignalName',leafportInfo.SignalName,...
                                     'BusObject','',...
                                     'SignalPath',leafportInfo.SignalLabels);
                leafportInfo = rmfield(leafportInfo,{'BlockPath','SignalName'});
                portInfo{1} = rootBusInfo;
                portInfo{end+1} = leafportInfo;
            else
                portInfo = leafportInfo;
            end
        else
            depth = 1;
            leafIdx = 1;
            leafSignalPaths = {portInfoData.compiledInfo.SignalPath};
            signalNames = getSignalNames(portInfoData, leafSignalPaths);
            signalPaths = signalNames(:);
            portInfo = genCombinedLeafPortInfoStruct(signalPaths,depth,portInfoData,leafIdx,isOutport);
        end
    else
        depth = 1;
        leafIdx = 1;
        leafSignalPaths = {portInfoData.compiledInfo.SignalPath};
        signalNames = getSignalNames(portInfoData, leafSignalPaths);
        signalPaths = signalNames(:);
        portInfo = genCombinedLeafPortInfo(signalPaths, depth, portInfoData, leafIdx, isOutport);
    end
    if isfield(portInfoData,'CompiledBusType') && isfield(portInfoData,'SignalHierarchy')
        if ~iscell(portInfo)
            portInfo.CompiledBusType = portInfoData.CompiledBusType;
            portInfo.SignalHierarchy = portInfoData.SignalHierarchy;
        else
            portInfo{1}.CompiledBusType = portInfoData.CompiledBusType;
            portInfo{1}.SignalHierarchy = portInfoData.SignalHierarchy;
        end
    end
end

function [portInfo, leafIdx] = genCombinedLeafPortInfo(signalPaths, depth, portInfoData, leafIdx, isOutport)
    portInfo = {};
    leafSignalPath = signalPaths(1);
    currDepthSigPath = leafSignalPath{1}{1};
    for i = 2:depth
        currDepthSigPath = strcat(currDepthSigPath, '.', leafSignalPath{1}{i});
    end

    leafBusObjPath = portInfoData.compiledInfo(leafIdx).BusObjPath;
    busNames = sldvshareprivate('util_get_signal_parts', leafBusObjPath);

    if depth==1 && leafIdx==1
        rootBusInfo = struct('BlockPath', portInfoData.BlockPath,...
                             'SignalName', portInfoData.SignalName,...
                             'BusObject', busNames(depth),...
                             'Dimensions', portInfoData.Dimensions,...
                             'SignalPath', currDepthSigPath);
    else
        rootBusInfo = struct('BusObject', busNames(depth),...
                             'SignalPath', currDepthSigPath);
    end

    % The signal to the output port can be a virtual or non-virtual
    % signal and Output port may not specify the BusObject. In this
    % case we can not understand whether Output port takes a virtual or
    % non-virtual signal unless you check CompiledBusStruct on the portH.
    % Checking CompiledBusStruct on the Outports taking a bus signal is
    % costly because it requires StrictBusMsg to be set to error.
    % Therefore, don't use the IsVirtualBus info for bus Outports. We
    % really don't know it is really virtual or not.
    % (We don't want to check CompiledBusStruct to really know
    % that it is a virtual or non-virtual bus). -DNA
    if depth==1 && ~isOutport
        rootBusInfo.IsVirtualBus = portInfoData.IsVirtualBus;
    end

    portInfo{1} = rootBusInfo;

    currentSigPaths = {};
    currSignalPrefix = '';
    for i=1:length(signalPaths)
        currentDepth = length(signalPaths{i})-1;
        if currentDepth==depth
            if ~isempty(currentSigPaths)
                [portInfo{end+1},leafIdx] = genCombinedLeafPortInfo(currentSigPaths, depth+1,portInfoData,leafIdx,isOutport); %#ok<AGROW>
                currentSigPaths = {};
                currSignalPrefix = '';
            end
            portInfo{end+1} = getLeafPortInfo(portInfoData,leafIdx); %#ok<AGROW>
            leafIdx = leafIdx+1;
        else
            nextSigPrefix = signalPaths{i}{depth+1};
            if ~strcmp(nextSigPrefix,currSignalPrefix)
                currSignalPrefix = nextSigPrefix;
                if ~isempty(currentSigPaths)
                    [portInfo{end+1},leafIdx] = genCombinedLeafPortInfo(currentSigPaths, depth+1,portInfoData,leafIdx,isOutport); %#ok<AGROW>
                    currentSigPaths = {};
                end
            end
            currentSigPaths{end+1} = signalPaths{i};          %#ok<AGROW>
        end
    end
    if ~isempty(currentSigPaths)
        [portInfo{end+1},leafIdx] = genCombinedLeafPortInfo(currentSigPaths, depth+1,portInfoData,leafIdx,isOutport);
    end
end

function [portInfo,leafIdx] = genCombinedLeafPortInfoStruct(signalPaths,depth,portInfoData,leafIdx,isOutport)
    portInfo = {};
    leafSignalPath = signalPaths(1);
    currDepthSigPath = leafSignalPath{1}{1};
    for i = 2:depth
        currDepthSigPath = strcat(currDepthSigPath, '.',leafSignalPath{1}{i});
    end

    structObjPath = portInfoData.compiledInfo(leafIdx).StructObjPath;

    if depth==1 && leafIdx==1
        rootBusInfo = struct('BlockPath',portInfoData.BlockPath,...
                             'SignalName',portInfoData.SignalName,...
                             'StructObject',structObjPath,...
                             'SignalPath',currDepthSigPath);
    else
        rootBusInfo = struct('StructObject',structObjPath,...
                             'SignalPath',currDepthSigPath);
    end

    rootBusInfo.IsVirtualBus = false;

    portInfo{1} = rootBusInfo;

    currentSigPaths = {};
    currSignalPrefix = '';
    for i=1:length(signalPaths)
        currentDepth = length(signalPaths{i})-1;
        if currentDepth==depth
            if ~isempty(currentSigPaths)
                [portInfo{end+1},leafIdx] = genCombinedLeafPortInfoStruct(currentSigPaths, depth+1,portInfoData,leafIdx,isOutport); %#ok<AGROW>
                currentSigPaths = {};
                currSignalPrefix = '';
            end
            portInfo{end+1} = getLeafPortInfo(portInfoData,leafIdx); %#ok<AGROW>
            leafIdx = leafIdx+1;
        else
            nextSigPrefix = signalPaths{i}{depth+1};
            if ~strcmp(nextSigPrefix,currSignalPrefix)
                currSignalPrefix = nextSigPrefix;
                if ~isempty(currentSigPaths)
                    [portInfo{end+1},leafIdx] = genCombinedLeafPortInfoStruct(currentSigPaths, depth+1,portInfoData,leafIdx,isOutport); %#ok<AGROW>
                    currentSigPaths = {};
                end
            end
            currentSigPaths{end+1} = signalPaths{i};          %#ok<AGROW>
        end
    end
    if ~isempty(currentSigPaths)
        [portInfo{end+1},leafIdx] = genCombinedLeafPortInfoStruct(currentSigPaths, depth+1,portInfoData,leafIdx,isOutport);
    end
end

function leafPortInfo = getLeafPortInfo(portInfoData,leafeIdx)
    if leafeIdx==1 && ~portInfoData.IsStructBus  && ~portInfoData.IsStruct
        leafPortInfo.BlockPath = portInfoData.BlockPath;
        leafPortInfo.SignalName = portInfoData.SignalName;
    end
     leafPortInfo.Dimensions = sldvshareprivate('util_resolve_dimensions',...
        portInfoData.compiledInfo(leafeIdx).Dimensions,...
        bdroot(get_param(portInfoData.BlockPath, 'Handle')));
    leafPortInfo.DataType = portInfoData.compiledInfo(leafeIdx).DataType;
    leafPortInfo.Complexity = portInfoData.compiledInfo(leafeIdx).Complexity;
    leafPortInfo.SampleTimeStr = portInfoData.SampleTimeStr;
    if portInfoData.IsStructBus
        leafPortInfo.SampleTime = portInfoData.compiledInfo(leafeIdx).SampleTime;
        leafPortInfo.ParentSampleTime = portInfoData.SampleTime;
        leafPortInfo.SamplingMode = portInfoData.compiledInfo(leafeIdx).SamplingMode;
    else
        leafPortInfo.SampleTime = portInfoData.SampleTime;
        leafPortInfo.ParentSampleTime = portInfoData.SampleTime;
        leafPortInfo.SamplingMode = portInfoData.SamplingMode;
    end
    leafPortInfo.SignalLabels = portInfoData.compiledInfo(leafeIdx).SignalPath;
    leafPortInfo.Used = portInfoData.compiledInfo(leafeIdx).Used;
end

function portInfo = addBusDimsToPortInfo(portInfo, model)
    if ischar(model)
        try
            modelH = get_param(model,'Handle');
        catch Mex %#ok<NASGU>
            modelH = [];
        end
    else
        modelH = model;
    end

    % Check if any port is a bus or bus array
    isBusPresent = false;
    for i=1:length(portInfo)
        if iscell(portInfo{i})
            isBusPresent = true;
            break;
        end
    end

    if isBusPresent
        dataAccessor = Simulink.data.DataAccessor.create(get_param(modelH, 'name'));
        try
            % For each port, set the dimensions of all nested buses
            for i=1:length(portInfo)
                if iscell(portInfo{i})
                    if isfield(portInfo{i}{1}, 'BusObject') && ~isempty(portInfo{i}{1}.BusObject)
                        busObj = Sldv.utils.getBusObjectFromName(portInfo{i}{1}.BusObject, true, dataAccessor);
                        isPortBusArray = false;
                        if isfield(portInfo{i}{1}, 'Dimensions') && any(portInfo{i}{1}.Dimensions ~= 1)
                            isPortBusArray = true;
                        end
                        [portInfo{i}, hasBusArray] = addDimsToBusElemInfo(dataAccessor, portInfo{i}, busObj, isPortBusArray, modelH);
                    else
                        hasBusArray = false;
                    end
                    portInfo{i}{1}.hasArrayOfBuses = hasBusArray;
                end
            end

            % Cleanup cached dataAccessor
            Sldv.utils.getBusObjectFromName(-1);
        catch Mex
            % Cleanup cached dataAccessor
            Sldv.utils.getBusObjectFromName(-1);

            throwAsCaller(Mex);
        end
    end
end

function [portNode, hasBusArray] = addDimsToBusElemInfo(dataAccessor, portNode, busObj, hasBusArrayIn, modelH)
    hasBusArray = hasBusArrayIn;
    for i=2:length(portNode)
        if iscell(portNode{i})
            portNode{i}{1}.Dimensions = sldvshareprivate('util_resolve_dimensions',...
                                        busObj.Elements(i-1).Dimensions,...
                                        modelH);
            subBusObj = Sldv.utils.getBusObjectFromName(busObj.Elements(i-1).DataType, true, dataAccessor);
            [portNode{i}, hasBusArray]  = addDimsToBusElemInfo(dataAccessor, portNode{i}, subBusObj, hasBusArray, modelH);
            if any(portNode{i}{1}.Dimensions ~= 1)
                hasBusArray = true;
            end
        end
    end
end

% LocalWords:  newvalue originalvalue inportinfo
