function [status, tunableParamList, allParamVarList] = getParamsFromModel(hModel, codeLocation, ignoreWarnings)
    % getParamsFromModel will compile the model and look for parameters and its
    % attributes (Value, Min, Max, Location).
    % paramlist : a struct with parameter names as fields and
    % Sldv.ParameterTuning.Param as values
    % status : If all went well status is 1 else 0.

%   Copyright 2013-2023 The MathWorks, Inc.

    if nargin < 3
        ignoreWarnings = false;
    end   
    if nargin < 2 
        codeLocation = [];
    end
    analyzeCodeForParameters = ~isempty(codeLocation);    
    [status, allParamVarListMap] = Sldv.ParameterTuning.Manager.getAllParamVarsFromModel(hModel);
    nontunableParamMap = getNonTunableParamMap(allParamVarListMap, analyzeCodeForParameters, codeLocation, ignoreWarnings);
    refMdls = keys(allParamVarListMap);
    paramVarLists = values(allParamVarListMap);
    allParamVarList = [];
    tunableParamList = struct;
    for idx = 1:length(refMdls)
        paramVarList = paramVarLists{idx};
        currMdlH = get_param(refMdls{idx},'Handle');
        [tunableParamList, paramVarList] = getAttribs(tunableParamList, paramVarList, currMdlH, nontunableParamMap);        
        allParamVarList = cat(1, allParamVarList, paramVarList);
    end
    % remove parameters from diff wks for tuning
    [tunableParamList, allParamVarList] = removeParamsWithSameName(tunableParamList, allParamVarList);
end

function [paramList, allParamVarList] = getAttribs(paramList, allParamVarList, hModel, nontunableParamMap)    
    vlen = length(allParamVarList);    
    for idx = 1: vlen
        if isKey(nontunableParamMap, allParamVarList(idx).Name)
            continue;
        end
        paramVariable = allParamVarList(idx);
        
        % populate Parameter Values
        if ~slavteng('feature','BusParameterTuning') || ~isStructParam(paramVariable)
            name = paramVariable.Name;
            value = []; %#ok<NASGU>
            min = [];
            max = [];
            type = [];
            if isa(paramVariable.Value, 'Simulink.Parameter')
                value = paramVariable.Value.Value;
                min = paramVariable.Value.Min;
                max = paramVariable.Value.Max;
            else
                value = paramVariable.Value;
            end
            
            typeinfo = whos(paramVariable.Name);
            if ~isempty(typeinfo)
                type = typeinfo.class;
            end
            paramList.(name) = createParamEntry(paramVariable, paramList, name, value, min, max, type, hModel);
        else
            % paramVariable is a struct/Bus Parameter, So, we create values
            % for each of it's leafs
            dataType = getDataTypeOfParam(paramVariable);
            dataType = strtrim(erase(dataType, 'Bus:'));
            fieldName = '';
            scope = Simulink.data.internal.getLegacyDataSourceObj(hModel);
            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) && ~isstruct(paramVariable.Value)
                % 'Simulink.Parameter' parameter that is of Bus type
                paramList.(paramVariable.Name) = createBusParamEntry(paramVariable, paramList,...
                          paramVariable.Value.Value, dataType, fieldName,hModel);
            elseif ~isempty(currentEntryDataType) && isstruct(paramVariable.Value)
                % Struct parameter that is of Bus type
                paramList.(paramVariable.Name) = createBusParamEntry(paramVariable, paramList,...
                              paramVariable.Value, dataType, fieldName,hModel);
            else
                % 'Simulink.Parameter' parameter that is of struct type and
                % struct parameter that is of struct type
                paramList.(paramVariable.Name) = createStructParamEntry(paramVariable, paramList, ...
                              paramVariable.Value, fieldName, hModel);
            end
        end
    end
end

function [tunableParamList, allParamVarList] = removeParamsWithSameName(paramList, allParamVarList)
    tunableParamList = struct;
    if ~ isempty(allParamVarList)
        uniqueParamList = [];
        paramName = {allParamVarList.Name};
        uniqueParamName = unique(paramName, 'Stable');
        for idx = 1:length(uniqueParamName)
            params = allParamVarList(strcmp(paramName, uniqueParamName{idx}));
            % this is to check when parameter with same name are used from
            % same workspace
            uniqueSource = unique({params.Source});
            if ~isscalar(params) && (isempty(uniqueSource) || ~isscalar(uniqueSource))
                sourceStr = '';
                for jdx = 1:length(params)
                    if isempty(sourceStr)
                        sourceStr = params(jdx).Source;
                    else
                        sourceStr = strcat(sourceStr, ' , ', params(jdx).Source);
                    end
                end
                warning(message('Sldv:Parameters:ParameterWithSameNameInDiffWks', uniqueParamName{idx}, sourceStr));
            else
                uniqueParamList = cat(1, uniqueParamList, params);
                if isfield(paramList, uniqueParamName{idx})
                    tunableParamList.(uniqueParamName{idx}) = paramList.(uniqueParamName{idx});
                end
            end
        end
        allParamVarList = uniqueParamList;
    end
end

function dataType= getDataTypeOfParam(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
    paramVariable.Value;
    parameterVal = paramVariable.Value;
    isSimulinkParam = isa(parameterVal, 'Simulink.Parameter');
    if isSimulinkParam
        dataType = parameterVal.DataType;
    else
        dataType = class(parameterVal);
    end
    if sldvshareprivate('util_is_simulink_builtin',dataType)
        return;
    end
    %If not built-in, then the data type is either a 'struct' or 'Bus'
    blkSources = paramVariable.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
            continue;
        end
    end
end

function currentEntry = createStructParamEntry(paramVariable, paramlist, ...
                              structOrLeafElem, fieldName, hModel)
    % Create entries for a structure parameters that do not have a bus
    % definition associated with them
    if isa(structOrLeafElem, 'Simulink.Parameter')
        structOrLeafElem = structOrLeafElem.Value;
    end
    currentEntry = struct();
    if isa(structOrLeafElem, 'struct')
        subEntries = fieldnames(structOrLeafElem);
        for idx = 1:length(subEntries)
            currentElementName = subEntries{idx};
            if isempty(fieldName)
                currentFieldName = currentElementName;
            else
                currentFieldName = [fieldName , '.', currentElementName];
            end
            % Executable name
            currentLeafName = [paramVariable.Name, '.', currentFieldName];
            currentVal = slResolve(currentLeafName,hModel);
            dataTypeStr = class(currentVal);
            if ~isstruct(currentVal)
                min = [];
                max = [];
                name = currentLeafName;
                currentEntry.(subEntries{idx}) = createParamEntry(paramVariable, paramlist, name, structOrLeafElem.(subEntries{idx}), min, max, dataTypeStr, hModel);
            else
                currentEntry.(subEntries{idx}) = createStructParamEntry(paramVariable, paramlist, ...
                    structOrLeafElem.(subEntries{idx}), currentFieldName, hModel);
            end
        end
    end
end

function busEntry = createBusParamEntry(paramVariable, paramlist, structOrLeafElem, busDataTypeStr,...
                                    fieldName, hModel)
    if isa(structOrLeafElem, 'Simulink.Parameter')
        structOrLeafElem = structOrLeafElem.Value;
    end
    scope = Simulink.data.internal.getLegacyDataSourceObj(hModel);
    busDataType = evalin(scope,  busDataTypeStr);
    busEntry = struct();
    if isa(structOrLeafElem, 'struct')
        subEntries = fieldnames(structOrLeafElem);
        for idx = 1:length(subEntries)
            currentBusElement = busDataType.Elements(idx);
            if isempty(fieldName)
                currentFieldName = [paramVariable.Name, '.', currentBusElement.Name];
            else
                currentFieldName = [fieldName , '.', currentBusElement.Name];
            end
            dataTypeStr = strtrim(erase(currentBusElement.DataType, 'Bus:'));
            if isBaseType(dataTypeStr)
                name = currentFieldName;
                min = currentBusElement.Min;
                max = currentBusElement.Max;
                busEntry.(subEntries{idx}) = createParamEntry(paramVariable, paramlist, name, structOrLeafElem.(subEntries{idx}), min, max, dataTypeStr, hModel);
            else
                busEntry.(subEntries{idx}) = createBusParamEntry(paramVariable, paramlist, ...
                    structOrLeafElem.(subEntries{idx}), dataTypeStr, currentFieldName, hModel);
            end
        end
    end
end

function entry = createParamEntry(paramVariable, paramlist, name, value, min, max, type, hModel)
    loc = []; %#ok<NASGU>
    if isfield(paramlist, paramVariable.Name)
        param = paramlist.(paramVariable.Name);
        users = paramVariable.Users;
        % If there is just one user and its a model itself we skip adding
        % it
        if isscalar(users) && ...
                strcmp(get_param(users, 'BlockType'), 'ModelReference')
            entry = param;
            return;
        else
            loc = unique(cat(1, param.Location, paramVariable.Users));
        end
    else
        loc = paramVariable.Users;
    end
        
    entry = Sldv.ParameterTuning.Param(name, 'Model', hModel, 'Value', value,...
            'Min', min, 'Max', max, 'Type', type,'Location', loc, 'Source', paramVariable.Source);
end

function skipParameter = checkParameter(parameter, alwaysNontunableParams, modelH, paramNameToObsMap, paramsInCode, analyzeCodeForParameters, ignoreWarnings, dataAccessor)


    skipParameter = false;
   
    % Data dictionary does not support tuning of matlab variables. Hence
    % during code generation, all these variables gets inlined in the
    % code and won't be available for sldv for tuning during the analysis.
    % Here 'warning' is displayed to indicate this. As suggested by Data
    % dictionary workflow, the use of 'Sldv.Parameter' instead is
    % advisable and is also indicated through the warning below. A
    % similar warning is indicated during analysis (settings_handler.m)

    % We only support parameters in Brokered Sources(.MAT) and data dictionary that have a type 'Simulink.Parameter'
    if (strcmp(parameter.SourceType, 'data dictionary') || strcmp(parameter.SourceType, 'external source')) && ~isa(parameter.Value,'Simulink.Parameter')
        skipParameter = true;
        if ~ignoreWarnings
            warning(message('Sldv:Parameters:DDMATLABVariableTuningNotSupportedWithName', parameter.Name));
        end
        return;
    end

    % We dont support matlab variables parameter tuning from model workspace
    if strcmp(parameter.SourceType, 'model workspace') && ~isa(parameter.Value,'Simulink.Parameter')
        skipParameter = true;
        if ~ignoreWarnings
            warning(message('Sldv:Parameters:MdlWksMATLABVariableTuningNotSupported', parameter.Name, parameter.Source));
        end
        return;
    end

    % Simulink.ValueType have their DataType defined as
    % "ValueType: SimulinkValueTypeObjectName"
    if isa(parameter.Value,'Simulink.Parameter') && contains(parameter.Value.DataType,'ValueType')
        skipParameter = true;
        if ~ignoreWarnings
            warning(message('Sldv:Parameters:SimulinkVaueTypeNotSupported', parameter.Name));
        end
        return;
    end

    if isa(parameter.Value,'Simulink.VariantExpression')
        skipParameter = true;
        % No need to issue a warning for this -- There is no chance of any
        % confusion as in the case of normal parameters, as a variant
        % control can only be used in the context of Variant blocks --
        % which is not supported by SLDV
        return;
    end
        
    if isa(parameter.Value,'Simulink.data.Expression')
        % Dont skip parameters that are Simulink Expressions, tuning is 
        % supported but the implicit constraints imposed by an expression
        % is not maintained (g2666037)
        if ~ignoreWarnings
            warning(message('Sldv:Parameters:SimulinkExprLimitedSupport', parameter.Name));
        end
    end

    if ~isa(parameter.Value,'Simulink.Parameter') && ~isstruct(parameter.Value)
        try
            skipParameter = ~(checkAllAllDims(isnumeric(parameter.Value)) || ...
                              checkAllAllDims(islogical(parameter.Value)) ||...
                              isa(parameter.Value,'embedded.fi'));
        catch Mex 
            skipParameter = true;
        end
        if skipParameter
            if ~ignoreWarnings
                warning(message('Sldv:xform:MdlComp:genParameterConfig:ParamNotNumLogFixpt',parameter.Name));
            end
            return;
        end
        
        try
            skipParameter = ~checkAllAllDims(isreal(parameter.Value));
        catch Mex 
            skipParameter = true;
            return;
        end
        if skipParameter
            return;
        end
        
    end
    
    if isa(parameter.Value,'Simulink.Parameter') && strcmp(parameter.Value.Complexity, 'complex')
        skipParameter = true;
        return;
    end
    
    if isa(parameter.Value,'Simulink.Parameter') && ~isBuiltinTypeParameter(parameter,modelH,dataAccessor) ...
            && ~isStructParam(parameter)
        if ~ignoreWarnings
            warning(message('Sldv:xform:MdlComp:genParameterConfig:ParamNotNumLogFixpt',parameter.Name));
        end
        skipParameter = true;
        return;
    end
    
    if (isa(parameter.Value, 'Simulink.Parameter') && ...
        ((isstruct(parameter.Value.Value) && (~isscalar(parameter.Value.Value))) || ...
         (isstruct(parameter.Value) && (~isscalar(parameter.Value)))))
        % Parameters that are array of structure or bus parameters are not
        % supported for tunability
        if ~ignoreWarnings
            warning(message('Sldv:Parameters:ArrayOfBusNotSupported', parameter.Name));
        end
        skipParameter = true;
        return;
    end

    for idx=1:length(alwaysNontunableParams)
        if strcmp(alwaysNontunableParams(idx).Value, parameter.Name)
            if ~ignoreWarnings
                warning(message('Sldv:xform:MdlComp:genParameterConfig:ParamUsedByBlkDgm',parameter.Name,alwaysNontunableParams(idx).Name));
            end
            skipParameter = true;
            return;
        end
    end
    for jdx=1:length(parameter.UsedByBlocks)
        block = parameter.UsedByBlocks{jdx};
        if strcmp(get_param(block,'Type'),'block')
            blockType = get_param(block,'BlockType');
            if any(strcmp(blockType,{'Inport','Outport'}))
                if ~ignoreWarnings
                    warning(message('Sldv:xform:MdlComp:genParameterConfig:ParamInOutPort',parameter.Name));
                end
                skipParameter = true;
                return;
            end
             if any(strcmp(blockType,{'Lookup','Lookup_n-D','LookupNDDirect',...
                                                         'Interpolation_n-D','Lookup Table Dynamic',...
                                                         'PreLookup'}))
                 if ~ignoreWarnings
                     warning(message('Sldv:Parameters:LookupTableTuningNotSupported',parameter.Name));
                 end
                 skipParameter = true;
                 return;
             elseif any(strcmp(blockType,{ ...
                                           'Saturate', ...
                                            'Selector', ...   
                                            'DiscreteIntegrator', ...
                                            'SwitchCase', ...
                                            'ForIterator', ...
                                            }))
                 if ~ignoreWarnings
                     warning(message('Sldv:Parameters:UnsupportedBlockType',parameter.Name, blockType));
                 end
                 skipParameter = true;
                 return;
            end
        end
    end

    for jdx=1:length(parameter.UsedByBlocks)
        block = parameter.UsedByBlocks{jdx};
        blockParameters = Sldv.xform.maskUtils.deriveAllParameters(block);
        if ~isempty(blockParameters)
            matched = blockParameters(strcmp(parameter.Name,{blockParameters.Name}));
            if ~isempty(matched)
                assert(isscalar(matched));
                if strcmp(matched.Tunable,'off')
                    if ~ignoreWarnings
                        warning(message('Sldv:xform:MdlComp:genParameterConfig:ParamNotTunable',parameter.Name,cr_to_space(block)));
                    end
                    skipParameter = true;
                    return;
                end
            end
        end
    end
    
    if isKey(paramNameToObsMap, parameter.Name)
        if ~ignoreWarnings
            warning(message('Sldv:Parameters:ParameterCommonToDesignAndObserverNotSupported', parameter.Name, getfullname(modelH), getfullname(paramNameToObsMap(parameter.Name))));
        end
        skipParameter = true;
        return;
    end
    if analyzeCodeForParameters && ~any(ismember(paramsInCode, parameter.Name))
        % analyzeCodeForParameters == true => parameterConfiguration = 'Auto' or 'Determine from generated code'
        % When we are analyzing code to tune only the parameters that are
        % tunable in code and the parameter we found using
        % 'Simulink.findVars' (parameter.Name) is not found in the list of
        % parameters tunable in code, we filter it.
        if ~ignoreWarnings
            warning(message('Sldv:Parameters:ParameterNotTunableInCode', parameter.Name));
        end
        skipParameter = true;
        return;
    end
end

function out = checkAllAllDims(value)
    out = all(value);
    while ~isscalar(out)
        out = all(out);
    end
end

function isStruct = isStructParam(paramVariable)
    % Returns true in the following cases
    % 1) paramVariable is a parameter that is a 'Simulink.Parameter' of Bus Data Type
    % 2) paramVariable is a parameter that is a 'Simulink.Parameter' of struct Data Type
    % 3) paramVariable is a parameter that is a structure of Bus Data Type
    % 4) paramVariable is a parameter that is a structure that is of struct
    % dataType
     isStruct =  (isa(paramVariable.Value,'Simulink.Parameter') ...
                    && isa(paramVariable.Value.Value,'struct')) || ...
                    isa(paramVariable.Value,'struct');
end

function out = isBuiltinTypeParameter(parameter,modelH, dataAccessor)
    isBaseParam = isBaseType(parameter.Value.DataType);
    if ~isBaseParam
        [isAliasParam, resolvedDataType] = sldvshareprivate('util_is_sim_alias_type', ...
            parameter.Value.DataType, dataAccessor, modelH);
        out = isBaseParam || isAliasParam;
    else
        out = isBaseParam;
    end

    if ~out
        % Parameter does not have a base or alias type. It can be a 
        % sumulink.NumericType type.
        try
            % Numeric type can be defined in model workspace, base
            % workspace or DD attached to a model which may be different
            % than the workspace where parameter is used
            try
                mdlWks = get_param(parameter.Source, 'ModelWorkspace');
                resolvedDataType = evalin(mdlWks, resolvedDataType);
                out = isa(resolvedDataType,'Simulink.NumericType');
            catch
                % TopTester: test/toolbox/sldv/params/tParameterConfigGUI_3.m
                % -testspec:tCheckSlParamOfNumericType
                out = sldvshareprivate('util_is_sim_numeric_type', ...
                        resolvedDataType, dataAccessor);
            end
        catch
            % an exception occurred. Assume that the type is not 
            % of a builtin type
            out = false;
        end
    end
end

function out = isBaseType(dtype)
    out = any(strcmp(dtype,{'auto','double','single',...
        'int8','int16','int32', 'int64','Simulink.NumericType',...
        'uint8','uint16','uint32','uint64','boolean'})) || strncmp(dtype,'fixdt',5) || strncmp(dtype,'Enum:',5);
end

function out = cr_to_space(in)
    out = in;
    if ~isempty(in)
        out(in==10) = char(32);
    end    
end

function nontunableParamMap = getNonTunableParamMap(allParamVarListMap, analyzeCodeForParameters, codeLocation, ignoreWarnings)
refMdls = keys(allParamVarListMap);
paramVarLists = values(allParamVarListMap);
nontunableParamMap = containers.Map();  
paramsInCode = {};
if analyzeCodeForParameters && ~isempty(codeLocation)
    paramsInCode = sldvshareprivate('getParamListTunableInCode', codeLocation);
end

% iterate over list of parameters and check whether parameters are tunable
for i = 1:length(refMdls)
    paramVarList = paramVarLists{i};
    modelH = get_param(refMdls{i}, 'Handle');
    alwaysNontunableParamNames = {'FixedStep','StartTime','StopTime'};   
    alwaysNontunableParams = struct('Name','','Value','');
    for j=1:length(alwaysNontunableParamNames)
        alwaysNontunableParams(j) = struct('Name',alwaysNontunableParamNames{j},...
            'Value', get_param(modelH, alwaysNontunableParamNames{j}));
    end
    
    % remove parameters that are used as variant control parameters from consideration
    variantControlVars = Sldv.utils.findVariantControlVars(get_param(modelH, 'name'));
    for j=1:length(variantControlVars)
        controlVar = variantControlVars(j);
        paramVarList = paramVarList(not(strcmp({paramVarList.Name}, controlVar.Name)));
    end
    dataAccessor = Simulink.data.DataAccessor.createForGlobalNameSpaceClosure(modelH);

    % This map would be required to filter out the parameters that are
    % present in both design model and observer model as 
    % 1). Such a parameter is marked as tunable only in design IR, while it
    %     gets inlined in Observer IR
    % 2). Now if backend assigns a value to this parameter other than it's 
    %     default value then design and observer logic would end up using 
    %     different values for same parameter which is incorrect
    paramNameToObsMap = Sldv.utils.getParamNamesToObsRefMap(modelH);
    for j=1:numel(paramVarList)
        if checkParameter(paramVarList(j), alwaysNontunableParams, modelH, ...
            paramNameToObsMap, paramsInCode, analyzeCodeForParameters, ...
            ignoreWarnings, dataAccessor)
            nontunableParamMap(paramVarList(j).Name) = true;
        end
    end
end
end

% LocalWords:  DDMATLAB xform Dgm Basetype paramlist
