function targetOnHostCodeGenHook(model, lGlobalRegistry, buildInfo)
%

%   Copyright 2017-2020 The MathWorks, Inc.
%   
%   this hook is called to configure ProbeGlobalRegistry for extmode
%   simulations via XCP and host-based targets. The implemented steps 
%   are: task-level instrumentation, timer registration and 
%   ProbeGlobalRegistry configuration to generate XCP code
%   This API instruments only PERIODIC entry points async and inherited
%   sample rates are not generated in external mode
    
    if strcmp(get_param(model, 'GenerateSampleERTMain'), 'off')
        DAStudio.error('CoderProfile:ExecutionTime:XCPGenerateMainFile', 'GenerateSampleERTMain');
    end
    
    lTimer = coder.profile.private.TimerWrap(coder.profile.crlHostTimer());
    if mod(lTimer.getTimerWordLength, get_param(model, 'TargetWordSize'))
        DAStudio.warning('CoderProfile:ExecutionTime:XCPTimerAndWordSizeMismatch');
    end  
    
    % find build directory
    lBuildDirectory = buildInfo.getSourcePaths(true, 'BuildDir');
    if iscell(lBuildDirectory)
        lBuildDirectory = lBuildDirectory{1};
    end

    hasFcnInstr = ~strcmp(get_param(model, 'CodeProfilingInstrumentation'), 'off');
    if hasFcnInstr
        mainDir =  fileparts(lBuildDirectory);
        buildDir = lBuildDirectory;
    else
        mainDir = lBuildDirectory;
        buildDir = fullfile(lBuildDirectory, 'instrumented');
    end

    lCaptureModeActive = strcmp(get_param(model, 'CodeProfilingSaveOptions'), 'MetricsOnly');

    % files with the instrumentation API
    [lSourceFile, lHeaderFile] = loc_GetSourceFiles(buildDir, model);
    lGlobalRegistry.SourceFileTargetInterface = lSourceFile;
    lGlobalRegistry.HeaderFileTargetInterface = lHeaderFile;
    lGlobalRegistry.TimerHWCounterUnit = ...
        coder.profile.private.getHWCounterUnit();
    lGlobalRegistry.UploadDataInRealTime = ~lCaptureModeActive;
    
    % task-level instrumentation
    lTopLevelRegistryNew = loc_CreateTaskLevelInstrumentation(mainDir, buildDir, model, lCaptureModeActive);
    lGlobalRegistry.addRegistries({lTopLevelRegistryNew});

    lGlobalRegistry.InlineGeneratedCode = ...
        ~strcmp(get_param(model, 'TargetLangStandard'), 'C89/C90 (ANSI)');
    lGlobalRegistry.SplitAtomicReadStoreFcn = false;
    
    % timer registration
    lGlobalRegistry.ProfilingTimer = lTimer;

    % data collection function
    lGlobalRegistry.AtomicReadStoreFcnName = 'xcpAtomicReadStoreProfiling';

    % single- or multi-tasking model
    lGlobalRegistry.SingleThreadTiming = false;
    lGlobalRegistry.SingleTaskModel = loc_IsSingleTaskingModel(mainDir, model);
    
    % XCP configuration
    lGlobalRegistry.RequireXCPSupport = true;
    lGlobalRegistry.XCPInstrumentedFolder = buildDir;
    lGlobalRegistry.AddXCPHeaderFiles = [];
    lGlobalRegistry.AddXCPSourceFiles = [];
    if ~lCaptureModeActive && ~lGlobalRegistry.SingleTaskModel && ~ismac
            lGlobalRegistry.XCPCoreIDFcn = 'profiling_get_cpuid_x86()';
            lGlobalRegistry.AddXCPHeaderFiles{end+1} = fullfile(matlabroot, ...
            'toolbox', 'coder', 'profile', 'src', 'host_cpuid_x86.h');
            lGlobalRegistry.AddXCPSourceFiles{end+1} = fullfile(matlabroot, ...
            'toolbox', 'coder', 'profile', 'src', 'host_cpuid_x86.c');
    else
        lGlobalRegistry.XCPCoreIDFcn = [];
    end
    lGlobalRegistry.XCPThreadIDFcn = '';
    lGlobalRegistry.XCPEventID = coder.profile.xcp.profilingXCPEventID();
    if hasFcnInstr
        maxNumSamples = [];
    else
        maxNumSamples = 4;
    end
    lGlobalRegistry.XCPBufferConfig = ...
        coder.profile.xcp.computeBufferParameters(getActiveConfigSet(model), ...
            lTimer.getTimerType, ~isempty(lGlobalRegistry.XCPCoreIDFcn), false, maxNumSamples);
end

function lTopLevelRegistry = loc_CreateTaskLevelInstrumentation(mainDir, buildDir, modelname, lCaptureModeActive)
    % create Task-Level profiling registry: create a probe for each init, 
    % update, output, initcondition and terminate function
    if lCaptureModeActive
        lBufferUploadMethods = 'uploadOnTargetProfilingData();';
    else
        lBufferUploadMethods = 'extmodeEmptyProfilingBuffer();';
    end
    
    % read main source file
    if strcmpi(get_param(modelname, 'TargetLang'), 'CPP')
        mainSrcFile = 'ert_main.cpp';
    else
        mainSrcFile = 'ert_main.c';
    end
    main_content = fileread(fullfile(mainDir, mainSrcFile));
    
    % check if the main source file is already instrumented with function
    % profiling
    if strcmp(get_param(modelname, 'CodeProfilingInstrumentation'), 'off') && ...
            contains(main_content, lBufferUploadMethods)
        % without fcn profiling, the main source file is compiled from the
        % main folder (not copied in the instrumented folder) so there is
        % only a copy with the instrumentation
        % with fcn profiling, the main source file in the main folder is
        % not instrumented and it is copied (and then instrumented) in the
        % instrumented folder
        % with fcn profiling, there is no problem with incremental build
        % but without fcn profiling we must avoid to instrument the main
        % source file more once
        % here, we have detected that the main source file is already
        % instrumented (from a previous build)
        lTopLevelRegistry =  coder.profile.CoderInstrumentationInfo.getTaskRegistry(buildDir);
        assert(~isempty(lTopLevelRegistry), ...
            ['The main source file is already instrumented ' ...
             'but the serialized information is missing']);
        return;
    end
    
    % this code instruments main source file generated from TLC. Although this
    % approach allows us to instrument static main (rt_main.c/cpp), it is not
    % supported yet
    [~, lCodeFolderRelative] = fileparts(buildDir);
    lTopLevelRegistry = coder.profile.TimeProbeComponentRegistry(modelname, ...
                            '', ...
                            get_param(modelname, 'TargetWordSize'),...
                            [],...
                            [],...
                            lCodeFolderRelative);
    codeDescriptor = coder.internal.getCodeDescriptorInternal(mainDir, modelname);
    codeInfo = codeDescriptor.getComponentInterface();
    assert(~isempty(codeInfo), 'The Code Descriptor is empty');
    codeInfoFcnMethods = {...
        'InitializeFunctions', ...
        'UpdateFunctions', ...
        'OutputFunctions', ...
        'InitConditionsFunction', ...
        'TerminateFunctions', ...
    };
 
    for i=1:length(codeInfoFcnMethods)
        fcns = codeInfo.(codeInfoFcnMethods{i});
        numFcns = length(fcns);
        names = cell(1, numFcns);
        for k=1:numFcns
            names{k} = fcns(k).Prototype.Name;
        end
        names = unique(names);
        for k=1:length(names)
            functionInfoForProfiling = coder.profile.ExecTimeConfig.getFunctionInfoForProfiling(fcns(k));
            functionName = functionInfoForProfiling.taskName;
            if contains(main_content, functionName)
                sectionIdForFunction = lTopLevelRegistry.requestIdentifierForTask(functionName, functionInfoForProfiling, modelname);
                codeIdentifiers = lTopLevelRegistry.getCodeIdentifiers(ProbeType.TASK_TIME_PROBE);
                profStart = sprintf('%s(%dU);', codeIdentifiers.startMacroName, sectionIdForFunction);
                profEnd = sprintf('%s(%dU);', codeIdentifiers.endMacroName, sectionIdForFunction);
                if lCaptureModeActive && strcmp(codeInfoFcnMethods{i}, 'TerminateFunctions')
                    profBuffer = lBufferUploadMethods;
                elseif lCaptureModeActive || strcmp(codeInfoFcnMethods{i}, 'InitializeFunctions') || ...
                        (strcmp(get_param(modelname,'CombineOutputUpdateFcns'), 'off') && ...
                         strcmp(codeInfoFcnMethods{i}, 'OutputFunctions'))
                    profBuffer = '';
                else
                    profBuffer = lBufferUploadMethods;
                end
                replaceStr = [profStart '\n' ...   % start profiling
                              '$&\n' ...           % instrumented function
                              profEnd '\n' ...     % stop profiling
                              profBuffer];         % update buffer
                main_content = regexprep(main_content, [functionName '\([\s\w,]*);'], replaceStr);
            end
        end
    end
    
    % add profiling header
    replaceStr = '$&\n#include "ext_mode_profiling.h"';
    if contains(main_content, '#include "ext_work.h"')
        findHeader = 'ext_work.h';
    elseif contains(main_content, '#include "ext_mode.h"')
        findHeader = 'ext_mode.h';
    else
        assert(false, 'External mode include not found in ert_main');
    end
    % find #include "ext_mode.h" /* External mode header file */
    main_content = regexprep(main_content, ['#include\s"' findHeader '"[\s]*/\*[\s\w]*\*/'], replaceStr);
    
    if ~strcmp(get_param(modelname, 'CodeProfilingInstrumentation'), 'off')
        filename = fullfile(buildDir, mainSrcFile);
    else
        filename = fullfile(mainDir, mainSrcFile);
    end
    fid = fopen(filename, 'w');
    fprintf(fid, '%s', main_content);
    fclose(fid);
    c_beautifier(filename);
end

function isSingleTasking = loc_IsSingleTaskingModel(mainDir, modelname)
    % the model is single-tasking if one of these two statements is true:
    % - there is only a single rate in the model
    % - all rates are executed sequentially within the same thread
    codeDescriptor = coder.internal.getCodeDescriptorInternal(mainDir, modelname);
    codeInfo = codeDescriptor.getComponentInterface();
    assert(~isempty(codeInfo), 'The Code Descriptor is empty');

    numRates = 0;
    for i=1:length(codeInfo.TimingProperties)
        timing = codeInfo.TimingProperties(i);
        if strcmp(timing.TimingMode, 'PERIODIC')
            numRates = numRates + 1;
        end
    end

    isSingleTasking = ~(numRates > 1 && ...
                        strcmp(get_param(modelname, 'SolverMode'), 'MultiTasking') && ...
                        strcmp(get_param(modelname, 'EnableMultiTasking'), 'on'));
end

function [sourceFullPath, headerFullPath] = ...
              loc_GetSourceFiles(buildDir, modelname)
    filename = 'xcp_instrumentation';
    lTargetLang = get_param(modelname, 'TargetLang');
    [lHeaderType, lSourceType] = loc_GetFileInfo(lTargetLang);
    headerFullPath = fullfile(buildDir, [filename lHeaderType]);
    sourceFullPath = fullfile(buildDir, [filename lSourceType]);
end

function [headerExt, sourceExt] = loc_GetFileInfo(targetLang)
    switch targetLang
        case 'C'
            headerExt = '.h';
            sourceExt = '.c';
        case 'CPP'
            headerExt = '.hpp';
            sourceExt = '.cpp';
        otherwise
            assert(true, 'Invalid target language');
    end
end

% LocalWords:  extmode XCP xcp cpuid initcondition async
