function NewResults = resume(Results, varargin)
%RESUME Resume a Bayesian Optimization.
%   RESULTS = resume(RESULTS, 'PARAM1', val1, 'PARAM2'
%   ,val2,...) continues running an optimization with modified
%   options until new stopping criteria are met. All name/value
%   parameters accepted by bayesopt are accepted, except
%   'UseParallel', 'NumSeedPoints' and the 'Initial___'
%   parameters. Important parameters for resuming are:
%       'MaxObjectiveEvaluations'
%                   - Specifies the additional number of
%                     function evaluations to run. Default: 30
%       'MaxTime'   - Specifies the additional number of
%                     seconds to run. Default: Inf
%       'VariableDescriptions'
%                   - Specifies new variable descriptions to
%                     use. The variable names to be optimized
%                     must remain the same. Numeric variables
%                     may have their Range, Type, and Transform
%                     changed, but must remain numeric.
%                     Categorical variables may not be changed.
%

%   Copyright 2016-2022 The MathWorks, Inc.

[varargin{:}] = convertStringsToChars(varargin{:});
C = bayesoptim.suppressWarnings();
if iAnyIllegalResumeArgs(varargin)
    bayesoptim.err('BadResumeArgs');
else
    % Parse out new variable descriptions
    [PassedVariableDescrips, RemainingArgs] = iParseVariableDescriptionsArg(varargin);
    if isempty(PassedVariableDescrips)
        VariableDescrips = Results.PrivOptions.VariableDescriptions;
    else
        checkVariableDescriptions(PassedVariableDescrips, Results.PrivOptions.VariableDescriptions);
        VariableDescrips = PassedVariableDescrips;
    end
    % Create NewOptions, install new variable descriptions
    NewOptions = Results.PrivOptions;
    NewOptions.isResuming = true;
    NewOptions.VariableDescriptions = VariableDescrips;
    % Overwrite stopping criteria with defaults
    NewOptions.MaxObjectiveEvaluations = bayesoptim.BayesoptOptions.DefaultMaxObjectiveEvaluations();
    NewOptions.MaxTime                 = bayesoptim.BayesoptOptions.DefaultMaxTime();
    % Overwrite any options with contents of passed NVPs
    NewOptions = fillFromNVPs(NewOptions, RemainingArgs);
    % Go back and correct stopping criteria. Interpret them
    % as increments to the current state.
    NewOptions.MaxObjectiveEvaluations = NewOptions.MaxObjectiveEvaluations + Results.NumObjectiveEvaluations;
    NewOptions.MaxTime                 = NewOptions.MaxTime + Results.TotalElapsedTime;
    % Check updated options as a whole
    NewOptions = validateAndFillDefaults(NewOptions);
    % Save current XTrace
    CurXTrace = Results.XTrace;
    % Install new options (this would change XTrace)
    Results.PrivOptions = NewOptions;
    % Update XTrain because variableDescriptions may have changed
    Results.XTrain = XTraceToXTrain(Results, CurXTrace);
    % Run the optimization using the new options
    Results = initializeParallel(Results);
    Results = initializeTimers(Results, true);
    Results = intializePrintObject(Results);
    NewResults = run(Results);
end
end

function tf = iAnyIllegalResumeArgs(NVPs)
Names = {'UseParallel', 'InitialX', 'InitialObjective', 'InitialConstraintViolations', ...
    'InitialErrorValues', 'InitialObjectiveEvaluationTimes', 'InitialIterationTimes', ...
    'InitialUserData', 'NumSeedPoints'};
Defaults = cell(1, numel(Names));
[~,~,~,~,~,~,~,~,~,setflag,~] = internal.stats.parseArgs(Names, Defaults, NVPs{:});
tf = bayesoptim.anyFlagsSet(setflag);
end

function [VariableDescriptions, RemainingArgs] = iParseVariableDescriptionsArg(Args)
[VariableDescriptions,~,RemainingArgs] = internal.stats.parseArgs(...
    {'VariableDescriptions'}, {[]}, Args{:});
end

function checkVariableDescriptions(NewVariableDescrips, OldVariableDescrips)
% The variable names to be optimized must be the same. Numeric variables
% may have their Range, Type, and Transform changed, but must remain
% numeric. Categoricals cannot be changed.
Old = sortVDByName(OldVariableDescrips([OldVariableDescrips.Optimize]));
New = sortVDByName(NewVariableDescrips([NewVariableDescrips.Optimize]));
if ~isequal({Old.Name}, {New.Name})
    bayesoptim.err('ResumeVarsChanged');
end
for i = 1:numel(Old)
    switch Old(i).Type
        case 'categorical'
            if ~isequal(New(i).Type, 'categorical') || ~isequal(Old(i).Range, New(i).Range)
                bayesoptim.err('ResumeCatsChanged');
            end
        case {'integer','real'}
            if ~ismember(New(i).Type, {'integer','real'})
                bayesoptim.err('ResumeNumericChanged');
            end
    end
end
end

function VD = sortVDByName(VD)
[~,I] = sort({VD.Name});
VD = VD(I);
end
