classdef Manager < handle
    % MANAGER Summary of this class goes here
    %   Detailed explanation goes here
    

%   Copyright 2013-2022 The MathWorks, Inc.

    properties (Access = 'private')
        % Array of SLDV.Param objects
        ParamStruct  = [];
        % Param attribs for book keeping in manager;
        % ParamAttributes.(<param name>) is a struct with fields isInModel and isInConfig
        IsInModel % containers.Map: "param name" -> 1/0; initialized in constructor
        IsInConfig % containers.Map: "param name" -> 1/0; initialized in constructor
        UseInAnalysis % containers.Map: "param name" -> 1/0; initialized in constructor
    end
    
    properties
        Config % Current configset or configset reference
        ProductTag
        HModel
    end
    
    methods (Static)
        [status, allParamVarsMap] = getAllParamVarsFromModel(mdl, reportCompileErrors);
        [status, tunableParamList, allParamVars] = getParamsFromModel(mdl,varargin);
        [tf, params, errparams, errstr] = loadParamsFile(file);
        writeToParamsFile(filename, pstruct, comments);
    end
    
    methods (Static = true, Access = 'private')
        param = getOneLeaf(value);
        parameterList = createFlatParamList(paramValue);
        function [isStructParam, topParamName] = findIfStructParam(param)
            % isStructParam will return true when param is a Sldv.ParameterTuning.Param that
            % is part of a structure - a constraint on the leaf of a
            % struct/Bus parameter

            % topParamName will return the top Bus/Parameter name
            % Example: if param.Name = 'myParam.subBus.a', topParamName
            %          = 'myParam'

            % Constraints on leaves of struct/Bus params will have '.' in parameter names
            % Example: 'myParam.myField'
            paramFields = strsplit(param.Name, '.');
            isStructParam = numel(paramFields) > 1;
            topParamName = paramFields{1};
    end
    end
    methods
        
        function this = Manager(hModel, varargin)
            if nargin == 2
                source = varargin{1};
                this.Config = source;
                if isa(this.Config, 'Simulink.ConfigSetRef')
                    localCS = this.Config.LocalConfigSet;
                    sldv = localCS.getComponent('Design Verifier');
                else
                    sldv = this.Config.getComponent('Design Verifier');
                end
                this.ProductTag = sldv.ProductTag;
                % create Sldv.ParameterTuning.Param objects from config
                [pList, use] = this.getParamsFromConfig();
                flds = fields(use);
            else
                pList = [];
                flds = [];
                use = [];
            end
            this.HModel = hModel;

            this.IsInModel = containers.Map;
            this.IsInConfig = containers.Map;
            this.UseInAnalysis = containers.Map;            
            
            % merge in the Sldv.ParameterTuning.Param objects
            this.importAndMerge(pList);
            % If none of the params are used in analysis, return
            if isempty(use) || isempty(flds)
                return
            end
            % set the checkbox (use) info.
            if slavteng('feature','BusParameterTuning')
                for idx = 1:length(use)
                    this.UseInAnalysis(use(idx).name) = use(idx).value;
                end
            else
                flds = fields(use);
                for i = 1:length(flds)
                    this.UseInAnalysis(flds{i})= use.(flds{i});
                end
            end
        end
        
        [list, use] = getParamsFromConfig(this)

        % Returns a list of parameters, constraints that are to be tuned in
        % the analysis
        [pNames, pConstraints, pChecks, sldv_params] = getParameterSettings(this)
        
        % A convenience func to get the list of params from file
        list = getParamsFromFile(this, file)
        
        
        % takes a list of Sldv.ParameterTuning.Params and merges into the current
        % list of parameters in manager
        importAndMerge(this, params)
        
        
        % Add constraint to a param
        updateParam(this, name, constr)
       
        saveToFile(this, file)
        [paramNames, values] = getSaveToConfigValues(this)
        saveToConfig(this);
        
        % Currently we export only to .m file.
        % This function should grow as we introduce exporting to other formats.
        export(this, file)
        function useAllParamsInAnalysis(this)
            if isempty(this.ParamStruct)
                return;
            end
            params = fieldnames(this.ParamStruct);
            for idx = 1:length(params)
                this.UseInAnalysis(params{idx}) = true;
            end
        end           
        function paramsinmodel = listParamsInModel(this)
            paramsinmodel = {};
            flds = fieldnames(this.ParamStruct);
            for idx = 1:length(flds)
                p = this.ParamStruct.(flds{idx});
                if slavteng('feature','BusParameterTuning') && isstruct(p)
                    % Any of the leafs can be not in the model
                    currentParamLeafs = Sldv.ParameterTuning.Manager.createFlatParamList(p);
                    for jdx = 1:length(currentParamLeafs)
                        if this.inModel(currentParamLeafs(jdx))
                            paramsinmodel{end+1} = currentParamLeafs(jdx); %#ok<AGROW>
                        end
                    end
                else
                    if this.inModel(p)
                        paramsinmodel{end+1} = p; %#ok<AGROW>
                    end
                end                
            end
        end
        
        
        function paramsinconf = listParamsInConfig(this)
            paramsinconf = {};
            flds = fieldnames(this.ParamStruct);
            for i = 1:length(flds)
                p = this.ParamStruct.(flds{i});
                if this.inConfig(p)
                    paramsinconf{end+1} = p; %#ok<AGROW>
                end                
            end
        end
        
        % rescan the model and config for params. split into refreshModel and refreshConfigSet
        addedconstraints = refreshModelParams(this, varargin)
        
        refreshConfigParams(this)
        
        
        % Forget everything I want to start fresh!
        clearAll(this, pnames)
        
        % get the Sldv.ParameterTuning.Param class object corresponding to
        % a name string
        function param = getParamObjByName(this, parameterName)
            pList = this.ParamStruct;
            % TopParam.subBus1.subBus2.Leaf is split to an array {'TopParam', 'subBus1', 'subBus2', 'leaf'}
            pHierarchicalList = strsplit(parameterName, '.');
            if length(pHierarchicalList)  == 1
                param = pList.(parameterName);
            else
                pValue = pList;
                for idx = 1:length(pHierarchicalList)
                    pValue = pValue.(pHierarchicalList{idx});
                end
                param = pValue;
            end
        end
        
        function removeFromConfig(this, param)
            if slavteng('feature','BusParameterTuning') && isstruct(param)
                % Removing a struct param from config means to remove every
                % single leaf of the struct param from config
                this.IsInConfig =  this.setAllLeavesOfStructOnAMap(param, 0, this.IsInConfig);
            else
                if this.inConfig(param)
                    this.IsInConfig(param.Name) = 0;
                    return;
                end
            end
        end
            
        
        % Returns the current list of parameters.
        function list = getAllParams(this)
            list = this.ParamStruct;
        end
        
              
        function location = locate(this, pname)
            location = '';
            if slavteng('feature','BusParameterTuning')
                % If the name of the parameter we are trying to locate is
                % the part of a Bus, Then we find the first leaf of the top
                % level parameter
                % Since all the leafs of a topLevel parameter are defined
                % by the same block, the location should be the same.
                hierarchicalParamNameArr = strsplit(pname, '.');
                topParamName = hierarchicalParamNameArr{1};
                if isfield(this.ParamStruct, topParamName)
                    param = this.ParamStruct.(topParamName);
                    if isstruct(param)
                        param = Sldv.ParameterTuning.Manager.getOneLeaf(param);
                    end
                    if isKey(this.IsInModel,pname) && this.IsInModel(pname)
                        location = param.getLocation2Str;
                    end
                end
            else
                if isfield(this.ParamStruct, pname)
                    param = this.ParamStruct.(pname);
                    if this.inModel(param)
                        location = param.getLocation2Str;
                    end
                end
            end
        end
        
        %turn on/off the flag to use constraint for a parameter in
        %analysis. If the input is a cell array, this operation is applied
        %to all entries in the cell.
        function selectParamForAnalysis(this, pname, tf)
            if isempty(pname)
                return;
            end
            if iscell(pname)
                for i = 1:length(pname)
                    if (length(tf)< i)
                        this.UseInAnalysis(pname{i}) = 0;
                        warning(message('Sldv:Parameters:NoConstraintSet'));
                    else
                        this.UseInAnalysis(pname{i}) = tf{i};
                        % Add/remove from config
                        this.initializeParameter(pname{i}, tf{i});
                    end
                end
            else
                this.UseInAnalysis(pname) = tf;
                % Add/remove from config
                this.initializeParameter(pname, tf);
            end           
        end
        
        function initializeParameter(this, paramName, tf)
            % Initialize a parameter with '[]' if it is selected for tuning
            % and it is a part of parameterStruct
            paramStr = strjoin({'this.ParamStruct', paramName},'.');
            toBeInitialized = isfieldOfStructParam(this, this.ParamStruct, paramName);
            if toBeInitialized
                param = eval(paramStr);
            end
            if toBeInitialized && ~param.hasConstraint()
                this.updateParamInConfig(tf, paramName);
            end
        end

        
        function params = getFlatListOfParams(this)
            % Returns a flat parameter structure with a 'name' and
            % 'Sldv.Parameter.param' object fields.
            params = [];
            if ~isempty(this.ParamStruct)
                flds = fieldnames(this.ParamStruct);
                for idx = 1:length(flds)
                    currentValue = this.ParamStruct.(flds{idx});
                    leafsInParam = Sldv.ParameterTuning.Manager.createFlatParamList(currentValue);
                    for jdx = 1:length(leafsInParam)
                        if isempty(params)
                            params = struct('name', leafsInParam(jdx).Name, 'value', leafsInParam(jdx));
                        else
                            params(end+1) = struct('name', leafsInParam(jdx).Name, 'value', leafsInParam(jdx)); %#ok<AGROW>
                        end
                    end
                end
            end
        end
        
        function tf = isParamUsedInAnalysis(this, pname)
            if isKey(this.UseInAnalysis, pname)
                tf = this.UseInAnalysis(pname);
            else
                tf = 0;
            end
        end
        
        function [status, parameterStruct] = autoPopulateParameterConstraints(this, modelH, issueWarnings, codeLocation)
            % This function discovers all the parameters that are tunable
            % in the model, filters the ones that are not tunable in code
            % and returns the resulting parameters
            parameterStruct = [];
            % Turn off warning backtrace. This is done to avoid showing the
            % source related information on command prompt when the
            % analysis is run from API
            backTraceObj = warning('backtrace');
            warning('backtrace', 'off');
            backtraceCleanUpVar = onCleanup(@() warning('backtrace', backTraceObj.state));
            try
                if nargin == 4
                    % When a codeLocation is provided, we tune all the
                    % parameters that are tunable in code
                    this.refreshModelParams(issueWarnings, codeLocation);
                else
                    this.refreshModelParams(issueWarnings);
                end
            catch ME
                % This could happen when an unhandled exception occur
                % during code analysis
                % As this is an API call, we need to catch this exception
                % and handle this through avtcgirrunupcollect
                sldvshareprivate('avtcgirunsupcollect','push', modelH,...
                                'sldv', ME.message,ME.identifier);
                status = false;
                return;
            end
            hasParameters = false;
            % We need to tune all the parameters that are discovered by 'refreshModelParams'
            this.useAllParamsInAnalysis();
            parametersList = this.getFlatListOfParams();
            for idx = 1:length(parametersList)
                [~, topParamName] = Sldv.ParameterTuning.Manager.findIfStructParam(parametersList(idx).value);
                hasParameters = this.isParamUsedInAnalysis(topParamName);
                if hasParameters
                    break;
                end
            end
            try
                if hasParameters
                    parameterStruct = this.getParameterStruct();
                end
                status = true;
            catch ME %#ok<NASGU> 
                status = false;
            end
        end
        
        function paramStruct = getParameterStruct(this)
            paramStruct = [];
            if ~isempty(this.ParamStruct)
                flatParamList = this.getFlatListOfParams();
                for idx = 1:length(flatParamList)
                    currentParam = flatParamList(idx);
                    [isStructParam, topParamName] = Sldv.ParameterTuning.Manager.findIfStructParam(currentParam.value);
                    if ~this.isParamUsedInAnalysis(topParamName)
                        % If a parameter is not marked for analysis, we
                        % can skip the parameter and go to the next one.
                        continue;
                        end
                    if ~isStructParam
                        % Tune a parameter only if SLDV heuristic says that
                        % it should be tuned
                        % For all parameters that are not struct/Bus type

                        if isempty(currentParam.value.Constraint)
                            paramConstraint = [];
                        else
                            paramConstraint = eval(currentParam.value.Constraint);
                        end
                        % This assignment statement deals with
                        % parameters when flatParamList(idx).name are not Bus/Struct type
                        % Example: % flatParamStruct.('myParam') = [0 1]
                        % When we do struct parameter assignment this
                        % way, we end up throwing an exception
                        % Example: flatParamStruct.('myStruct.a')=[0 1]
                        %          errors out
                        paramStruct.(currentParam.name) = paramConstraint;
                    else
                        % Ideally, we reach this place when the assignment
                        % statements errors out. This case occurs for
                        if isempty(flatParamList(idx).value.Constraint)
                            % When the parameter constraint is an empty char array
                            paramConstraint = '[]';
                        else
                            paramConstraint = flatParamList(idx).value.Constraint;
                    end
                        % Note paramConstraint is stored as a string.
                        % So, eval on this doesn't cause any further
                        % loss in precision.
                        assignStmt = sprintf('paramStruct.%s = %s;', currentParam.name,...
                            paramConstraint);
                        eval(assignStmt);
                    end
                end
            end
        end
    end
    
    methods (Access = 'private')
        
        function clearConfigParams(this)
            if ~isempty(this.ParamStruct)
                pnames = fieldnames(this.ParamStruct);
                for idx = 1: length(pnames)
                    p = this.ParamStruct.(pnames{idx});
                    this.removeFromConfig(p);
                end
            end
        end
        
        
        function params = getStructForSave(this)
            params = [];
            if ~isempty(this.ParamStruct)
                flds = fieldnames(this.ParamStruct);
                for i = 1:length(flds)
                    p = this.ParamStruct.(flds{i});
                    % save all parameter constraints in config.
                    % even if its not in model.
                    if this.inConfig(p)                        
                       params.(p.Name) = p.Constraint;                        
                    end
                end
            end
        end
        
        function isFieldOfStruct = isfieldOfStructParam(this, parameterStruct, paramName)
            % Returns true if 'paramName' is a part of parameterStruct
            
            % paramName is the name of the parameter from the parameter
            % table (in the first call of all the recursive calls)
            % For example: 
                          % For Struct/Bus Params, paramName : 
                                % myStructParam.subBus1.subBus11.leaf111
                          % For Non Struct Entries, paramName:
                                % myScalarParameter
            % ParameterStruct could be of the following form
            %                                      subBus11   leaf111
            %                          subBus1 [============[--------
            %                       [==========[  subBus12
            %    ParameterStruct  [  subBus2 [------------
            %    >==================[----------
            %                       [  subBus3
            %                       [----------
            %                       [ myScalarParameter
            
            % This function recursively iterates through the input 'parameterStruct' 
            % and returns true if the parameter name provided in the input argument 
            % 'parameterName' is a part of the struct
            paramElems = strsplit(paramName, '.');
            currentTopParamName = paramElems{1};
            if length(paramElems) == 1 && isfield(parameterStruct, currentTopParamName)
                isFieldOfStruct = isfield(parameterStruct, currentTopParamName); % Scalar Parameter or leaf of a struct parameter
            elseif isfield(parameterStruct, currentTopParamName)
                isFieldOfStruct = isfieldOfStructParam(this, parameterStruct.(currentTopParamName), ...
                                                                                            strjoin(paramElems(2:end),'.'));
            else
                isFieldOfStruct = false;
            end
        end
        
        function mapToSet = mergeAndAssignParam(this, paramValue, pName, mapToSet)
            % Assigns the paramValue to 'ParamStruct' and set the source of the parameter 'IsInModel'
            % or 'IsInConfig' to 1
            if slavteng('feature','BusParameterTuning') && isstruct(paramValue)
                % For each leaf of the struct param, if we already have an
                % existing constraint, get that existing constraint and merge
                % it with the new constraint
                currentLeafs = this.createFlatParamList(paramValue);
                for jdx = 1:length(currentLeafs)
                    p = this.getSameParam(currentLeafs(jdx));
                    if isempty(p)
                        eval(['this.ParamStruct.' currentLeafs(jdx).Name '= currentLeafs(jdx);']); %#ok<EVLDOT> 
                    else
                        p.merge(currentLeafs(jdx));
                    end
                    mapToSet(currentLeafs(jdx).Name) = 1;
                end
            else
                p = this.getSameParam(paramValue);
                if isempty(p)
                    this.ParamStruct.(pName) = paramValue;
                else
                    p.merge(paramValue);
                end
                mapToSet(pName) = 1;
            end
        end
        
        function params = getParamListForSave(this)
            % Returns a flat parameter structure with a 'name' and
            % 'constraint' fields.
            params = [];
            if ~isempty(this.ParamStruct)
                flds = fieldnames(this.ParamStruct);
                for idx = 1:length(flds)
                    currentValue = this.ParamStruct.(flds{idx});
                    leafsInParam = Sldv.ParameterTuning.Manager.createFlatParamList(currentValue);
                    for jdx = 1:length(leafsInParam)
                        if isKey(this.IsInConfig, leafsInParam(jdx).Name) && this.IsInConfig(leafsInParam(jdx).Name)
                            if isempty(params)
                                params = struct('name', leafsInParam(jdx).Name, 'constraint', leafsInParam(jdx).Constraint);
                            else
                                params(end+1) = struct('name', leafsInParam(jdx).Name, 'constraint', leafsInParam(jdx).Constraint); %#ok<AGROW>
                            end
                        end
                    end
                end
            end
        end
        
        function mapToSet = setAllLeavesOfStructOnAMap(this, param, value, mapToSet)
            % Can be used to set all the names of leafs of a struct parameter
            % 'param' with 'value' (0/1)
            % mapToSet can be IsInModel, IsInConfig or UseInAnalysis
            if isstruct(param)
                leafFields = fieldnames(param);
                for idx = 1:length(leafFields)
                    mapToSet = this.setAllLeavesOfStructOnAMap(param.(leafFields{idx}), value, mapToSet);
                end
            else
                % The leaf should be an object of the class
                % Sldv.ParameterTuning.Param
                mapToSet(param.Name) = value;
            end
        end
        
        function clearModelParams(this)
            if ~isempty(this.ParamStruct)
                pnames = fieldnames(this.ParamStruct);
                for idx = 1:length(pnames)
                    p = this.ParamStruct.(pnames{idx});
                    if this.inModel(p)
                        if slavteng('feature','BusParameterTuning')
                            this.IsInModel = setAllLeavesOfStructOnAMap(this, p, 0, this.IsInModel);
                            if ~this.inConfig(p)
                                this.ParamStruct = rmfield(this.ParamStruct,pnames{idx});
                            end
                        else
                            this.IsInModel(p.Name) = 0;
                            if ~this.inConfig(p)
                                % remove the parameter if it is not in the
                                % model or in the config
                                this.remove(this.ParamStruct.(pnames{idx}));
                            end
                        end
                    end
                end
            end
        end
        
        function remove(this, p)
            this.ParamStruct = rmfield(this.ParamStruct, p.Name);
        end
        
        % Return a compatible parameter from the current list if it exists.
        function p = getSameParam(this,param)
            % param should always be an object of Sldv.ParamTuning.Param
            p = [];
            if ~isempty(this.ParamStruct)
                pnames = fields(this.ParamStruct);
                for idx = 1:length(pnames)
                    if slavteng('feature','BusParameterTuning') && isstruct(this.ParamStruct.(pnames{idx}))
                        % If the current Parameter we are scanning is a
                        % struct/Bus Parameter, we need to look inside the
                        % struct to get a compatible parameter
                        currentParamLeafs = Sldv.ParameterTuning.Manager.createFlatParamList(this.ParamStruct.(pnames{idx}));
                        for jdx = 1:length(currentParamLeafs)
                            if strcmp(currentParamLeafs(jdx).Name,param.Name) && currentParamLeafs(jdx).isCompatible(param)
                                p = currentParamLeafs(jdx);
                                return;
                            end
                        end
                    else
                        if this.ParamStruct.(pnames{idx}).isCompatible(param)
                            p = this.ParamStruct.(pnames{idx});
                        end
                    end
                end
            end
        end
        
        %Is the parameter in model?
        function bool = inModel(this, p)
            if slavteng('feature','BusParameterTuning') && isstruct(p)
                bool = false;
                currentParamLeafs = Sldv.ParameterTuning.Manager.createFlatParamList(p);
                for idx = 1:length(currentParamLeafs)
                    % If any of the leafs are present in the model, then
                    % the top level parameter is present in the model
                    paramName = currentParamLeafs.Name;
                    if isKey(this.IsInModel, paramName) && this.IsInModel(paramName)
                        % If any of the leafs are In Model, the
                        % topLevel parameter is considered to be in
                        % Model
                        bool = true;
                        break;
                    end
                end
            else
                bool = isKey(this.IsInModel, p.Name);
                if bool
                    bool = this.IsInModel(p.Name);
                end
            end
        end
        
        % Is the parameter in config?
        function bool = inConfig(this, p)
            if slavteng('feature','BusParameterTuning') && isstruct(p)
                bool = false;
                currentParamLeafs = Sldv.ParameterTuning.Manager.createFlatParamList(p);
                for idx = 1:length(currentParamLeafs)                  
                    paramName = currentParamLeafs.Name;
                    if isKey(this.IsInConfig, paramName) && this.IsInConfig(paramName)
                        % If any of the leafs are present in the model, then
                        % the top level parameter is present in the model
                        bool = true;
                        break;
                    end
                end
            else
                mapToCheck = this.IsInConfig;
                paramName = p.Name;
                bool = isKey(mapToCheck, paramName);
                if bool
                    bool = this.IsInConfig(p.Name);
                end
            end
        end
        
        % If the constraint is not set for a parameter, try to generate one
        function addedNewConstraints = generateConstraintsForParams(this)
            addedNewConstraints = false;
            params = this.ParamStruct;
            if isempty(params)
                return;
            end
            pnames = fields(params);
            for idx = 1:length(pnames)
                if slavteng('feature','BusParameterTuning')
                   paramValue = params.(pnames{idx});
                   plist = Sldv.ParameterTuning.Manager.createFlatParamList(paramValue);
                   for jdx = 1:length(plist)
                       currentLeafParam = plist(jdx);
                       if ~currentLeafParam.hasConstraint && this.inModel(currentLeafParam)
                           currentLeafParam.Constraint = currentLeafParam.autogenerateConstraint;
                           if currentLeafParam.hasConstraint
                               addedNewConstraints = true;
                               this.selectParamForAnalysis(currentLeafParam.Name, 1);
                               this.IsInConfig(currentLeafParam.Name) = 1;
                           end
                       end
                   end
                else
                    if ~(params.(pnames{idx}).hasConstraint) && this.inModel(params.(pnames{idx}))
                        params.(pnames{idx}).Constraint = params.(pnames{idx}).autogenerateConstraint;
                        if params.(pnames{idx}).hasConstraint
                            % If any constraint is generated this flag is
                            % turned on.
                            addedNewConstraints = true;
                            this.selectParamForAnalysis(pnames{idx}, 1);
                            this.IsInConfig(pnames{idx}) = 1;
                        end
                    end
                end
            end
        end

        % If constraint for a parameter is empty, it will not be present in
        % the config already. When such a constraint is checked/unchecked,
        % its has to be added/removed from config.
        function updateParamInConfig(this, tf, paramname)
            if tf               
                this.updateParam(paramname, '[]');
            elseif isfield(this.ParamStruct, paramname)
                this.removeFromConfig(this.ParamStruct.(paramname));
            end
        end 
    end
end

% LocalWords:  attribs func parameterStruct jdx avtcgirrunupcollect








