function analysisInformation = getAnalysisInformation(testcomp, modelH)
%

%   Copyright 2012-2023 The MathWorks, Inc.

    [InputPortInfo, OutputPortInfo] = Sldv.DataUtils.generateIOportInfo(modelH);
    settings = testcomp.activeSettings;

    % TODO: Convert analysisStatus to enum
    analysisInformation.Status = testcomp.analysisStatus;
    analysisInformation.AnalysisTime = testcomp.getAnalysisTime();
    analysisInformation.ElapsedTime = testcomp.getElapsedTime();
    analysisInformation.Options = settings.deepCopy;
    analysisInformation.InputPortInfo = InputPortInfo;
    analysisInformation.OutputPortInfo = OutputPortInfo;
    analysisInformation.SampleTimes = testcomp.mdlSampleTimes;
    analysisInformation.Parameters = [];

    if isempty(testcomp.tunableParamsAndConstraints)
        analysisInformation.MultiParamConstraints = [];
    else
        analysisInformation.MultiParamConstraints = testcomp.tunableParamsAndConstraints.Constraints.multiParamConstraints;
    end

    % ObservabilityCustomization and DetectDSMAccessViolations
    % being private options, do not get copied during 
    % deepCopy. Copying them explicitly now. If these options are made public, 
    % these lines can be deleted.
    analysisInformation.Options.ObservabilityCustomization = settings.ObservabilityCustomization;
    analysisInformation.Options.DetectDSMAccessViolations = settings.DetectDSMAccessViolations;
    analysisInformation.Options.DetectBlockInputRangeViolations = settings.DetectBlockInputRangeViolations;

    if isempty(testcomp.tunableParamsAndConstraints)
        singleParamConstraints = [];
    else
        singleParamConstraints = testcomp.tunableParamsAndConstraints.Constraints.singleParamConstraints;
    end
    
    if ~isempty(singleParamConstraints)...
            && ~isempty(fields(singleParamConstraints))
        % testcomp.tunableParamsAndConstraints.Constraints.singleParamConstraints could also be an empty struct In which
        % case, we need to initialize analysisInformation.Parameters to '[]'
        analysisInformation.Parameters = ...
            prepareParameterSettings(testcomp, modelH);
    else
        analysisInformation.Parameters = [];
    end

    %Rename getAbstractedBlockHandles to getAbstractedModelObjects..
    analysisInformation.AbstractedBlocks = ...
        prepareAbstractionInfo(testcomp.getAbstractedBlockHandles, modelH);

    analysisInformation.Approximations = getAnalysisApproximations(testcomp);

    % Block handles are computed from the compiled info. Block handles need
    % not be preserved across compilation. For instance, how could Simulink
    % guarantee preservation of block handles for:
    % load_system(model)
    % ... cache block handles ...
    % close_system(model)
    % load_system(anothermodel)
    % load_system(model)
    % since anothermodel may end up using the same handles (since model
    % was closed).
    analysisInformation.ReducedBlocks = convertBlockHListToSidList(testcomp.reducedBlocks, testcomp);
    analysisInformation.conditionallyExecutedBlocks = convertBlockHListToSidList(testcomp.conditionallyExecutedBlocks, testcomp);

    analysisInformation = Sldv.DataUtils.getTimerOptimizations(testcomp, analysisInformation);
    analysisInformation = Sldv.DataUtils.getSlowOverflow(testcomp, analysisInformation);
    
    analysisInformation.AppliedAnalysisStrategy = ...
        sldvshareprivate('sldv_get_strategy_name', testcomp);
    
    analysisInformation.HasNotInterpretableStubing = ...
        testcomp.analysisInfo.hasNotInterpretableStubbedElem;
    
    analysisInformation.IsExportFcn = testcomp.analysisInfo.blockDiagramExtract && ...
        ~isempty(testcomp.analysisInfo.exportFcnGroupsInfo);
    if analysisInformation.IsExportFcn
        analysisInformation.ExportFcnGroupInfo = testcomp.analysisInfo.exportFcnGroupsInfo;
    end

    if ~isempty(testcomp.analysisInfo.stubbedSimulinkFcnInfo)
        analysisInformation.StubbedSimulinkFcnInfo = testcomp.analysisInfo.stubbedSimulinkFcnInfo;
    end
end

function sidList = convertBlockHListToSidList(aBlkList, aTestComp)
    if ~isempty(aBlkList)
        % Technically, conditionallyExecutedBlocks does not need the
        % designSid/replacementSid distinction. In order to not add special
        % casing, using the same structure as ReducedBlocks
        sidList = struct('designSid',{},'replacementSid',{});
        defaultBlkH.slBlkH = [];
        defaultBlkH.isSlBlock = true;
        defaultBlkH.sfObjID = 0;
        defaultBlkH.isStateflow = false;
        defaultBlkH.typeDesc = '';
        defaultBlkH.isTruthTableGen = false;
        for i=1:length(aBlkList)
            blkH = defaultBlkH;
            % reduced blocks are computed from the compiled info. Some
            % blocks may no longer be present which were created during
            % compilation. Hence, we need to check for the validity of such
            % blocks before finding their handles.
            if Simulink.ID.isValid(aBlkList{i})
                blkH.slBlkH = Simulink.ID.getHandle(aBlkList{i});
                [designObjectSL, replacementSid] = Sldv.DataUtils.getDesignObject(aTestComp, blkH.slBlkH);
                [~, ~, designSid] = Sldv.DataUtils.getPathAndNumber(blkH, designObjectSL, 0);
                sidList(i).designSid = designSid;
                sidList(i).replacementSid = replacementSid;
            end
        end
    else
        sidList = [];
    end
end

function params = prepareParameterSettings(testcomp, modelH)
    paramsAndConstraints = testcomp.tunableParamsAndConstraints;
    parameterInfo = testcomp.mdlFlatIOInfo.Parameter;
    modelName = get_param(modelH, 'name');
    dataAccessor = Simulink.data.DataAccessor.createForGlobalNameSpaceClosure(modelName);

    if isfield(parameterInfo, 'name')
        paramNameList = {parameterInfo.name};
    end

    settings = paramsAndConstraints.Constraints.singleParamConstraints;
    n_config = length(settings);

    params = cell(n_config, 1);
    for idx=1:n_config
        flattenedParameterStruct = sldvshareprivate('getFlatParameterStructure',settings(idx), testcomp.mdlFlatIOInfo, dataAccessor);
        n = length(flattenedParameterStruct);
        % fields = fieldnames(flattenedParameterStruct);
        params_i = cell(n, 1);
        for jdx=1:n
                % If'BusParameterTuning' is turned on, we are able to deal
                % with struct/bus params using the flattenedParameterStruct.
                % If not, flattenedParameterStruct would not have
                % structures. Either cases can be fulfilled by this code.
                params_i{jdx}.name = flattenedParameterStruct(jdx).name;
                params_i{jdx}.values = { sldvprivate('util_mxarray_print', ...
                                               flattenedParameterStruct(jdx).spec) };
                params_i{jdx}.type = getParamType(paramsAndConstraints, params_i{jdx}.name);
                if contains(flattenedParameterStruct(jdx).name, '.')
                    hierarchyNames = split(flattenedParameterStruct(jdx).name, '.');
                    topParamName = hierarchyNames{1};
                    % linearly searching for the soruce correspoding to parameter
                    % to which struct fields belongs to
                    paramIdx = find(ismember(paramNameList, topParamName));
                else
                    paramIdx = find(ismember(paramNameList, flattenedParameterStruct(jdx).name));
                end
                params_i{jdx}.source = parameterInfo(paramIdx).Source;
                params_i{jdx}.sourceType = parameterInfo(paramIdx).SourceType;
        end
        params{idx} = params_i;
    end
end

function paramType = getParamType(paramsAndConstraints, paramName)
    import Sldv.utils.SLDVParamType;

    paramType = char(SLDVParamType(0));

    for idx = 1:numel(paramsAndConstraints.VariantParams)
        if isequal(paramName, paramsAndConstraints.VariantParams{idx})
            paramType = char(SLDVParamType(1));
            return;
        end
    end
end

function abstractBlocks = prepareAbstractionInfo(abstractModelObjInfo, modelH)

    abstractBlocks = [];

    if ~isempty(abstractModelObjInfo)
        % Each row is one block info - ordered by
        % slBlkH, sfObjId, emlStart, emlEnd
        [numBlks, ~] = size(abstractModelObjInfo);
        abstractBlocks = cell(numBlks, 1);

        for idx=1:numBlks        
            modelObj = Sldv.DataUtils.createModelObject(modelH, abstractModelObjInfo{idx,1}, abstractModelObjInfo{idx,2}, abstractModelObjInfo{idx,5});
            [typeFromModelObj, ~, sid] = Sldv.DataUtils.getPathAndNumber(modelObj);
            name = 'Unsupported block'; %NO_L10N
            type = 'Unknown';
            if modelObj.isStateflow
                [~, name, type] = sldvshareprivate('util_sf_link', modelObj.sfObjID);
                if ~isempty(typeFromModelObj) && ~strcmp(typeFromModelObj,'-')
                    type = typeFromModelObj;
                end
                if ~isempty(sid) && all([abstractModelObjInfo{idx,3} abstractModelObjInfo{idx,4}])
                    sid = sprintf('%s:%d-%d',sid,abstractModelObjInfo{idx,3},abstractModelObjInfo{idx,4});
                end
            elseif modelObj.slBlkH == 0 && modelObj.sfObjID == 0
                % External MATLAB file from MLSys block
                name = '';
                type = typeFromModelObj; % 'Script'
                sid = '';
            elseif modelObj.slBlkH~=modelH
                name = get_param(modelObj.slBlkH, 'Name');
                type = get_param(modelObj.slBlkH, 'BlockType');
            end                
            abstractBlocks{idx}.name = { name };
            abstractBlocks{idx}.sid = { sid };
            abstractBlocks{idx}.type = { type };
        end        
    end
end

function approx = getAnalysisApproximations(testComp)
    approx = [];
    flags = [];
    err = [];

    flags.hasDouble2RatConvert = testComp.hasDouble2RatConvert;
    flags.hasWhileLoopApprox = testComp.hasWhileLoopApprox;
    flags.hasMultiInsNormalMode = testComp.hasMultiInsNormalMode;
    flags.lookup2DisReported = false;
    flags.lookupFxpisReported = false;
    flags.lookupApproxisReported = false;

    % Special case for XIL-based test generation:
    %    - no distinction between model reference instances
    %    - no approximation on lookup tables
    %    - no model approximation (because code based!)
    isXIL = testComp.isModelRefSIL() || testComp.isSIL();
    if isXIL
        flags.hasMultiInsNormalMode = false;
    end

    if testComp.usesLookupApproximation && ~isXIL
        % when we get usesLookupApproximation from backend, we have the
        % generic LUT approximation flag because not all LUT approximation cases 
        % are captured by testComp.analysisInfo.Approximations.LookupTables
        flags.lookupApproxisReported = true;
        
        isEvenSpacing = false;
        
        aLUTs = testComp.analysisInfo.Approximations.LookupTables;
        if ~isempty(aLUTs)
            for idx = 1:length(aLUTs)
                this_lut = aLUTs(idx);
                blk = this_lut.blk;
                types = this_lut.types;
                % g1401150 - If the DataSpecification LUT is of Lookup Table 
                % Object type, then use the table and breakpoint information 
                % from the object. Only when the specification is "Table
                % and breakpoints" pick the information from blk
                % parameters.
                
                % The LookupTables field only has 1D and 2D Lookup Tables.
                % Thus, it is safe to assume the dimension is either 1 or 2.
                % For more info, please see collect_luts(...) in
                % toolbox/sldv/sldv/+Sldv/@Translator/initCoverage.m
                numDims = str2double(get_param(blk, 'NumberOfTableDimensions'));
                assert(numDims == 1 || numDims == 2);
                
                if strcmp(get_param(blk, 'DataSpecification'), 'Lookup table object')
                    lutObjName = get_param(blk, 'LookupTableObject');
                    % lutVar = Simulink.findVars(getfullname(blk), 'Name', lutObjName);
                    % As the SLDV analysis has completed successfully, the LookupTableObject
                    % will be available and found. No need to check isempty(lutVar).
                    lutObj = slResolve(lutObjName, blk);
                    table = lutObj.Table.Value;
                    
                    if strcmp(lutObj.BreakpointsSpecification, 'Explicit values')
                        bpSpec = lutObj.Breakpoints;
                        bpx = bpSpec(1).Value;
                        if length(bpSpec) >= 2
                            bpy = bpSpec(2).Value;
                        else
                            bpy = [];
                        end
                    else
                        isEvenSpacing = true;
                    end
                elseif strcmp(get_param(blk, 'BreakpointsSpecification'), 'Explicit values')
                    bpx = slResolve(get_param(blk,'BreakpointsForDimension1'),blk);
                    if numDims > 1
                        bpy = slResolve(get_param(blk,'BreakpointsForDimension2'),blk);
                    else
                        bpy = [];
                    end
                    table = slResolve(get_param(blk,'Table'),blk);
                else
                    isEvenSpacing = true;
                end
                mode = strcmp(get_param(blk,'ExtrapMethod'),'Clip');
                
                errorData.blockH = blk;
                errorData.blockType = 'Lookup_n-D';
                errorDetail.useEndPoint = mode;
                
                if isEvenSpacing
                    % We do not insert an approximation for evenly spaced
                    % breakpoints.
                    maxErrorAll = 0;
                elseif isempty(types)
                    % If types is empty, it means this is a 2D floating-point LUT
                    flags.lookup2DisReported = true;
                    [~, ~, ~, maxErrorAll] = sldvprivate('util_lookup2D_slope_off',...
                        double(mode), bpx, bpy, table(:));
                elseif numel(types) == 2
                    % If we have two types, this is a 1D int/fixpt LUT
                    % and we have the types of the input and the output
                    flags.lookupFxpisReported = true;
                    Tx = types(1);
                    To = types(2);
                    bpx = fi(bpx,Tx);
                    bpx = bpx.storedInteger;
                    table = fi(table,To);
                    table = table.storedInteger;
                    if length(unique(bpx)) > 1
                        [~,~,~,~,maxErrorAll] = ...
                            Sldv.Utils.sldvemlLookup1DfxpPrecompute(Tx, To,...
                            bpx(:), table(:), length(bpx));
                    else
                        maxErrorAll = 0;
                    end
                else
                    % 2D int/fixpt LUT with parameter and output types
                    flags.lookupFxpisReported = true;
                    Tx = types(1);
                    Ty = types(2);
                    To = types(3);
                    bpx = fi(bpx,Tx);
                    bpx = bpx.storedInteger;
                    bpy = fi(bpy,Ty);
                    bpy = bpy.storedInteger;
                    table = fi(table,To);
                    table = table.storedInteger;
                    if (length(unique(bpx)) > 1 ) && ...
                            (length(unique(bpy)) > 1)
                        [~,~,~,~,~,~,~,maxErrorAll] = ...
                            Sldv.Utils.sldvemlLookup2DfxpPrecompute(Tx, Ty, To,...
                            bpx(:), bpy(:), table(:), length(bpx), length(bpy));
                    else
                        maxErrorAll = 0;
                    end
                end
                maxError = max(max(maxErrorAll));
                errorDetail.errorMatrix = maxErrorAll;
                errorData.maxError = maxError;
                errorData.errorDetail = errorDetail;
                
                if isempty(testComp.mdlApproxErrorsInfo)
                    testComp.mdlApproxErrorsInfo = errorData;
                else
                    testComp.mdlApproxErrorsInfo(end+1) = errorData;
                end
            end
        end
    end
  
    if ~isempty(testComp.mdlApproxErrorsInfo) && ~isXIL

        for i=1:length(testComp.mdlApproxErrorsInfo)
            errorData = testComp.mdlApproxErrorsInfo(i);
            blockType = errorData.blockType;
            
            if strcmp(blockType,'Lookup_n-D') || ...
                    isRelatedToBlockReplacement(testComp, errorData)
                %flags.lookup2DisReported = true;
                
                
                if errorData.errorDetail.useEndPoint
                    blockname = get_param(errorData.blockH,'Name');
                    sid = Simulink.ID.getSID(errorData.blockH);
                    err(end+1).name = { blockname }; %#ok<AGROW>
                    err(end).sid = { sid };
                    err(end).type = { errorData.blockType };
                    err(end).error = { num2str(errorData.maxError) };
                end
            end
        end
    end
    approx.flags = flags;
    approx.errors = err;
end

function out = isRelatedToBlockReplacement(testComp, errorData)
    out = false;
    if ~isempty(testComp.analysisInfo) && ...
            isfield(testComp.analysisInfo,'replacementInfo') && ...
            ~isempty(testComp.analysisInfo.replacementInfo) && ...
            testComp.analysisInfo.replacementInfo.replacementsApplied
        table = testComp.analysisInfo.replacementInfo.replacementTable;
        if table.isKey(errorData.blockH)
            replacementInfo = table(errorData.blockH);
            rule = replacementInfo.RepRuleInfo;
            out = strcmp(rule.BlockType,'Lookup_n-D');
        end
    end
end
