classdef (Hidden = true) ExecutionTimeSet < coder.profile.ExecutionSet
    %EXECUTIONTIMESET collects execution profiling results (instances of
    %coder.profile.ExecutionTime) from different runs of the same model

    %   Copyright 2023 The MathWorks, Inc.

    properties (SetAccess = private, GetAccess = public, Hidden)
        % specify the workflow this object comes from
        AnalysisType = 'standalone';
    end

    properties (Access = private)
        % profiling results
        ResultMap;
        AnalysisResultMap;

        MF0Model;
    end
    
    methods
        function obj = ExecutionTimeSet(component)
            arguments
                component (1,1) string
            end
            % superclass
            obj@coder.profile.ExecutionSet(component);
            % clean values
            obj.ResultMap = containers.Map('KeyType', 'char', 'ValueType', 'any');
            obj.AnalysisResultMap = containers.Map('KeyType', 'char', 'ValueType', 'any');
        end

        function add(this, testname, ex)
            % add an execution profiling result to the set associated to a
            % specific testname

            if ~isa(ex, 'coder.profile.ExecutionTime')
                % check the type of the input to store
                error(message('CoderProfile:Test:SetWrongObject', ...
                    'coder.profile.ExecutionTime'));
            end
            if this.ResultMap.Count > 0
                % check the profiling result is compatible with the ones we
                % already have
                ks = this.ResultMap.keys;
                if ~this.ResultMap(ks{1}).areComparable(ex)
                    error(message('CoderProfile:Test:SetDifferentModel'));
                end
            end
            if ~strcmp(this.AnalysisType, 'standalone') && ~ex.isSILPILSimulation()
                error(message('CoderProfile:Test:SetAnalysisNotXIL'));
            end
            % store it
            index = convertStringsToChars(testname);
            this.ResultMap(index) = ex;
        end
        
        function res = get(obj, testname)
            % return the execution profiling result associated to a
            % specific testname

            if ~strcmp(obj.AnalysisType, 'standalone')
                % don't return the intermediate results of an analysis
                error(message('CoderProfile:Test:SetErrorAnalysis'));
            end
            % return result if present
            res = [];
            if obj.ResultMap.isKey(testname)
                res = obj.ResultMap(testname);
            end
        end
        
        function remove(obj, testname)
            % remove the execution profiling result associated to a
            % specific testname

            if obj.ResultMap.isKey(testname)
                obj.ResultMap.remove(testname);
            end
            if obj.AnalysisResultMap.isKey(testname)
                obj.AnalysisResultMap.remove(testname);
            end
        end
    end

    methods (Access = public, Hidden)
        function tbt = getTaskSummary(this)
            if strcmp(this.AnalysisType, 'hybrid')
                tbt = computeHybridTaskSummary(this);
            else
                tbt = computeStandardTaskSummary(this);
            end
        end

        function testNames = getTestNames(this)
            testNames = this.ResultMap.keys;
        end
        
        function addAnalysisResult(this, k, ex)
            index = convertStringsToChars(k);
            if isa(ex, 'coder.profile.ExecutionTime') || ...
                isa(ex, 'coder.profile.datamodel.ExecutionTime')
                this.AnalysisResultMap(index) = ex;
            else
                assert(false, 'Input not valid');
            end
        end

        function res = getAnalysisResult(obj, s)
            res = [];

            if obj.AnalysisResultMap.isKey(s)
                res = obj.AnalysisResultMap(s);
            end
        end

        function res = getIntermediateResult(obj, s)
            res = [];

            if obj.ResultMap.isKey(s)
                res = obj.ResultMap(s);
            end
        end

        function res = getSummaryInfo(this)
            res.ComponentUnderTest = this.ComponentUnderTest;
            if this.ResultMap.Count > 0
                ks = this.ResultMap.keys;
                ex = this.ResultMap(ks{1});
                res.SimulinkEnvironment = ex.getIsSimulinkWorkflow();
                res.IsAllData = strcmpi(ex.getCodeProfilingSaveOptions(), 'alldata');
            else
                res.IsAllData = false;
                res.SimulinkEnvironment = [];
            end
            res.AnalysisType = this.AnalysisType;
            res.NumTests = this.ResultMap.Count;
            r = this.getTaskSummary();
            res.NumTasks = numel(unique(r.Task));
            res.TimeCreated = this.TimeCreated;

            if this.ResultMap.Count > 0
                keys = this.ResultMap.keys;
                key = keys{1};
                ex = this.ResultMap(key);
                res.ReportFcn = ex.ReportFcn;
                res.TPS = double(ex.TimerTicksPerSecond);
                if numel(ex.Sections) > 0
                    t = ex.Sections(1).getTraceInfo();
                    res.CheckSumStr = t.getStoredChecksum();
                    res.BuildDir = t.getBuildDir();
                    res.ModelName = t.getOriginalModelName();
                else
                    res.CheckSumStr = [];
                    res.BuildDir = [];
                    res.ModelName = [];
                end
            else
                res.ReportFcn = [];
                res.TPS = 0;
                res.CheckSumStr = [];
                res.BuildDir = [];
                res.ModelName = [];
            end
        end
    end

    methods(Access = public, Hidden, Static)
        function res = createHybridSet(cmp, m)
            if matlab.internal.feature("CodeProfileExtendedTimeSet") == 0
                res = [];
                return;
            end
            res = coder.profile.ExecutionTimeSet(cmp);
            res.AnalysisType = 'hybrid';
            res.MF0Model = m;
        end

        function res = createDynamicSet(cmp)
            if matlab.internal.feature("CodeProfileExtendedTimeSet") == 0
                res = [];
                return;
            end
            res = coder.profile.ExecutionTimeSet(cmp);
            res.AnalysisType = 'dynamic';
        end
    end

    methods
        function set.AnalysisType(this, v)
            arguments
                this
                v (1,:) char {mustBeMember(v,{'standalone', 'hybrid', 'dynamic'})} = 'standalone'
            end
            this.AnalysisType = v;
        end
    end

    methods (Access = private)
        function tbt = computeStandardTaskSummary(this)
            testNameCln = cell(1,0);
            taskNameCln = cell(1,0);
            maxWcetCln = zeros(1,0);
            maxExecAtCln = zeros(1,0);
            avgWcetCln = zeros(1,0);
            numCallsCln = zeros(1,0);

            keys = this.ResultMap.keys;
            numKeys = length(keys);
            isAllData = false;
            for i = 1:numKeys
                tstName = keys{i};
                ex = this.ResultMap(tstName);
                if isempty(ex)
                    continue;
                end
                isAllData = strcmpi(ex.getCodeProfilingSaveOptions(), 'alldata');
                sections = ex.Sections;
                for j = 1:length(sections)
                    section = sections(j);
                    t = section.getTraceInfo();
                    sampleTime = t.getSampleTime;
                    if ~t.isTask() || isinf(sampleTime(1))
                        continue;
                    end
                    if isAllData
                        [sortedExecTime, origIdx] = sort(section.ExecutionTimeInTicks, 'descend');
                    else
                        sortedExecTime = section.MaximumExecutionTimeInTicks;
                        origIdx = 1;
                    end
                    numElems = numel(sortedExecTime);
                    if numElems > this.ResultsPerTest
                        numElems = this.ResultsPerTest;
                    end
                    avgExec = double(section.TotalExecutionTimeInTicks) / ...
                            double(section.NumCalls);
                    for z = 1:numElems
                        testNameCln{end+1} = tstName; %#ok<AGROW>
                        taskNameCln{end+1} = section.Name; %#ok<AGROW>
                        maxWcetCln(end+1) = sortedExecTime(z); %#ok<AGROW>
                        avgWcetCln(end+1) = avgExec; %#ok<AGROW>
                        if isAllData
                            maxExecAtCln(end+1) = section.Time(origIdx(z)); %#ok<AGROW>
                        end
                        numCallsCln(end+1) = section.NumCalls; %#ok<AGROW>
                    end
                end
            end

            if isAllData
                tbt = table(taskNameCln', maxWcetCln', maxExecAtCln', ...
                    avgWcetCln', numCallsCln', ...
                    testNameCln');
                tbt.Properties.VariableNames = {'Task', ...
                    'MaxExec', 'MaxExecAt', 'AvgExec', 'NumCalls', 'TestName'};
            else
                tbt = table(taskNameCln', maxWcetCln', ...
                    avgWcetCln', numCallsCln', ...
                    testNameCln');
                tbt.Properties.VariableNames = {'Task', ...
                    'MaxExec', 'AvgExec', 'NumCalls', 'TestName'};
            end
            tbt = sortrows(tbt, {'MaxExec'},{'descend'});
        end

        function tbt = computeHybridTaskSummary(this)
            ks = this.AnalysisResultMap.keys;
            taskName = cell(1,0);
            maxExecTime = zeros(1,0);
            for ki = 1:numel(ks)
                k = ks{ki};
                res = this.AnalysisResultMap(k);
                sections = res.Sections;
                for j = 1:sections.Size
                    section = sections(j);
                    if strcmp(section.Name, k)
                        taskName{end+1} = section.Name; %#ok<AGROW>
                        maxExecTime(end+1) = section.MaximumExecutionTimeInTicks; %#ok<AGROW>
                    end
                end
            end
            tbt = table(taskName', maxExecTime');
            tbt.Properties.VariableNames = {'Task', 'MaxExec'};
            tbt = sortrows(tbt, {'MaxExec'},{'descend'});
        end
    end
end

% LocalWords:  testname datamodel alldata
