classdef ExecutionTimePlotController < coder.profile.ExecutionTimePlotControllerBase
    %EXECUTIONTIMEPLOTCONTROLLER co-ordinates View-ViewModel pairs
    %corresponding to execution time plots.

    %   Copyright 2014-2017 The MathWorks, Inc.

    properties (GetAccess = public, SetAccess = private)
        ViewModels = coder.profile.ExecutionTimelineViewModel.empty();
    end

    properties (Access = private)
        TimerData;
        ExecutionBlocks = coder.profile.ExecutionBlock.empty();
        Tasks = coder.profile.ExecutionTimelineTask.empty();

        NewViewModel;
    end

    methods
        % Constructor, creates instance of ExecutionTimePlotController
        % using execution profile data 'timeData'.
        %
        % 'timeData' is coder.profile.ExecTimeData or some wrapper.
        % 'newViewModel' is a delegate the creates an
        % ExecutionTimelineViewModelBase object.
        function this = ExecutionTimePlotController(timerData, newViewModel)
            this.NewViewModel = newViewModel;
            initialise(this, timerData);
        end

        % Creates and adds a new view of the Execution Profile plot.
        function h = addView(this, varargin)

            % parse additional parameters
            p = inputParser;
            addOptional(p, 'SlotRangeInTicks', 0);
            addOptional(p, 'TaskToHighlight', 0);
            addOptional(p, 'TaskEndTimerValueInTicks', 0);
            addOptional(p, 'MaxResizeIncrement', 20, @isnumeric);
            
            p.parse(varargin{:});
            
            newViewModel = createViewModel(this, p.Results.SlotRangeInTicks, p.Results.TaskToHighlight, p.Results.TaskEndTimerValueInTicks, p.Results.MaxResizeIncrement);
            newViewModel.Tasks = this.Tasks;

            this.ViewModels(end+1) = newViewModel;

            h = show(newViewModel);
        end

        function editView(this, viewModel, lOperationType)
                
                tStart = viewModel.XLimit(1);                
                tEnd = viewModel.XLimit(2);
                
                tStart = i_displayUnitsToRawTicks(viewModel, tStart);
                tEnd = i_displayUnitsToRawTicks(viewModel, tEnd);
                
                if isempty(viewModel.PreviousXLimitTicks)                    
                    viewModel.PreviousXLimitTicks = uint64([0 0]);
                end  
                
                [timerStartValues, timerEndValues, ...
                 lCoreNumStartVals, lCoreNumEndVals,...
                 nodeNumbers, lAnalysisWarnings] ...
                    = this.TimerData.getTimerValues;                                             
                                
                if isempty(viewModel.PlottedIndices)
                    viewModel.PlottedIndices = false(size(timerStartValues));                    
                end
                
                [idx, plotRange] = coder.profile.private.newPointsToPlot...
                    (timerStartValues, timerEndValues, ...
                    viewModel.PlottedIndices, viewModel.PreviousXLimitTicks, ...
                    [tStart tEnd], viewModel.MaxNewPoints, ...
                    'OperationType', lOperationType);
                 
                timerStartValues = timerStartValues(idx);
                timerEndValues = timerEndValues(idx);
                lCoreNumStartVals = lCoreNumStartVals(idx);
                lCoreNumEndVals = lCoreNumEndVals(idx);
                nodeNumbers = nodeNumbers(idx);
                
                if ~isempty(lAnalysisWarnings) 
                    % only reported for real-time case
                    lAnalysisWarnings = lAnalysisWarnings(idx);
                else
                    lAnalysisWarnings = [];
                end               
                
                % Units transformation 
                tRange = i_rawTicksToDisplayUnits(viewModel, plotRange);
                timerStartValues = i_rawTicksToDisplayUnits(viewModel, timerStartValues);
                timerEndValues = i_rawTicksToDisplayUnits(viewModel, timerEndValues);
                
                viewModel.XLimit = tRange; 
                viewModel.PreviousXLimitTicks = plotRange;
                
                viewModel.PlottedIndices = viewModel.PlottedIndices | idx;
                
                showCoreNumber = this.TimerData.getIsMultiCore;
                
                if isempty(lAnalysisWarnings)
                    % At present, warnings are reported only in the real-time case
                    lAnalysisWarnings = false(size(nodeNumbers));
                end
                
                % Update blocks to the ones that need to be plotted
                viewModel.ExecutionBlocks = coder.profile.ExecutionBlock.empty();
                                    
                % Clear plotted Invocations
                for i = 1:length(this.ExecutionBlocks)
                    this.ExecutionBlocks(i).Invocations = coder.profile.ExecutionInvocation.empty();
                end
                
                for i=1:length(timerStartValues)
                    nodeNumber = nodeNumbers(i)-1;
                    block = this.ExecutionBlocks(nodeNumber==[this.ExecutionBlocks.Id]);
                    
                    % We have a new invocation to display
                    invo = coder.profile.ExecutionInvocation();                    
                    invo.ShowCoreNumber = showCoreNumber;
                    
                    block.SectionIndex = find(nodeNumber==viewModel.VisibleSections);
                    invo.TimeStart = timerStartValues(i);
                    invo.TimeEnd = timerEndValues(i);
                    lCoreNumStartValue = lCoreNumStartVals(i);
                    lCoreNumEndValue = lCoreNumEndVals(i);
                    if lAnalysisWarnings(i)
                        invo.WarningIndicator ='! ';
                    else
                        invo.WarningIndicator ='';
                    end
                    
                    if (block.IsPreemption)
                        invo.Core = lCoreNumEndValue;
                        if invo.Core == intmax('uint32')
                            % If the recorded data finishes while a task is preempted we do not
                            % know which core it restarted on
                            invo.ShowCoreNumber = false;
                        end
                    elseif block.IsSuspended
                        invo.Core = lCoreNumEndValue;
                    else
                        invo.Core = lCoreNumStartValue;
                    end
                    
                    % Add invocation to execution block to be displayed
                    block.Invocations(end + 1) = invo;
                    
                    if isempty(viewModel.ExecutionBlocks(nodeNumber==[viewModel.ExecutionBlocks.Id]))
                        viewModel.ExecutionBlocks(end+1) = block;
                    end
                end


            % The execution blocks must be sorted by Id so that parents are
            % drawn before their children. This ensures that blanked out
            % sections for a parent are drawn after the parent section
            % itself.
            [blockIds{1:length(viewModel.ExecutionBlocks)}] = ...
                viewModel.ExecutionBlocks(:).Id;
            [~, sortIdx] = sort(cell2mat(blockIds));
            viewModel.ExecutionBlocks = viewModel.ExecutionBlocks(sortIdx);
        end
    end

    methods (Access = private)

        function initialise(this, timerData)

            this.TimerData = timerData;

            numNodes = timerData.getNumNodes-1; % Exclude root node

            isTaskSuspendSection = timerData.getIsTaskSuspendSection;
            for ii3=1:numNodes
                tmpSection = timerData.getSectionProfile(ii3);
                block = coder.profile.ExecutionBlock();
                block.Id = ii3;
                block.Name = tmpSection.Name;
                block.IsSuspended = isTaskSuspendSection(ii3);
                [block.CallDepth, block.TaskIndex] = tmpSection.getCallDepth;

                newTask = isempty(this.Tasks);

                if (~newTask)
                    taskIdx = find(block.TaskIndex==[this.Tasks.Index]);
                    newTask = isempty(taskIdx);
                end

                if newTask
                    task = coder.profile.ExecutionTimelineTask();
                    task.Index = block.TaskIndex;
                    task.MaxCallDepth = 1;
                    task.Name = tmpSection.Name;
                    this.Tasks(end+1) = task;
                    taskIdx = length(this.Tasks);
                else
                    task = this.Tasks(taskIdx);
                end
                block.IsPreemption = tmpSection.isPreemptionSection;
                if ~block.IsPreemption
                    task.MaxCallDepth = max(block.CallDepth, task.MaxCallDepth);
                end

                this.ExecutionBlocks(end + 1) = block;
            end
        end

        % Creates a new ExecutionTimelineViewModel using the data stored in
        % TimerData. If no TimerData exists this function returns an empty
        % view model.
        function viewModel = createViewModel(this, slotRangeTicks, taskToHighlight, taskEndTimerValueInTicks, maxResizeIncrement)

            viewModel = this.NewViewModel(this);

            if nargin < 2
                slotRangeTicks = 0;
            end

            % If we have no timer data return the empty view model
            if (isempty(this.ExecutionBlocks))
                return;
            end

            blockIds = [this.ExecutionBlocks.Id];
            viewModel.VisibleSections = blockIds(~[this.ExecutionBlocks.IsPreemption]);
            viewModel.TicksPerSecond = this.TimerData.getTimerTicksPerSecond;
            viewModel.SimulationStartTime = getSimulationStartTime(this.TimerData);
            viewModel.MaxNewPoints = maxResizeIncrement;

            % Calculate the initial time range to plot
            [timerMinTicks, timerMaxTicks, numPoints] = this.TimerData.getTimerMinMax;
            viewModel.TicksMin = timerMinTicks;
            viewModel.TicksMax = timerMaxTicks;
            
            timerMin = i_rawTicksToDisplayUnits(viewModel, timerMinTicks);
            timerMax = i_rawTicksToDisplayUnits(viewModel, timerMaxTicks);

            if (slotRangeTicks == 0)
                pointsToDisplay=15*length(this.Tasks);
                xRange = min(timerMax-timerMin, ...
                    (timerMax-timerMin)*pointsToDisplay/numPoints);
                viewModel.XLimit = [timerMin, timerMin+xRange];
            else
                % If a slot range was specified, set the x-axis limits to
                % include the whole slot with a delta at either end
                lSlotRange = i_rawTicksToDisplayUnits(viewModel, slotRangeTicks);
                rangeDelta = 0.1 * diff(lSlotRange);
                viewModel.XLimit = lSlotRange + [-rangeDelta rangeDelta];
                viewModel.VerticalMarkers = lSlotRange;

                viewModel.TaskToHighlightIndex = [this.Tasks.Index] == taskToHighlight;
                viewModel.TaskEndXPosition = i_rawTicksToDisplayUnits...
                    (viewModel, taskEndTimerValueInTicks);
            end

            viewModel.XLimit = i_axis_pretty(viewModel.XLimit);

            viewModel.ExecutionTimeRange = double([timerMin timerMax]); % No uint64 for axis limits
            viewModel.ExecutionTimeRange = i_axis_pretty(viewModel.ExecutionTimeRange);

            % Each task occupies callDepth
            viewModel.YLimit = [0.9 (1.1 + sum([this.Tasks.MaxCallDepth] + 0.5))];
            h = figure('Visible', 'off');
            plot([0 1], viewModel.YLimit);
            viewModel.YLimit = get(gca, 'YLim');
            close(h)

            function xLim = i_axis_pretty(xLim)
                hTmp = figure('Visible','off');
                plot(xLim, [0 0]);
                xLim = get(gca, 'XLim');
                delete(hTmp);
            end


        end
    end


end

function tRaw = i_displayUnitsToRawTicks(viewModel, tDisplay)
% Convert from display units to raw timer value in ticks. The display units
% will be in seconds if a timer calibration is available or in ticks
% otherwise.

lTicksPerSecond = viewModel.TicksPerSecond;

if ~isempty(lTicksPerSecond)
    % Offset that matches x-axis start time with simulation start time
    simTimeOffset = viewModel.SimulationStartTime;
    
    % Raw timer start value in ticks
    initialTicks = double(viewModel.TicksMin);
    
    % Convert seconds to ticks
    tRaw = uint64((tDisplay - simTimeOffset) * lTicksPerSecond + initialTicks);
else
    % The x-axis display unit is already ticks: only need to ensure
    % that values have the same type as the timer values
    tRaw = uint64(floor(tDisplay));
end
end

function tDisplay = i_rawTicksToDisplayUnits(viewModel, tRaw)
% Convert from raw timer value in ticks to display units. The display units
% will be in seconds if a timer calibration is available or in ticks
% otherwise.


lTicksPerSecond = viewModel.TicksPerSecond;
% If timer calibration is available then x-axis units must be in seconds
if ~isempty(lTicksPerSecond)
    % Apply an offset so that the x-axis start time matches the
    % simulation start time
    simTimeOffset = viewModel.SimulationStartTime;
    
    % The raw timer value (in ticks) may have a non-zero initial value
    initialTicks = double(viewModel.TicksMin);
    
    tDisplay = (double(tRaw) - initialTicks) / double(lTicksPerSecond) + simTimeOffset;
else
    tDisplay = double(tRaw);
end
end


