function [parameterArray, paramMap] = mdl_generate_paraminfo(parameter, modelH, allVarsUsage, dataAccessor)
%

%   Copyright 2020-2023 The MathWorks, Inc.
    parameterArray = [];
    topModelH = modelH;
       paramMap = containers.Map();
    for idx = 1:length(parameter)
        paramName = char(parameter{idx});
        % TODO: create an entry in modelFlatIO for parameters from
        % different workspaces
        varUsage = allVarsUsage(strcmp({allVarsUsage.Name}, paramName));
        uniqueSource = unique({varUsage.Source});
        if length(uniqueSource) == 1
            % If the parameter is used across model references, we can get multiple
            % Simulink.VariableUsage objects for that parameter. In such a case,
            % we should ideally be merging those all together. But as this VariableUsage
            % objects cannot be merged, we are keeping only the first entry. This seems
            % to be sufficient for this method to work correctly. But, please note that
            % the '.Users' information may not be complete in such cases.
            varUsage = varUsage(1);
        end
            % getting compiledInfo of parameters
        if ~(length(varUsage) > 1)
        if ~(isempty(varUsage) || strcmp(varUsage.SourceType, 'mask workspace'))
            if strcmp(varUsage.SourceType, 'base workspace') || ...
                    strcmp(varUsage.SourceType, 'data dictionary') || strcmp(varUsage.SourceType, 'external source')
                modelH = topModelH;
                varId = dataAccessor.identifyByName(paramName);
                currentParamVal = dataAccessor.getVariable(varId);
            else
                modelH = get_param(varUsage.Source, 'Handle');
                currentParamVal = getVariableFromModel(modelH, paramName);
            end

            parameterSourceType = varUsage.SourceType;
            parameterSource = varUsage.Source;

            [isStruct, dataType, busObj] = paramInfo(varUsage, currentParamVal, modelH);

            paramAttribs = [];
            paramAttribs.name = varUsage.Name;
            paramAttribs.isStructBus = false;
            paramAttribs.type  = dataType;
            paramAttribs.Source = parameterSource;
            paramAttribs.SourceType = parameterSourceType;

            % Initialize compiledInfo
            if isa(currentParamVal, 'Simulink.Parameter')
                isSimulinkParam = true;
            else
                isSimulinkParam = false;
            end

            if isSimulinkParam
                paramAttribs.Dimensions = currentParamVal.Dimensions;
                complexity = currentParamVal.Complexity;
            else
                paramAttribs.Dimensions = size(currentParamVal);
                if isreal(currentParamVal) || isstruct(currentParamVal)
                    complexity = 'real';
                else
                    complexity  = 'complex';
                end
            end
            % Populate a default CompiledInfo
            paramAttribs.compiledInfo = populateCompiledInfo(complexity, paramAttribs.Dimensions, paramAttribs.type, ...
                paramAttribs.name, -1, paramAttribs.name, isSimulinkParam, modelH, parameterSource, parameterSourceType);
            portAttributes.IsStruct = false;

            if isStruct == -1
                paramAttribs.Dimensions = size(currentParamVal);
                paramAttribs.compiledInfo.Complexity = 'real';
                if slavteng('feature','ReuseTranslationParamTun')
                    if isa(currentParamVal, 'Simulink.Parameter')
                        paramAttribs.compiledInfo.Value = currentParamVal.Value;
                    else
                        paramAttribs.compiledInfo.Value = currentParamVal;
                    end
                end
            else
                if isa(currentParamVal, 'Simulink.Parameter')
                    paramAttribs.Dimensions = currentParamVal.Dimensions;
                    isSimulinkParam = true;
                end
                if isStruct == 0
                    % If bus param
                    paramAttribs.isStructBus = true;
                    busObject =  busObj;
                    flatSignalInfo = [];
                    busPath = regexprep(paramAttribs.type,'^dto(Dbl|Sgl|Scl)(Flt|Fxp)?_','');
                    busElemNamePath = paramAttribs.type;
                    paramAttribs.compiledInfo = constructLeaves(dataAccessor, busObject, busPath, busElemNamePath, flatSignalInfo, modelH, paramAttribs.name, isSimulinkParam, parameterSource, parameterSourceType);
                elseif isStruct == 1
                    % If struct param
                    paramAttribs.isStructBus = true;
                    portAttributes.IsStruct = true;
                    structName = paramAttribs.name;
                    if isSimulinkParam
                        structObj = currentParamVal.Value;
                    else
                        structObj = currentParamVal;
                    end
                    flatSignalInfo = [];
                    structElemNamePath = regexprep(structName,'^dto(Dbl|Sgl|Scl)(Flt|Fxp)?_','');
                    paramAttribs.compiledInfo = ...
                        constructLeavesForStruct(dataAccessor, structObj, structName, structElemNamePath,flatSignalInfo, modelH, paramAttribs.name, isSimulinkParam, parameterSource, parameterSourceType);
                end
            end

            if isempty(parameterArray)
                parameterArray = paramAttribs;
            else
                parameterArray(end+1) = paramAttribs; %#ok<AGROW>
            end
            paramMap(paramAttribs.name) = length(parameterArray);
        end
        end
    end
end


% To DO (g2325107) -- Consider creating a seperate private function for the methods 'constructLeaves',
% isLeafType, push_into_flatSignalInfo, get_bus_object_properties,
% getParamString and  genFieldID. These files can be reused here and in 'private/util_getIOportBus.m'
function flatSignalInfo = constructLeaves(dataAccessor, busObject, busPath, busElemNamePath, flatSignalInfo, modelH, paramName, isSimulinkParam, sourceWorkspace, parameterSourceType)
    for i=1:length(busObject.Elements)
        subBusObject = busObject.Elements(i);
        subBusElemNamePath = sprintf('%s.%s',busElemNamePath,subBusObject.Name);
        busName = subBusObject.DataType;
        [isLeaf, baseType] = isLeafType(busName, dataAccessor, modelH);
        if(isLeaf)
            subBusObject.DataType = baseType;
            flatSignalInfo = push_into_flatSignalInfo(flatSignalInfo,...
                                                      subBusObject,...
                                                      subBusElemNamePath,...
                                                      paramName,...
                                                      isSimulinkParam, modelH, sourceWorkspace, parameterSourceType);
        else
            [leafebusObject, busName] = Sldv.utils.getBusObjectFromName(busName, true, dataAccessor);
            subBusPath = sprintf('%s.%s',busPath,busName);
            flatSignalInfo = constructLeaves(dataAccessor, leafebusObject, subBusPath, subBusElemNamePath, flatSignalInfo, modelH, paramName, isSimulinkParam, sourceWorkspace, parameterSourceType);
        end
    end
end

function flatSignalInfo = constructLeavesForStruct(dataAccessor, structObj, structPath, structElemNamePath, flatSignalInfo, modelH, paramName, isSimulinkParam, sourceWorkspace, parameterSourceType)
    StructElements = {};
    if isstruct(structObj)
        StructElements = fieldnames(structObj);
    end
    for idx=1:length(StructElements)
        subStructObject = structObj.(StructElements{idx});
        subStructElemNamePath = sprintf('%s.%s', structElemNamePath, StructElements{idx});
        structName = subStructElemNamePath;
        [isLeaf, baseType] = isLeafType(class(subStructObject), dataAccessor, modelH);
        if isLeaf
            if isempty(flatSignalInfo)
                flatSignalInfo = get_struct_object_properties(subStructObject, baseType, structPath, subStructElemNamePath, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType);
            else
                flatSignalInfo(end+1) = get_struct_object_properties(subStructObject, baseType, structPath, subStructElemNamePath, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType); %#ok<AGROW>
            end
        else
            subStructPath = sprintf('%s.%s',structPath,structName);
            flatSignalInfo = constructLeavesForStruct(dataAccessor, subStructObject, subStructPath, subStructElemNamePath, flatSignalInfo, modelH, paramName, isSimulinkParam, sourceWorkspace, parameterSourceType);
        end
    end
end

function structObjectProperties = get_struct_object_properties(subStructObject, dataType, ~, subStructElemNamePath, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType)
    if isreal(subStructObject)
        complexity = 'real';
    else
        complexity = 'complex';
    end
    dims = size(subStructObject);
    structObjectProperties = populateCompiledInfo(complexity, dims,...
        dataType, subStructElemNamePath, -1, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType);
end

function compiledInfoElem = populateCompiledInfo(complexity, dims, dataType, elemPath, sampleTime, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType)
    parameterName = getParamString(elemPath, paramName, false);
    if strcmp(complexity, 'complex')
        error(message('Sldv:Parameters:UnsupportedComplexParam',...
            parameterName));
    end
    compiledInfoElem.Complexity = complexity;
    compiledInfoElem.Dimensions = dims;
    compiledInfoElem.DataType = dataType;
    compiledInfoElem.paramNameStr = parameterName;
    compiledInfoElem.paramInitStr = getParamString(elemPath, paramName, isSimulinkParam);
    compiledInfoElem.SignalPath = elemPath;
    compiledInfoElem.Used = true;
    compiledInfoElem.SampleTime = sampleTime;
    compiledInfoElem.isConstrained = false;
    compiledInfoElem.fieldID = genFieldID();
    if slavteng('feature','ReuseTranslationParamTun') 
        % This would fail if the parameter belongs to DD which is attached
        % only to referneced model and this would be addressed via g3028426
        if ~strcmp(parameterSourceType, 'model workspace') 
            compiledInfoElem.Value = getVarFromGlobalContext(modelH, compiledInfoElem.paramInitStr);
        else
            % assert(slavteng('feature','ModelWorkspaceParamTun'));
            compiledInfoElem.Value = getVariableFromModel(sourceWorkspace, compiledInfoElem.paramInitStr);
        end
    end
end

function [out, baseTypeStr] = isLeafType(dataTypeStr, dataAccessor, modelH)
    baseTypeStr = dataTypeStr;
    if util_is_simulink_builtin(dataTypeStr) || ...
            util_is_fxp_type(dataTypeStr, modelH) || ...
            util_is_string_type(dataTypeStr)
        out = true;
    else
        [isEnum, className] = util_is_enum_type(dataTypeStr);
        if isEnum
            out = true;
            baseTypeStr = className;
        else
            out = false;
        end
    end
    if ~out
        out = strncmp(dataTypeStr,'fixdt',5) || ...
              strncmp(dataTypeStr,'numerictype',11);
        if(~out)
            %check for Simulink NumericType
            [out, baseTypeStr] = util_is_sim_numeric_type(dataTypeStr, dataAccessor);
        end
        if ~out
            %check is it alias type
            [out, baseTypeStr] = util_is_sim_alias_type(dataTypeStr, dataAccessor);
        end
    end
end

function flatSignalInfo = push_into_flatSignalInfo(flatSignalInfo,subBusObject,busPath, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType)
    if isempty(flatSignalInfo)
        flatSignalInfo = get_bus_object_properties(subBusObject,busPath, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType);
    else
        flatSignalInfo(end+1) = get_bus_object_properties(subBusObject,busPath, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType);
    end
end

function busObjectProperties = get_bus_object_properties(subBusObject,busPath, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType)
    busObjectProperties = populateCompiledInfo(subBusObject.Complexity, subBusObject.Dimensions,...
        subBusObject.DataType, busPath, subBusObject.SampleTime, paramName, isSimulinkParam, modelH, sourceWorkspace, parameterSourceType);
end

function paramString = getParamString(busPath, parameterName, isSimulinkParam)
% replace the bus type name in the bus path with parameter name
    splitBusPath = strsplit(busPath,'.');
    % Replace the first string
    if(length(splitBusPath) > 1)
        if isSimulinkParam
            splitBusPath{1} = [parameterName '.Value'];
        else
            splitBusPath{1} = parameterName;
        end
    end
    paramString = strjoin(splitBusPath,'.');
end

function fieldID = genFieldID()
    persistent currentCounter;
    if isempty(currentCounter)
        currentCounter = 0;
    else
        currentCounter = currentCounter + 1;
    end
    fieldID = currentCounter;
end

function isStruct = isStructParam(paramVariable)
     isStruct =  (isa(paramVariable,'Simulink.Parameter') ...
                    && isa(paramVariable.Value,'struct')) || ...
                    isa(paramVariable,'struct');
end

function [isStruct, dataType, busObj] = paramInfo(paramVarUsage, paramVariable, modelH)
    busObj = [];
    if ~isStructParam(paramVariable)
        if isa(paramVariable, 'Simulink.Parameter')
            dataType = class(paramVariable.Value);
        else
            dataType = class(paramVariable);
        end
        isStruct = -1;
    else
        % paramVariable is a struct/Bus Parameter, So, we create values
        % for each of it's leafs
        dataType = getDataTypeOfParam(paramVarUsage, paramVariable);
        dataType = strtrim(erase(dataType, 'Bus:'));
        scope = Simulink.data.internal.getLegacyDataSourceObj(modelH);
        try
            currentEntryDataType = sl('slbus_get_object_from_name', dataType, true, scope);
        catch
            % No Bus data type for the paramVariable, This means that we are
            % dealing with a struct variable
            currentEntryDataType = '';
        end
        if ~isempty(currentEntryDataType)
            busObj = currentEntryDataType;
            isStruct = 0;
        else
            % 'Simulink.Parameter' parameter that is of struct type and
            % struct parameter that is of struct type
            dataType = 'struct';
            isStruct = 1;
        end
    end
end

function dataType= getDataTypeOfParam(paramVarUsage, paramVariable)
    % Get the data Type of the paramVariable.

    % This Function gets the data type of a parameter variable that is
    % assigned to it by the block using it.
    % For example, Consider a parameter of type 'Simulink.Parameter'. This
    % parameter can be used in multiple instances in several blocks in the
    % same model. We need to find if the parameter is of bus Type even if
    % only one block out of all these defines it. So, we iterate through
    % all the blocks and look for the property 'OutputDataTypeStr'

    % If the bus type is defined in the DataType Field of the
    % Simulink.Parameter, then return it
    isSimulinkParam = isa(paramVariable,'Simulink.Parameter');
    if (isSimulinkParam && isstruct(paramVariable.Value))
        if isSimulinkParam
            dataType = paramVariable.DataType;
        else
            dataType = class(paramVariable.Value);
        end
        if sldvshareprivate('util_is_simulink_builtin',dataType)
            return;
        end
    end
    %If not built-in, then the data type is either a 'struct' or 'Bus'
    blkSources = paramVarUsage.UsedByBlocks;
    for idx = 1:length(blkSources)
        try
            val = get_param(blkSources{idx}, 'OutDataTypeStr');
            dataType = val; % If the block does not have that parameter, we immediately skip to catch
        catch
            dataType = 'struct';
            continue;
        end
    end
end
