function this = runParallel(this)
%

%   Copyright 2016-2023 The MathWorks, Inc.

CleanupFutures = onCleanup(@()cancelAllJobs(this.Parallel));
checkForOptimizableVariables(this);
checkXConstraintFcnSatisfiability(this);
this = processInitializationData(this);
this.IterationStartTime = tic;
this = callPlotFcn(this, 'initial');
this = callOutputFcn(this, 'initial');
this = fitModels(this);
[this.IncumbentF, this.IncumbentX] = findIncumbent(this);
if this.NumObjectiveEvaluations == 0
    % We're not resuming, so add more points to the queue if
    % necessary to fill it...
    this = padQueuedX(this, this.Parallel.NumWorkers);
    % ... and launch the first NumWorkers points in the queue; Then remove them from queue.
    this.Parallel = launchMultipleJobs(this, this.QueuedX(1:this.Parallel.NumWorkers,:));
    this.QueuedX(1:this.Parallel.NumWorkers,:) = [];
end
this = chooseNextPointParallel(this);
iteration = this.NumObjectiveEvaluations + 1;
% Main loop
if iteration == 1 && this.PrivOptions.Verbose >= 1
    printVerboseSummary(this.PrivOptions.BayesOptPrint, this);
end
while ~optimizationFinished(this, iteration)
    % If there's a free worker and XNext, launch XNext.
    if anyFreeWorkers(this.Parallel) && ~isempty(this.XNext)
        this.Parallel = launchJob(this.Parallel, this.PrivOptions.ObjectiveNargout, ...
            this.PrivOptions.NumCoupledConstraints, this.XNext, conditionalizeX(this, this.XNext));
        this.XNext = [];
    end
    % If workers are under-utilized, launch random jobs to immediately fill
    % all workers
    if numPendingX(this.Parallel) < this.PrivOptions.MinWorkerUtilization
        NumFreeWorkers = this.Parallel.NumWorkers - numPendingX(this.Parallel);
        [Xs, this] = randomOrGridXFeasiblePoints(this, NumFreeWorkers);
        if this.PrivOptions.Verbose >= 2 && ~isempty(Xs)
            bayesoptim.printInfo('MultipleJobLaunch', num2str(numPendingX(this.Parallel)), num2str(size(Xs,1)));
        end
        this.Parallel = launchMultipleJobs(this, Xs);
    end
    % Maybe wait for a job to finish.
    if ~isempty(this.Parallel.PendingX) && ~anyFinishedJobs(this.Parallel) && ...
            (doingGridsearch(this) && gridFullySampled(this) || ...
            ~doingGridsearch(this) && ~anyFreeWorkers(this.Parallel))
        timeout = max(0, this.PrivOptions.MaxTime - toc(this.CurrentRunStartTime));
        waitForAnyJobToFinish(this.Parallel, timeout);
    end
    % If any jobs finished, record them all.
    NumFinishedJobs = 0;
    while anyFinishedJobs(this.Parallel)
        NumFinishedJobs = NumFinishedJobs + 1;
        [FinishedX, Objective, ConstraintViolations, UserData, ErrorConstraintViolation, ...
            TotalJobTime, LaunchTime, ObjectiveNargout, this.Parallel] = getJobResults(this.Parallel);
        this = recordFEvalResults(this, FinishedX, Objective, ConstraintViolations, ...
            UserData, ErrorConstraintViolation, TotalJobTime, ObjectiveNargout);
    end
    FinishedIterationNums = iteration : iteration+NumFinishedJobs-1; % The iteration numbers of the finished fevals.
    % If there were any finished jobs, refit the models and
    % find the new incumbent
    PointSelectionT = [];
    if NumFinishedJobs > 0
        FitModelsT = tic;
        this = fitModels(this);
        if this.PrivOptions.Verbose >= 2
            bayesoptim.printInfo('ModelFitTime', num2str(toc(FitModelsT)));
        end
        this = updateTraces(this, FinishedIterationNums);
        PointSelectionT = tic;
        [this.IncumbentF, this.IncumbentX] = findIncumbent(this);
    end
    if isempty(PointSelectionT)
        PointSelectionT = tic;
    end
    % Choose a new point to evaluate unless we have one already.
    if isempty(this.XNext)
        this = chooseNextPointParallel(this);
    end
    if this.PrivOptions.Verbose >= 2
        bayesoptim.printInfo('PointSelectionTime', num2str(toc(PointSelectionT)));
    end
    % Update timing data for all newly-finished iterations, if any.
    this = updateTimingPlotsAndOutput(this, FinishedIterationNums);
    % Update iteration number
    if ~isempty(FinishedIterationNums)
        iteration = FinishedIterationNums(end)+1;
    end
    % If gridsearch exhausted the grid, and there are no outstanding jobs,
    % reset SampledGridIndices so we can start another grid pass.
    if isempty(this.XNext) && doingGridsearch(this) && gridFullySampled(this) && ...
            isempty(this.Parallel.PendingX) && ~anyFinishedJobs(this.Parallel)
        this.SampledGridIndices = [];
        if this.PrivOptions.Verbose >= 2
            bayesoptim.printInfo('GridFullySampled');
        end
    end
end
this = pruneGridIndices(this);
this = callPlotFcn(this, 'done');
this = callOutputFcn(this, 'done');
showStoppingReason(this);
showBestPoints(this);
showVerboseDocHelp(this);
% Remove things that will cause a parallel pool to be opened
% upon load
this.FantasyModels = [];
this.Parallel = [];
end
