classdef ProfileHilighter < coder.profile.AnnotateFeature
%

%   Copyright 2020-2022 The MathWorks, Inc.
    
    properties(Access = private)
        ModelStyle;
        OverlayStyle;
        BlockStyle;
        ModelSelectorName;
        OverlaySelectorName;
        BlockSelectionNames;
        Styler;
        IsIntegratedView;
        ModelsWithOverlay = {};
    end
    
    properties(Constant=true, Access=private)
        WrapperBlockHtml = sprintf('%s', DAStudio.message...
            ('CoderProfile:ExecutionTimeInformer:etiHierarchyText'));
    end
    
    methods
        function style = get.OverlayStyle(this)
            if isempty(this.OverlayStyle)
                % Create overlay style
                style = diagram.style.Style;
                diagram.style.Style.registerProperty('GreyEverything','bool');
                style.set('GreyEverything', true);
                this.OverlayStyle = style;
            end
            style = this.OverlayStyle;
        end

        function style = get.ModelStyle(this)
            if isempty(this.ModelStyle)
                % Create model style
                style = diagram.style.Style;
                style.set('FillColor', [0.86, 0.86, 1]);
                style.set('FillStyle', 'Solid');
                style.set('StrokeColor', [0.0 0.0 0.0 1.0]);
                this.ModelStyle = style;
            end
            style = this.ModelStyle;
        end
        
        function style = get.BlockStyle(this)
            if isempty(this.BlockStyle)
                % Create block style
                style = diagram.style.Style;
                style.set('FillColor', [0.69, 0.69, 1]);
                style.set('FillStyle', 'Solid');
                style.set('StrokeColor', [0.0 0.0 0.0 1.0]);
                this.BlockStyle = style;
            end
            style = this.BlockStyle;
        end

        function delete(this)
            this.destroyImpl();
        end
    end
    
    methods (Access=public)
        function obj = ProfileHilighter(executionProfile, models, integratedView)
            obj = obj@coder.profile.AnnotateFeature(executionProfile, models);
            
            obj.IsIntegratedView = integratedView;
            modelName = executionProfile.SimulationComponent;
            stylerName = sprintf('MathWorks.ProfilingHighlight_%s', modelName);
            
            diagram.style.createStyler(stylerName);
            obj.Styler = diagram.style.getStyler(stylerName);
            
            % Model styling
            lModelSelectorName = sprintf('ModelProfiling.ModelStyling_%s', modelName);
            lModelSelector = diagram.style.ClassSelector(lModelSelectorName);
            obj.Styler.addRule(obj.ModelStyle, lModelSelector);
            obj.ModelSelectorName = lModelSelectorName;

            % Overlay styling
            if obj.IsIntegratedView
                lOverlaySelectorName = sprintf('ModelProfiling.OverlayStyling_%s', modelName);
                lOverlaySelector = diagram.style.ClassSelector(lOverlaySelectorName);
                obj.Styler.addRule(obj.OverlayStyle, lOverlaySelector);
                topSelector = diagram.style.DescendantSelector({lOverlaySelectorName}, {}, {}, {'simulink'});
                obj.Styler.addRule(obj.OverlayStyle, topSelector);
                obj.OverlaySelectorName = lOverlaySelectorName;
            end
        end
    end
    
    methods (Access=protected)
        function refreshItemImpl(this, itemHandle, lModelAnnotationData, varargin)
            % Apply styling to the model and populate the informer with corresponding
            % annotations

            hModel = get_param(lModelAnnotationData.Model, 'handle');
            if this.IsIntegratedView && ...
                    ~any(strcmp(lModelAnnotationData.Model, this.ModelsWithOverlay))
                this.ModelsWithOverlay{end+1} = lModelAnnotationData.Model;
                this.applyHighlighting(hModel, this.OverlaySelectorName);
            end
            if (itemHandle == hModel)
                this.applyHighlighting(itemHandle, this.ModelSelectorName);
            else
                if this.IsIntegratedView
                    html = this.getBlockTooltip(lModelAnnotationData, itemHandle);
                    this.BlockStyle.set('Tooltip', html);
                end
                lBlockSelectorName = sprintf('BlockProfiling.h%s', num2str(itemHandle));
                lBlockSelector = diagram.style.ClassSelector(lBlockSelectorName);
                this.Styler.addRule(this.BlockStyle, lBlockSelector);
                this.applyHighlighting(itemHandle, lBlockSelectorName);
            end
        end

        function destroyImpl(this)
            if ~isempty(this.Styler) && this.Styler.isvalid
                this.ModelsWithOverlay = {};
                % Remove all highlighting belonging to this instance
                this.Styler.destroy();
            end
        end
    end
    
    methods(Access = private)
        function applyHighlighting(this, itemsToStyle, itemStyling)
            this.Styler.applyClass(num2cell(itemsToStyle), itemStyling);
        end
        
        function html = getBlockTooltip(this, lModelAnnotationData, itemHandle)
            sid = Simulink.ID.getSID(itemHandle);
            sdiIdx = strcmp(sid, lModelAnnotationData.SIDsWithProfiling());
            if any(sdiIdx)
                sectionsForSID = lModelAnnotationData.ExecutionTime.Sections(lModelAnnotationData.SectionNumbersForSID{find(sdiIdx, 1)});
                numSections = length(sectionsForSID);
                tps = lModelAnnotationData.ExecutionTime.TimerTicksPerSecond;
                isTime = ~isempty(tps) && tps > 0;
                if isTime
                    unit = DAStudio.message('CoderProfile:ExecutionTime:ReportUnitsNanoSeconds');
                else
                    unit = DAStudio.message('CoderProfile:ExecutionTime:ReportUnitsTimerTicks');
                end
                html = '<table border="1" cellspacing="0" cellpadding="3">';
                html = sprintf('%s<tr>', html);
                html = sprintf('%s<th style="text-align:left">Function name</th>', html);
                html = sprintf('%s<th style="text-align:left">Average execution (%s)</th>', html, unit);
                html = sprintf('%s<th style="text-align:left">Calls</th>', html);
                html = sprintf('%s</tr>', html);
                for i=1:numSections
                    section = sectionsForSID(i);
                    numCalls = section.NumCalls;
                    if isTime
                        scaleFactor = 1e-9;
                        avgExec = double(section.TotalExecutionTimeInTicks) / scaleFactor;
                        avgExec = avgExec / double(tps*numCalls);
                    else
                        avgExec = section.TotalExecutionTimeInTicks / double(numCalls);
                    end
                    ti = section.getTraceInfo;
                    name = ti.getCodeSectionName();
                    html = sprintf('%s<tr>', html);
                    html = sprintf('%s<td>%s</td>', html, name);
                    html = sprintf('%s<td style="text-align:right">%0.0f</td>', html, avgExec);
                    html = sprintf('%s<td style="text-align:right">%d</td>', html, numCalls);
                    html = sprintf('%s</tr>', html);
                end
                msg = DAStudio.message('CoderProfile:ExecutionTime:TooltipSeeProfilingDetails');
                html = sprintf('%s</table><p>%s</p>', html, msg);
            else
                html = this.WrapperBlockHtml;
            end
        end
    end
    
end
