classdef ModelAnnotationData < handle & matlab.mixin.Heterogeneous
% Holds data required for annotating a model
    
%   Copyright 2010-2020 The MathWorks, Inc.
    
    properties (SetAccess=private, GetAccess = public)
        Model;
        SIDsWithOwnProfilingData;
        SectionNumbersForSID;
        ExecutionTime;
        % unit of measurement (e.g.: ticks, seconds)
        Units;
        % System International unit: this field is not empty
        % when Units is in second and the Scale Factor parameter
        % is 1e-3, 1e-6 or 1e-9.
        % Allowed values: ms, us, ns
        SIUnits; 
        DispInSeconds;
        TimeFormat;
        TimeScaleFactor;
        TaskSIDsInThisModel;
    end        
    
    methods (Access=public, Static=true)
        
        function allModels = getModelsToAnnotate(executionProfile)
            allModels = [];
            numModules = executionProfile.getModuleNumber();
            for idModule = 1:numModules
                execProf = executionProfile.getModule(idModule);
                
                [~, allProfiledBlocks] = coder.profile.ModelAnnotationData.getModelDataForProfiledSections(execProf);
                
                % Blocks containing a profiled block and not themselves profiled
                allIntermediateBlocks = execProf.IntermediateSIDs;
                
                allBlocks = [allIntermediateBlocks(:); allProfiledBlocks(:)];
                
                % Extract model names from block specifications
                models = coder.profile.private.getTopModelFromBlock(allBlocks);
                models = unique(models(~strcmp(models, '')));
                allModels = unique([allModels(:); models(:)]);
            end
        end
    end
    
    methods (Access=private, Static=true)
        
        function [models, blocks, sectionNumbers, taskSIDs] = getModelDataForProfiledSections...
                (executionProfile)
            % Return model name and SIDs for each profiled section
            
            sections = executionProfile.Sections;
            blocks = {};
            sectionNumbers = [];
            lSimulationComponentSID = executionProfile.SimulationComponentSID;
            taskSIDs = {};
            
            for i=1:length(sections)
                traceInfo = sections(i).getTraceInfo;
                if(traceInfo.isTask)
                    % Always the task level section with the simulation component
                    newBlocks = {lSimulationComponentSID};
                    
                    % Identify the SID from the original model associated with this task
                    % section
                    lCallSiteSID = getCallSiteSids(traceInfo);
                    if ~isempty(lCallSiteSID)
                        assert(length(lCallSiteSID)==1, 'Expected a single call site');
                        lCallSiteSID = lCallSiteSID{1};
                        
                        if ~strcmp(lSimulationComponentSID, lCallSiteSID)
                            if ~strcmp(lCallSiteSID, strtok(lCallSiteSID, ':'))
                                % SIL/PIL block case: highlight subsystem in
                                % the original model
                                newBlocks{end+1} = lCallSiteSID; %#ok<AGROW>
                            end
                        end
                    end
                    N = length(newBlocks);
                    taskSIDs(end+1:end+N) = newBlocks;
                else
                    newBlocks = getCallSiteSids(traceInfo);
                end
                blocks = [blocks; newBlocks(:)]; %#ok
                sectionNumbers = [sectionNumbers; repmat(i, size(newBlocks(:)))]; %#ok
            end
            % Extract model names from block specifications
            models = coder.profile.private.getTopModelFromBlock(blocks);
            taskSIDs = unique(taskSIDs);
        end
    end
    
    methods (Access=private)
        function intermediateBlockHandles = getIntermediateBlockHandles(this)
            intermediateSIDs = this.ExecutionTime.IntermediateSIDs;
            % The above returns intermediate SIDs for all models in the hierarchy: select
            % only the SIDs applying to the current model
            thisModelIdx = strcmp(strtok(intermediateSIDs, ':'), this.Model);
            intermediateSIDs = intermediateSIDs(thisModelIdx);
            
            intermediateBlockHandles = zeros(size(intermediateSIDs));
            keepIdx = false(size(intermediateSIDs));
            for i=1:length(intermediateSIDs)                
                sidTemp = intermediateSIDs{i};
                try
                    intermediateBlockHandles(i) = get_param(sidTemp, 'Handle');
                    keepIdx(i) = true;
                catch exc
                    if strcmpi(exc.identifier, 'Simulink:Commands:InvSimulinkObjectName')
                        % The original block no longer exists
                    else
                        rethrow(exc);
                    end
                end
            end
            intermediateBlockHandles = intermediateBlockHandles(keepIdx);
        end
        
        function [handles, modelHasDeletedBlocks] = getValidItemsWithProfilingData(this)
            modelHasDeletedBlocks = false;
            sids = this.SIDsWithOwnProfilingData;
            handles = zeros(size(sids));
            keepIdx = false(size(sids));
            for i=1:length(sids)                
                sid = sids{i};
                try
                    [h, ~, sidSpace] = Simulink.ID.getHandle(sid);
                    if isa(h,'Stateflow.Object')
                        handles(i) = sidSpace;
                    else
                        handles(i) = h;
                    end
                    keepIdx(i) = true;
                catch exc
                    if strcmpi(exc.identifier, 'Simulink:utility:objectDestroyed')
                        % The original block no longer exists
                        modelHasDeletedBlocks = true;
                    else
                        rethrow(exc);
                    end
                end
            end
            
            % Filter out deleted blocks
            handles = handles(keepIdx);
        end
    end
    

    methods (Access=public)
        
        function [unit, SIUnit] = getUnits(this)
            unit = this.Units;
            SIUnit = this.SIUnits;
        end
        
        function sids = SIDsWithProfiling(this)
            sids = this.SIDsWithOwnProfilingData;
        end
                
        function hTaskBlockAncestors = getTaskBlockAncestors(this)
           
            hTaskBlockAncestors = [];
            
            lTaskSIDs = this.TaskSIDsInThisModel;
            
            if isempty(lTaskSIDs)
                return;
            end
            
            for i=1:length(lTaskSIDs)
                lTaskSID = lTaskSIDs{i};
                try
                    hBlock = get_param(lTaskSID, 'handle');
                catch e
                    if strcmp(e.identifier, 'Simulink:Commands:InvSimulinkObjectName')
                        % block may have been deleted (e.g. replace with
                        % SIL/PIL block workflow)
                        continue
                    else
                        rethrow(e);
                    end
                end
                
                assert(strcmp(bdroot(lTaskSID), this.Model), 'SID must be within this model');
                
                if ~strcmp(lTaskSID, this.Model)
                    parent = get_param(hBlock, 'parent');
                    while ~strcmp(parent, this.Model)
                        hParent = get_param(parent, 'handle');
                        hTaskBlockAncestors(end+1) = hParent; %#ok<AGROW>
                        parent = get_param(hParent, 'parent');
                    end
                end
            end
            
        end
        
        function [itemHandles, modelHasDeletedBlocks] = getItemsToHighlight(this)
            % Get valid handles for this model
            [hEntitiesWithProfiling, modelHasDeletedBlocks] = ...
                getValidItemsWithProfilingData(this);
            % Any model block or sub-system block containing a profiled block,
            % (but not itself profiled) must be highlighted
            intermediateBlockHandles = getIntermediateBlockHandles(this);
            % The task level block may be several levels down in the model hierarchy: we
            % highlight its ancestor blocks (up to but not including bdroot)
            hTaskBlockAncestors = getTaskBlockAncestors(this);            
            itemHandles = [hEntitiesWithProfiling(:); 
                           intermediateBlockHandles(:);
                           hTaskBlockAncestors(:)];
        end
    
        function this = ModelAnnotationData(model, executionProfile, varargin)
            % Extract the data related to profiling for this model only
            [this.Units, this.SIUnits, this.DispInSeconds, this.TimeFormat, this.TimeScaleFactor] = ...
                coder.profile.processReportArgs...
                    (executionProfile.TimerTicksPerSecond, executionProfile.TimerHWCounterUnit, varargin{:});                
                
            this.Model = model;
            this.ExecutionTime = executionProfile;
            [models, blocks, sectionNumbers, taskSIDs] = ...
                coder.profile.ModelAnnotationData...
                .getModelDataForProfiledSections(executionProfile);
            thisModelIdx = strcmp(models, model);
            % Find SIDs for all entities, within this model, having their own profiling data
            sids = blocks(thisModelIdx);
            sectionNumbersThisModel = sectionNumbers(thisModelIdx);
            [uniqueSIDs, ~, ic] = unique(sids);
            % Each SID may have multiple profiled sections associated with it
            lSectionNumbersForSID = cell(size(uniqueSIDs));
            
            for i=1:length(uniqueSIDs)
                lSectionNumbersForSID{i} = sectionNumbersThisModel((ic==i));
            end
            
            this.SIDsWithOwnProfilingData = uniqueSIDs;
            this.SectionNumbersForSID = lSectionNumbersForSID;
            
            % Identify any task SIDs in this model
            tempModels = coder.profile.private.getTopModelFromBlock(taskSIDs);
            this.TaskSIDsInThisModel = taskSIDs(strcmp(tempModels, model));
        end
    end
end
