function inputs = validateInputsForFitlmFitglm(inputs, dataPvPairNames, paramNames)
%VALIDATEINPUTSFORFITLMFITGLM Validates inputs for fitlm and fitglm
%   This function inspects the arguments for fitlm or fitglm to decide
%   whether the fit should be performed on the GPU and then manipulates the
%   inputs to ensure the functions produce consistent results. Specifically
%   it ensures that if a fit is performed on the GPU, a given model
%   property will always be either "in memory" or a gpuArray.
%
%   The fit will be performed on the GPU if any "data input" is specified
%   as a gpuArray. The data inputs are defined as follows:
%      - If the first argument is a table, then all variables in the table
%        are data inputs.
%      - If the first argument is a matrix, then the first two arguments
%        are data inputs (predictor matrix and response vector).
%      - Any PV pair value corresponding to a PV pair name in
%        dataPvPairNames
%
%   If the fit is to be performed on the GPU, then all numeric or logical
%   data inputs are converted to gpuArrays.
%
%   Any gpuArray PV pair values other than those specified in
%   dataPvPairNames will be gathered into regular arrays, regardless of
%   whether the fit is to be performed on the GPU or not.

%   Copyright 2020-2023 The MathWorks, Inc.

X = inputs{1};
trailingArgs = inputs(2:end);
[trailingArgs{:}] = convertStringsToChars(trailingArgs{:});

% X should never be a dataset because dataset is not an inferior class to
% gpuArray.
assert(~isa(X,"dataset"), "Input should not be dataset.");

% Cell arrays used to hold modelSpec and Y (if specified).  We store these
% in a cell array so that we don't have to track whether they were
% specified or not before including in leadingArgs.
YCell = {};
modelSpecCell = {};

xContainsGpuArray = isgpuarray(X) || (istable(X) && any(varfun(@isgpuarray, X, 'OutputFormat', "uniform")));

if istable(X) 
    % Determine where the start of the NV args is
    if numel(trailingArgs) > 0
        yIsResponse = classreg.regr.TermsRegression.isValidResponse(trailingArgs{1}, size(X));
        if yIsResponse || classreg.regr.TermsRegression.isYAResponseName(X, trailingArgs{1}, trailingArgs(2:end), paramNames)
            [X, trailingArgs] = classreg.regr.TermsRegression.parseResponseOrResponseNameY(X, trailingArgs{1}, trailingArgs(2:end), yIsResponse);
        end
    end
    [modeldef, trailingArgs] = classreg.regr.TermsRegression.parseModelDef(trailingArgs, paramNames, 'linear');
    if ~isempty(modeldef)
        modelSpecCell = {gather(modeldef)};
    end
else
    if isempty(trailingArgs)
        error(message('stats:classreg:regr:TermsRegression:MissingY'))
    end
    YCell = trailingArgs(1);
    trailingArgs = trailingArgs(2:end);
    
    if mod(length(trailingArgs),2) == 1 % Odd
        % trailingArgs is assumed to be modelSpec, followed by PV pairs.
        modelSpecCell = {gather(trailingArgs{1})};
        trailingArgs = trailingArgs(2:end);
    end
end

shouldUseGpu = xContainsGpuArray || isgpuarray(YCell{:}) || isAnySpecifiedPvPairValueAGpuArray(dataPvPairNames, trailingArgs{:});

if shouldUseGpu
    X = sendToDeviceIfPossible(X);
    if ~isempty(YCell)
        YCell = {sendToDeviceIfPossible(YCell{:})};
    end
    trailingArgs = ensureOnlySpecifiedPVPairsOnDevice(dataPvPairNames, trailingArgs{:});
else
    if ~isempty(trailingArgs)
        [trailingArgs{:}] = gather(trailingArgs{:});
    end
end

% If modelSpec was not specified, leadingArgs will reduce to {X, Y}
% If Y was not specified, leadingArgs will reduce to {X, modelSpec}
% If modelSpec and Y were not specified, leadingArgs will reduce to {X}
leadingArgs = [{X}, YCell(:)', modelSpecCell(:)'];
inputs = [leadingArgs, trailingArgs];
end