function obj = fitensemble(X,Y,method,nlearn,learners,varargin)
%FITENSEMBLE Fit an ensemble of learners.
%   ENS = FITENSEMBLE(TBL,Y,METHOD,NLEARN,LEARNERS)
%   ENS = FITENSEMBLE(X,Y,METHOD,NLEARN,LEARNERS)
%   ENS = FITENSEMBLE(___,'PARAM1',val1,'PARAM2',val2,...)
%
%   Limitations:
%   1) For data with categorical predictors, only "OVAByClass" is supported
%      for multiclass.
%   2) For data with categorical predictors, "NumVariablesToSample" must be
%      "all".
%   3) Only decision tree learners are supported.
%   4) If you use templateTree to create a learner template object, the
%      name-value arguments Surrogate and PredictorSelection must be "off"
%      and "allsplits", respectively.
%   5) You cannot specify the name-value argument Method as "Bag",
%      "LPBoost", "RobustBoost", "Subspace", or "TotalBoost".

%   Copyright 2021-2022 The MathWorks, Inc.

narginchk(5,inf);

if iDoClassification(method, varargin{:})
    [X,Y,varargin{:}] = validateInputsForClassificationFitFunction(X,Y,varargin);
else
    [X,Y,varargin{:}] = validateInputsForRegressionFitFunction(X,Y,varargin);
end
obj = parallel.internal.array.callToolboxFunction("stats/classreg/fitensemble", X, Y, method, nlearn, learners, varargin{:});
end

function isClassification = iDoClassification(method, varargin)
% Determine whether to do classification or regression

% Ensure method is valid before using it.
method = convertStringsToChars(method);
if ~ischar(method)
    throwAsCaller(MException(message("stats:fitensemble:MethodNameNotChar")));
end
if ~any(startsWith(classreg.learning.ensembleModels(), method, "IgnoreCase", true))
    throwAsCaller(MException(message("stats:fitensemble:BadMethod", method)));
end

% Obtain the matched method name for only for classification because it may be
% needed to throw an error with the method formatted correctly.
classificationMethods = classreg.learning.classificationModels();
[isClassification, methodIdx] = ismember(lower(method), lower(classificationMethods));
isRegression = contains(method, classreg.learning.regressionModels(), "IgnoreCase", true);
if ~isClassification && ~isRegression
    throwAsCaller(MException(message("stats:classreg:learning:FitTemplate:make:UnknownMethod", method)));
end
if isClassification && isRegression
    % Some methods (for example, bag) are supported for regression and
    % classification. For these methods, the 'Type' pv pair must be
    % specified to distinguish between classification and regression.
    args = {'type'};
    defs = {    ''};
    [varargin{:}] = convertStringsToChars(varargin{:});
    [userType,~,~] = internal.stats.parseArgs(args,defs,varargin{:});
    if ~ischar(userType)
        throwAsCaller(MException(message("stats:classreg:learning:FitTemplate:gettype:BadType")));
    end
    if isempty(userType)
        throwAsCaller(MException(message("stats:classreg:learning:FitTemplate:fit:BadType", classificationMethods{methodIdx})));
    end
    if startsWith("regression", userType, "IgnoreCase", true)
        isClassification = false;
        return
    elseif ~startsWith("classification", userType, "IgnoreCase", true)
        throwAsCaller(MException(message('stats:classreg:learning:FitTemplate:gettype:UnknownType')));
    end
end
end