function [metricStruct, traceStruct] = perform_operation(lhs_cvdata,rhs_cvdata,opFcn,opChar, joinedAggregatedTestInfo)
%PERFORM_OPERATION - Produce the data from a binary operation on cvdata objects
%
%   METRICSTRUCT = PERFORM_OPERATION(LHS_CVDATA,RHS_CVDATA,OPSTR)  A data
%   operation expressed as the string OPSTR in the form u=f(lhs,rhs) is 
%   performed on the raw execution counts in the cvdata objects LHS_CVDATA
%   and RHS_CVDATA.  The raw counts from each metric are aggregated and
%   the collection of metrics is returned in METRICSTRUCT.

%   Copyright 1990-2019 The MathWorks, Inc.

% NOTE: Arguments should have been verified before calling this function


if ~valid(lhs_cvdata)
    error(message('Slvnv:simcoverage:cvdata:InvalidCvData', 1));
end
if ~valid(rhs_cvdata)
    error(message('Slvnv:simcoverage:cvdata:InvalidCvData', 2));
end

[lhs_metricNames, lhs_toMetricNames] = getEnabledMetricNames(lhs_cvdata);
[rhs_metricNames, rhs_toMetricNames] = getEnabledMetricNames(rhs_cvdata);

metricNames = union(lhs_metricNames, rhs_metricNames);
toMetricNames = union(lhs_toMetricNames, rhs_toMetricNames);

metricStruct = [];
traceStruct = [];
rootId = lhs_cvdata.rootID;

lhsMetrics = lhs_cvdata.metrics;
rhsMetrics = rhs_cvdata.metrics;

lhsNumRuns = getNumRuns(lhs_cvdata);
rhsNumRuns = getNumRuns(rhs_cvdata);

traceOn = lhs_cvdata.traceOn || rhs_cvdata.traceOn;

cvi.ReportData.updateDataIdx(lhs_cvdata);

% If modelcov has no handle and active/current test is unset, use cvtest 
% from lhs cvdata as current test.
% (Necessary to ensure use of appropriate mcdcMode when dealing with 
% certain Simulink Test harnesses).
modelcovId = cv('get', rootId, '.modelcov');
[modelHandle, activeTest, currentTest] = ...
    cv('get', modelcovId, '.handle', '.activeTest', '.currentTest');
if (modelHandle == 0) && (activeTest == 0) && (currentTest == 0)
    cv('set', modelcovId, '.currentTest', lhs_cvdata.id);
end

% Loop through each possible metric
for metricI = metricNames(:)'
    metric = metricI{1};
    lhs = lhsMetrics.(metric);
    rhs = rhsMetrics.(metric);
    isSigrange = false;
    blankFiller = 0;

    if strcmpi(metric,'sigrange') || strcmpi(metric,'sigsize')
        isSigrange = true;
        blankFiller = NaN;
    elseif( strcmp(metric,'tableExec'))
        cvi.TopModelCov.checkMetricConsistency(lhs_cvdata.rootID, rhs_cvdata.rootID, true);
    end

    collectedMetricData = concatMetric(lhs, rhs, blankFiller);
    metricData = cvdata.processMetric(rootId, metric, collectedMetricData, opFcn, opChar);

    if ~isempty(metricData) && ~isSigrange 
            metricEnumVal = cvi.MetricRegistry.getEnum(metric);     
            metricData = cv('ProcessData',rootId, metricEnumVal, metricData);
    end
    metricStruct.(metric) = metricData;
    if traceOn && ~isSigrange 
        lhsTraceData = [];
        if ~isempty(lhs_cvdata.trace) && ...
            isfield(lhs_cvdata.trace, metric)
                lhsTraceData = lhs_cvdata.trace.(metric);
        end
        rhsTraceData = [];
        if ~isempty(rhs_cvdata.trace) && ...
            isfield(rhs_cvdata.trace, metric)
                rhsTraceData = rhs_cvdata.trace.(metric);
        end

        traceStruct.(metric) =  processTrace(lhsTraceData, rhsTraceData, lhs, rhs);
    end
end

if ~isempty(toMetricNames)
    tmpMtricStruct = [];
    for metricI = toMetricNames(:)'
        metric = metricI{1};
        lhs = [];
        rhs = [];
        if isfield(lhs_cvdata.metrics.testobjectives, metric)
            lhs = lhs_cvdata.metrics.testobjectives.(metric);                    
        end
        if isfield(rhs_cvdata.metrics.testobjectives, metric)
            rhs = rhs_cvdata.metrics.testobjectives.(metric);
        end

        collectedMetricData = concatMetric(lhs, rhs, 0);
        metricData = cvdata.processMetric(rootId, metric, collectedMetricData, opFcn, opChar);

        if ~isempty(metricData)
            metricenumValue = cvi.MetricRegistry.getEnum(metric);    
            metricdataId = cv('new', 'metricdata', '.metricName', metric, '.metricenumValue',metricenumValue);
            cv('set',metricdataId ,'.data.rawdata', metricData,'.size', numel(metricData)); 
            metricData = cv('ProcessTOData',rootId, metricdataId);
            cv('delete',metricdataId ); 
        end
        tmpMtricStruct.(metric) = metricData;

        if traceOn 
            lhsTraceData = [];
            if ~isempty(lhs_cvdata.trace) && ...
                isfield(lhs_cvdata.trace, 'testobjectives') && ...
                isfield(lhs_cvdata.trace.testobjectives, metric)
                    lhsTraceData = lhs_cvdata.trace.testobjectives.(metric);
            end
            rhsTraceData = [];
            if ~isempty(rhs_cvdata.trace) && ...
                isfield(rhs_cvdata.trace, 'testobjectives') && ...
                isfield(rhs_cvdata.trace.testobjectives, metric)
                    rhsTraceData = rhs_cvdata.trace.testobjectives.(metric);
            end
            tmpTraceStruct.(metric) =  processTrace(lhsTraceData, rhsTraceData, lhs, rhs);
        end
    end
    metricStruct.testobjectives = tmpMtricStruct;

    if traceOn 
        traceStruct.testobjectives = tmpTraceStruct;    
    end

end


    %=================================
    function  trace = processTrace(lhsTraceData, rhsTraceData, lhs, rhs)
    try    
        trace = [];
        if (opChar ~= '+')
            return;
        end

        if ~isempty(lhsTraceData)       % LHS previously aggregated.
            lhsTrace = lhsTraceData;    %   -> Use existing trace data.
        elseif ~isempty(lhs)            % LHS is single run.
            lhsTrace = lhs;             %   -> Use metric value.
        else                            % LHS has metric off.
            lhsTrace = zeros(size(rhs,1), lhsNumRuns);%   -> Use zeros (length of RHS).
        end

        if ~isempty(rhsTraceData)       % RHS previously aggregated.
            rhsTrace = rhsTraceData;    %   -> Use existing trace data.
        elseif ~isempty(rhs)            % RHS is single run.
            rhsTrace = rhs;             %   -> Use metric value.
        else                            % RHS has metric off.
            rhsTrace = zeros(size(lhs,1), rhsNumRuns);%   -> Use zeros (length of LHS).
        end

        trace = [lhsTrace, rhsTrace];
        assert(isempty(trace) || (numel(joinedAggregatedTestInfo) == size(trace,2)));
    catch MEx
        rethrow(MEx);
    end
    end
end

%=================================
function numRuns = getNumRuns(cvd)
    numRuns = numel(cvd.aggregatedTestInfo);
    if (numRuns == 0)
        % No previously aggregated runs. Count 1 for the current run.
        numRuns = 1;
    end
end

function collectedMetricData = concatMetric(lhs, rhs, blankFiller)
    if isempty(lhs)
        lhs = ones(size(rhs)) .* blankFiller;
    elseif isempty(rhs)
        rhs = ones(size(lhs)) .* blankFiller;
    end
    
    collectedMetricData = [lhs, rhs];
end

