function [G,GR,GC] = groupsummary(T,groupVars,varargin)
%MATLAB Code Generation Library Function

%   Limitations:
%       * All inputs except T must be constant
%       * ND arrays in tables are not supported
%       * Cell arrays of character vectors and cell arrays of cell arrays
%         are not supported
%       * GR, the second output, is always a cell array unless groupVars
%         are constant and there are 1 or fewer groups
%   Copyright 2021-2023 The MathWorks, Inc.
%#codegen

coder.internal.prefer_const(groupVars, varargin);
ONE = coder.internal.indexInt(1);

narginchk(2,Inf);
coder.internal.errorIf(iscellstr(T),'MATLAB:groupsummary:CodegenCellstrInput'); %#ok<ISCLSTR> 

% Compute matrix/table switch flag
tableFlag = isa(T,'tabular');

if tableFlag
    % Error if asking for more than 1 output for table
    nargoutchk(0,1);
end

% Parse grouping variables
[groupingData,groupVars] = parseGroupVars(groupVars,tableFlag,'groupsummary',T);

if tableFlag
    % Create labels for groupVars in output table
    gvLabels = groupVars;

    % Create label for the group count in output table
    gcLabel = {'GroupCount'};

    % Set default values for data variables
    varNamesT = T.Properties.VariableNames;
    defaultDataVars = varNamesT;
else
    gvLabels = numel(groupingData);
    % in-memory version uses cellstrs for gvLabels - those can't be
    % generated at runtime in generated code
    defaultDataVars = T;
    varNamesT = [];
end

if iscell(T)
    coder.internal.assert(coder.internal.isConst(numel(T)), 'MATLAB:groupsummary:CodegenVarsizeCell');
    numMethodInputTmp = numel(T);
else
    numMethodInputTmp = 1;
end

if tableFlag
    numLabels = numel(gvLabels);
else
    numLabels = gvLabels;
end

[groupBins, gbProvided, scalarExpandBins, scalarExpandVars, methods, ...
    methodPrefix, numMethods, dataVars, dvNotProvided, inclEmpty, inclNan, ...
    inclEdge, numMethodInput, dvSets]  = parseOptions(varargin, tableFlag, T, ...
    varNamesT, defaultDataVars, numLabels, numMethodInputTmp);

coder.internal.assert(coder.internal.isConst(inclEmpty), 'MATLAB:groupsummary:CodegenConstInclEmpty');
coder.internal.assert(coder.internal.isConst(inclNan), 'MATLAB:groupsummary:CodegenConstInclNan');
coder.internal.assert(coder.internal.isConst(inclEdge), 'MATLAB:groupsummary:CodegenConstInclEdge');

if gbProvided
    % Discretize grouping variables and remove repeated pairs of grouping
    % variables and group bins
    [groupingData_,groupVars_,gvLabels_] = discgroupvar(groupingData,...
        groupVars,gvLabels,groupBins,inclEdge,scalarExpandBins,...
        scalarExpandVars,'groupsummary',tableFlag);
    coder.assertDefined(groupingData_);
elseif tableFlag
    % Remove repeated grouping variables
    [groupVars_,ridx] = coder.const(@feval,'unique',groupVars,'stable');
    groupingData_ = {groupingData{ridx}};
    gvLabels_ = {gvLabels{ridx}};
else
    groupVars_ = groupVars;
    groupingData_ = groupingData;
    gvLabels_ = gvLabels;
end

% Compute final number of grouping variables from labels
if tableFlag
    numGroupVars = numel(gvLabels_);
else
    numGroupVars = gvLabels_;
end

% Compute grouping index and data
numrows = size(T,1);
if iscell(T) && ~iscellstr(T) && ~isempty(T) %#ok<ISCLSTR>
    numrows = size(T{1},1);
end
if tableFlag || (nargout == 3)
    [gvIdx_,numGroups,gData,gCount] = mgrp2idx(groupingData_,numrows,inclNan,inclEmpty);
elseif nargout == 2
    [gvIdx_,numGroups,gData] = mgrp2idx(groupingData_,numrows,inclNan,inclEmpty);
else
    [gvIdx_,numGroups] = mgrp2idx(groupingData_,numrows,inclNan,inclEmpty);
end

% Set group count correctly in case of 0xN input
if ~tableFlag && numrows == 0
    numGroups = 0;
    if (nargout == 3)
        gCount = zeros(0,1);
    end
end

% Extract data variables
if numMethods == 0
    if tableFlag
        % Quick return
        % Create output table
        if isempty(groupVars_)
            G = table.init({gCount},numel(gCount),{},1,gcLabel);
        else
            % Make sure labels are unique
            MAXNAMELENGTH = coder.const(feval('namelengthmax'));
            uniqueLabels = coder.const(feval('matlab.lang.makeUniqueStrings',{gvLabels_{:},gcLabel{:}},{'Row', 'Variables'},MAXNAMELENGTH));
            vars = {gData{:},gCount};
            G = table.init(vars,numel(gCount),{},numel(vars),uniqueLabels);
        end
        return;
    else
        % Error if no methods are specified in matrix case
        coder.internal.assert(false,'MATLAB:groupsummary:MustSpecifyMethod');
    end
else
    if numMethodInput > 1 % Only true when T is a table or cell array
        % Each column of dvData will have the data input sets
        if tableFlag
            numDataVars = numel(dataVars);
            dataLabels = dataVars;
            dvData = cell(size(dvSets));
            for k=1:numel(dvSets)
                dvData{k} = T.(dvSets(k));
            end
        else % iscell(T)
            numDVSets_ = coder.nullcopy(zeros(1,numel(T)));
            isHeterogeneous = ~coder.internal.isHomogeneousCell(T);
            if isHeterogeneous
                % Avoid trying to unroll a loop of unknown size
                % (heterogeneous cell arrays must have a fixed size)
                coder.unroll();
            end
            for ii = ONE:numel(T)
                numDVSets_(ii) = size(T{ii},2);
            end
        
            if isHeterogeneous
                numDVSets = coder.const(feval('unique',coder.const(numDVSets_)));
            else
                numDVSets = unique(numDVSets_);
            end

            coder.internal.errorIf(numel(numDVSets) > 2 || (numel(numDVSets) == 2 && numDVSets(1) ~= 1), ...
                'MATLAB:groupsummary:FirstInputSizeCell');
            numDataVars = numDVSets(end);

            % To avoid errors, create numbered labels
            dataLabels = cell(1,numDataVars);
            for ii = ONE:numel(dataLabels)
                dataLabels{ii} = ii;
            end

            % Copy columns over to cell array
            if numDataVars ~= 0
                dvData = cell(numMethodInput,numDataVars);
                coder.unroll();
                for k=1:numMethodInput
                    if size(T{k},2) == 1
                        for jj = 1:numDataVars
                            dvData{k,jj} = T{k}(:,1);
                        end
                    else
                        for jj = 1:size(T{k},2)
                            dvData{k,jj} = T{k}(:,jj);
                        end
                    end
                end
            else
                % Special case where cell contains nx0 matrices
                dvData = reshape(T,[],1);
                numDataVars = 1;
                dataLabels = {ONE};
            end
            coder.assertDefined(dvData); % required when numDVSets = 2 - coder can't tell that both loops over the second dim of dvData will fill it completely
        end
    else
        [dvData,dataLabels,numDataVars] = extractDataVars(T,groupVars_,dataVars,tableFlag,dvNotProvided,true);
        coder.assertDefined(dvData);
    end
end

% Compute group summary computations
gStats = cell(1,numMethods*numDataVars);
gStatsLabel = cell(1,numMethods*numDataVars);
% Remove NaNs from gvIdx, which represent missing groups when we don't include them
[gvIdx, idx] = removeNaNs(gvIdx_);

if ~isempty(gvIdx)
    % Sort the groups and get index.
    [gvIdxSorted,sortOrd] = sort(gvIdx);
    % Find the first and last element for each group
    dGvIdxSorted = [1;diff(gvIdxSorted)>0];
    coder.varsize('grpStart',[inf,1],[1 0]);
    grpStart = 1;
    for ii = coder.internal.indexInt(2):numel(dGvIdxSorted)
        if dGvIdxSorted(ii) ~= 0
            grpStart = [grpStart; cast(ii,'like',grpStart)]; %#ok<AGROW> 
        end
    end

    % Branch to avoid runtime indexing with scalar vs indexing with vector issues
    if isscalar(grpStart)
        grpEnd = length(gvIdxSorted);
    else
        grpEnd = [grpStart(2:end)-1;length(gvIdxSorted)];
    end
    outGrpIdx = gvIdxSorted(grpStart);
else
    % These values will not be used, but it's simpler to pass values to
    % applyFunWrapper and then ignore them
    sortOrd = [];
    grpStart = [];
    grpEnd = [];
    outGrpIdx = [];
end

for ii = ONE:numDataVars
    % Extract set of data to be passed to method. x will be a cell,
    % where each cell is the data for each input to the method

    % When not including missing groups, need to remove data associated with those groups
    x = cell(1,numMethodInput);
    if coder.internal.isConst(numMethodInput)
        coder.unroll();
    end
    for k = 1:numMethodInput
        if ~isempty(dvData{k,ii})            
            x{k} = dvData{k,ii}(idx,:);
        else
            x{k} = dvData{k,ii};
        end
    end

    coder.unroll()
    for jj = 1:numMethods
        % Error for median of char
        methodIsMedian = coder.const(feval('strcmp',methodPrefix{jj},'median'));
        coder.internal.errorIf(methodIsMedian && ischar(dvData{ii}), 'MATLAB:groupsummary:MedianMethodWithChar')

        % The in-memory version wraps the following code in a try-catch.
        % The errors thrown here will not match.
        f = methods{jj};

        % If not grouping by anything then apply method directly
        d = applyFunWrapper(x,gvIdx,f,numGroups,inclEmpty,methodPrefix{jj},grpStart,grpEnd,outGrpIdx,sortOrd,numMethodInput,tableFlag,dataLabels{ii});

        gStats{(ii-1)*numMethods + jj} = d;
        gStatsLabel{(ii-1)*numMethods + jj} = [methodPrefix{jj} '_' dataLabels{ii}];

        % Check to make sure vertcat output the correct number of elements
        if tableFlag
            coder.internal.errorIf(~isequal(size(d,1),numGroups), ...
                'MATLAB:groupsummary:ApplyDataVarsError', ...
                methodPrefix{jj},dataLabels{ii});
        else
            coder.internal.errorIf(~isequal(size(d,1),numGroups), ...
                'MATLAB:groupsummary:ApplyDataVecsError', ...
                methodPrefix{jj},dataLabels{ii});
        end
    end
end
coder.assertDefined(gStats);
coder.assertDefined(gStatsLabel);

if tableFlag
    % Make sure all labels are unique
    gvLabels_ = coder.const(gvLabels_);
    gcLabel = coder.const(gcLabel);
    gStatsLabel = coder.const(gStatsLabel);

    concatenatedLabelsTmp = coder.const(feval('cat',2,gvLabels_,gcLabel));
    concatenatedLabels = coder.const(feval('cat',2,concatenatedLabelsTmp,gStatsLabel));
    % namelengthmax is not supported for codegen
    NAMELENGTHMAX = coder.const(feval('namelengthmax'));
    uniqueLabels = coder.const(feval('matlab.lang.makeUniqueStrings',concatenatedLabels,{'Row', 'Variables'},NAMELENGTHMAX));
    gvLabelsOut = {uniqueLabels{1:numGroupVars}};
    gcLabelOut = {uniqueLabels{numGroupVars+1}};
    gStatsLabelOut = {uniqueLabels{numGroupVars+2:end}};
    
    % Assemble results
    if numGroupVars == 0
        vars = {gCount,gStats{:}};
        G = table.init(vars,numel(gCount),{},numel(vars),{gcLabelOut{:}, gStatsLabelOut{:}});
    else
        vars = {gData{:},gCount,gStats{:}};
        G = table.init(vars,numel(gCount),{},numel(vars),{gvLabelsOut{:}, gcLabelOut{:}, gStatsLabelOut{:}});
    end

else       
    % Try to extract G from data
    % Errors thrown here may differ from the in-memory version
    G = [gStats{:}];
    
    % If requested also return groups
    if nargout > 1
        % Set groups
        if coder.internal.isConstTrue(numGroupVars == 1)
            GR = gData{1};
        elseif coder.internal.isConstTrue(numGroupVars == 0)
            groupVars_Eg = coder.internal.scalarEg(groupVars_);
            if size(T,1) == 0 || (iscell(T) && ~isempty(T) && size(T{1},1) == 0)
                % groupVars_ must be 0x0 here because for 0xn numGroupVars is n
                GR = constructEmpty(groupVars_Eg,0,0);
            else
                if size(groupVars_,1) == 0
                    GR = constructEmpty(groupVars_Eg,1,0);
                else
                    GR = groupVars_(1,:);
                end
            end
        else
            GR = gData;
        end
    end
    
    % If requested return group counts also
    if nargout > 2
        % Set group counts
        GC = gCount;
    end
end

end
%--------------------------------------------------------------------------

function [groupBins, gbProvided, scalarExpandBins, scalarExpandVars, methods, ...
    methodPrefix, numMethods, dataVars, dvNotProvided, inclEmpty, inclNan, ...
    inclEdge, numMethodInput, dvSets] = parseOptions(inputs, tableFlag, T, ...
    varNamesT, defaultDataVars, numLabels, numMethodInputTmp)
coder.internal.prefer_const(inputs, tableFlag, varNamesT, defaultDataVars);
% Loop through inputs
ONE = coder.internal.indexInt(1);
checkMethod = true;
checkDataVars = false;
checkGroupbins = true;
dataVarsNotDefined = true;
groupbinsNotDefined = true;
numMethodInputNotDefined = true;
dvSetsNotDefined = true;

optionWasParsed = false;

coder.unroll()
for ind = ONE:numel(inputs)
    % currentOption = coder.const(inputs{ind});
    currentOption = inputs{ind};
    optionWasParsed = false;
    if checkGroupbins && isgroupbins(currentOption,'groupsummary')
        [groupBins,scalarExpandBins,scalarExpandVars,areValidGroupbins] = ...
            parsegroupbins(currentOption,numLabels,'groupsummary');
        checkGroupbins = false; % The only way that areValidGroupbins can be false is if currentOption is a char array but not a group bin option. This should skip the rest of the checks
        if areValidGroupbins
            % Only move on to the next input if current option is a valid
            % groupbin
            optionWasParsed = true;
            groupbinsNotDefined = false;
            gbProvided = true;
        end
    end
    if ~optionWasParsed && checkMethod && isgroupmethod(currentOption)
        [methods,methodPrefix,numMethods] = parseMethods(currentOption);
        checkMethod = false;
        checkDataVars = true;
        checkGroupbins = false;
        optionWasParsed = true;
    end
    if ~optionWasParsed && tableFlag && checkDataVars && isDataVars(currentOption,numel(inputs),ind)
        dataVars = parseDataVars(T,currentOption,varNamesT);
        optionWasParsed = true;
        dataVarsNotDefined = false;
        dvNotProvided = false;
        % dvNotProvided is used in addition to dataVarsNotDefined because
        % dvNotProvided has to be const and dataVarsNotDefined has its
        % value changed during this loop, so it can't be const
        break % No positional options after dataVars
    end
    if ~optionWasParsed && tableFlag && checkDataVars && iscell(currentOption) && ~iscellstr(currentOption) %#ok<ISCLSTR>
        dataVarsTmp = currentOption;
        numMethodInput = numel(dataVarsTmp);
        % dataVars will be concatenated names of input sets
        % to be passed to function handle
        % dvSets is a numeric matrix where the columns
        % represent the indices of input data sets
        [dataVars,dvSets] = checkMultDataVariables(T,dataVarsTmp,varNamesT,numMethodInput);
        dataVars=coder.const(dataVars);
        optionWasParsed = true;
        dataVarsNotDefined = false;
        dvNotProvided = false;
        numMethodInputNotDefined = false;
        dvSetsNotDefined = false;
        break % No positional options after dataVars
    end
    if ~optionWasParsed
        break
    end
end

if groupbinsNotDefined
    groupBins = {};
    scalarExpandBins = false;
    scalarExpandVars = false;
    gbProvided = false;
end
if checkMethod
    % Only true if methods have not been defined
    methods = [];
    methodPrefix = [];
    numMethods = 0;
end
if dataVarsNotDefined
    dataVars = defaultDataVars;
    dvNotProvided = true;
end
if numMethodInputNotDefined
    numMethodInput = coder.const(numMethodInputTmp);
end
if dvSetsNotDefined
    dvSets = {};
end

if optionWasParsed
    ind = ind + ONE;
end

% Empty inputs still get passed to parseNameValuePairs for default values
if isempty(ind)
    % This happens when inputs is empty
    ind_ = ones('like',ind); % Avoid type issues when ind is empty
else
    ind_ = ind;
end
% Empty check required for case when numel(inputs) == 0
[inclEmpty, inclNan, inclEdge, inputs] = parseNameValuePairs(inputs, ind_, gbProvided); %#ok<ASGLU>
end

%--------------------------------------------------------------------------
function tf = isDataVars(in,numInputs,ind)
coder.internal.prefer_const(in,numInputs);
% Char, cellstr, string scalar, numeric vector, logical vector
% Error for function handle
% Check chars and string scalars for valid names
coder.internal.errorIf(isa(in,'function_handle'),'MATLAB:groupsummary:CodegenFuncHandleDataVars');
if ischar(in) || isstring(in)
    % Check that in isn't the start of a N-V pair
    % in should have already been checked for const-ness
    if rem(numInputs - ind,2) == 1
        % Only check for possible parameter names if there are an even
        % number of inputs left
        coder.internal.assert(coder.internal.isConst(in),'MATLAB:groupsummary:CodegenConstDataVars');
        matchesName = coder.const(feval('strncmpi',in,{'IncludeMissingGroups','IncludeEmptyGroups','IncludedEdge'},8));
        tf = ~coder.const(feval('any',matchesName));
    else
        tf = true;
    end
elseif iscellstr(in) || islogical(in) || isnumeric(in)
    tf = true;
    return;
else
    tf = false;
end
end
%--------------------------------------------------------------------------
function dataVars = parseDataVars(T,in,varNamesT)
    % The only allowable formats for dataVars are char arrays, string
    % scalars, cellstrs, logical vectors, and numeric vectors
    coder.internal.prefer_const(in,varNamesT);
    coder.internal.assert(coder.internal.isConst(in),'MATLAB:groupsummary:CodegenConstDataVars');
    dataVars_ = checkDataVariables(T, in, 'groupsummary', 'Data');
    dataVars = coder.const(feval('unique',{varNamesT{dataVars_}},'stable'));
end

%--------------------------------------------------------------------------
function [inclEmpty, inclNan, inclEdge, inputs] = parseNameValuePairs(inputs, ind, gbProvided)
    if ind <= numel(inputs)
        % Parse name-value pairs
        % Error checking is done by parseParameterInputs - error IDs will not match the in-memory version
        potentialNVPairs = {inputs{ind:end}};
        parms = {'IncludeEmptyGroups', 'IncludeMissingGroups', 'IncludedEdge'};
        poptions = struct( ...
            'CaseSensitivity',false, ...
            'PartialMatching','unique', ...
            'StructExpand',false, ...
            'IgnoreNulls',true);
        pstruct = coder.internal.parseParameterInputs(parms,poptions,potentialNVPairs{:});
        inclEmptyTmp = coder.internal.getParameterValue(pstruct.IncludeEmptyGroups,false,potentialNVPairs{:});
        coder.internal.assert(isscalar(inclEmptyTmp) && (islogical(inclEmptyTmp) || isnumeric(inclEmptyTmp)), ...
            'MATLAB:table:InvalidLogicalVal', 'IncludeEmptyGroups');
        inclEmpty = logical(inclEmptyTmp);

        inclNanTmp = coder.internal.getParameterValue(pstruct.IncludeMissingGroups,true,potentialNVPairs{:});
        coder.internal.assert(isscalar(inclNanTmp) && (islogical(inclNanTmp) || isnumeric(inclNanTmp)), ...
            'MATLAB:table:InvalidLogicalVal', 'IncludeMissingGroups');
        inclNan = logical(inclNanTmp);

        if pstruct.IncludedEdge ~= 0
            coder.internal.assert(gbProvided, 'MATLAB:groupsummary:IncludedEdgeNoGroupBins');
            inclEdge = coder.internal.getParameterValue(pstruct.IncludedEdge,'left',potentialNVPairs{:});
        else
            inclEdge = 'left';
        end
    else
        inclEmpty = false;
        inclNan = true;
        inclEdge = 'left';
    end
end

%--------------------------------------------------------------------------
function [methodsOut,methodPrefixOut,numMethodsOut] = parseMethods(methods_)
%PARSEMETHODS Assembles methods into a cell array of function handles
%   This function checks and replaces all with list of methods then changes
%   values into function handles and computes correct prefixes for the
%   methods given

coder.internal.prefer_const(methods_);
coder.internal.assert(coder.internal.isConst(methods_),'MATLAB:groupsummary:CodegenConstMethod');

ONE = coder.internal.indexInt(1);

if isstring(methods_)
    methodsTmp = {char(methods_)};
elseif ~iscell(methods_)
    methodsTmp = {methods_};
else
    methodsTmp = methods_;
end

numMethodsIn = coder.const(coder.internal.indexInt(numel(methodsTmp)));

% Check for all option
tmpLables = cell(1,numMethodsIn);
for ii = ONE:numMethodsIn
    if ischar(methodsTmp{ii})
        coder.internal.assert(isrow(methodsTmp{ii}),'MATLAB:groupsummary:InvalidMethodOption');
        tmpLables{ii} = methodsTmp{ii};
    else
        tmpLables{ii} = 'fun'; % This could be any character vector except 'all'
    end
end
tmpLables = coder.const(tmpLables);

IS_ALL = coder.const(feval('matlab.internal.math.checkInputName','all',tmpLables));
ALL_IDX = coder.const(feval('find',IS_ALL));
numSupportedMethods = 12;
if isempty(ALL_IDX)
    % 'all' is not one of the specified methods
    methodsComplete = methodsTmp;
    numMethodsComplete = numMethodsIn;
else
    % Replace the first 'all' with all the named methods, remove duplicate
    % alls
    FIRST_ALL = ALL_IDX(1);
    numMethodsComplete = numel(methodsTmp) + numSupportedMethods - numel(ALL_IDX);
    methodsComplete = cell(1,numMethodsComplete);
    indMethodsComplete = ONE;
    coder.unroll();
    for indMethodsTmp = ONE:numMethodsIn
        if indMethodsTmp == FIRST_ALL
            methodsComplete{indMethodsComplete}      = 'mean';
            methodsComplete{indMethodsComplete + 1}  = 'sum';
            methodsComplete{indMethodsComplete + 2}  = 'min';
            methodsComplete{indMethodsComplete + 3}  = 'max';
            methodsComplete{indMethodsComplete + 4}  = 'range';
            methodsComplete{indMethodsComplete + 5}  = 'median';
            methodsComplete{indMethodsComplete + 6}  = 'mode';
            methodsComplete{indMethodsComplete + 7}  = 'var';
            methodsComplete{indMethodsComplete + 8}  = 'std';
            methodsComplete{indMethodsComplete + 9}  = 'nummissing';
            methodsComplete{indMethodsComplete + 10} = 'nnz';
            methodsComplete{indMethodsComplete + 11} = 'numunique';
            indMethodsComplete = indMethodsComplete + numSupportedMethods;
        elseif ~IS_ALL(indMethodsTmp)
            methodsComplete{indMethodsComplete} = methodsTmp{indMethodsTmp};
            indMethodsComplete = indMethodsComplete + 1;
        end
    end
end

% Change each option to a function handle and set names appropriately
methodPrefix = cell(1,numMethodsComplete);
methodsFuncHandle = cell(1,numMethodsComplete);
validMethods = {'nummissing','nnz','mean','median','mode','var','std','min' ...
    'max', 'range', 'sum', 'numunique'};
groupbinsOptions = {'second','minute','hour','day','week','month','quarter', ...
    'year','decade','century','secondofminute','minuteofhour','hourofday','dayname', ...
    'dayofweek','dayofmonth','dayofyear','weekofmonth','weekofyear','monthofyear', ...
    'monthname','quarterofyear','none'};
numfun = 1;
coder.unroll();
for jj = ONE:numel(methodPrefix)
    if ischar(methodsComplete{jj})
        currentMethod = coder.const(methodsComplete{jj});
        matchesMethod = coder.const(feval('matlab.internal.math.checkInputName', ...
            currentMethod,validMethods));
        numMatches = coder.const(feval('sum',matchesMethod));
        coder.internal.assert(numMatches == 1, 'MATLAB:groupsummary:InvalidMethodOption');
        % Make sure there are no ambiguous matches with groupbins
        matchesGroupbin = coder.const(feval('matlab.internal.math.checkInputName', ...
            currentMethod,groupbinsOptions));
        anyMatchesGroupbin = coder.const(feval('any',matchesGroupbin));
        coder.internal.assert(~anyMatchesGroupbin || matches(currentMethod,'min'), 'MATLAB:groupsummary:InvalidMethodOption');
        currentMatch = coder.const(feval('find',matchesMethod));
        switch currentMatch
            case 1 % nummissing
                methodsFuncHandle{jj} = @(x) sum(ismissing(x),1);
                methodPrefix{jj} = 'nummissing';
            case 2 % nnz
                methodsFuncHandle{jj} = @(x) nnz(x(~ismissing(x)));
                methodPrefix{jj} = 'nnz';
            case 3 % mean
                methodsFuncHandle{jj} = @(x) mean(x,1,'omitnan');
                methodPrefix{jj} = 'mean';
            case 4 % median
                methodsFuncHandle{jj} = @(x) median(x,1,'omitnan');
                methodPrefix{jj} = 'median';
            case 5 % mode
                methodsFuncHandle{jj} = @(x) mode(x,1);
                methodPrefix{jj} = 'mode';
            case 6 % var
                methodsFuncHandle{jj} = @(x) var(x,0,1,'omitnan');
                methodPrefix{jj} = 'var';
            case 7 % std
                methodsFuncHandle{jj} = @(x) std(x,0,1,'omitnan');
                methodPrefix{jj} = 'std';
            case 8 % min
                methodsFuncHandle{jj} = @(x) min(x,[],1,'omitnan');
                methodPrefix{jj} = 'min';
            case 9 % max
                methodsFuncHandle{jj} = @(x) max(x,[],1,'omitnan');
                methodPrefix{jj} = 'max';
            case 10 % range
                methodsFuncHandle{jj} = @(x) unsignedRange(x);
                methodPrefix{jj} = 'range';
            case 11 % sum
                methodsFuncHandle{jj} = @(x) sum(x,1,'omitnan');
                methodPrefix{jj} = 'sum';
            case 12 % numunique
                methodsFuncHandle{jj} = @(x) numel(unique(x(~ismissing(x))));
                methodPrefix{jj} = 'numunique';
        end
    else
        methodsFuncHandle{jj} = methodsComplete{jj};
        coder.internal.assert(isa(methodsFuncHandle{jj},'function_handle'),'MATLAB:groupsummary:InvalidMethodOption');
        methodPrefix{jj} = ['fun' num2str(numfun)];
        numfun = numfun + 1;
    end
end

% Coder requires this explicit 'cast to const' to recognize that these cell
% arrays are const
methodsFuncHandle = coder.const(methodsFuncHandle);
methodPrefix = coder.const(methodPrefix);

% Remove repeated method names
if ~isequal(numMethodsComplete,1)
    [methodPrefixOut,idx] = coder.const(@feval,'unique',methodPrefix,'stable');
    methodsOut = {methodsFuncHandle{idx}};
    numMethodsOut = numel(methodsOut);
else
    methodPrefixOut = methodPrefix;
    methodsOut = methodsFuncHandle;
    numMethodsOut = numel(methodsOut);
end
end

%--------------------------------------------------------------------------
function R = unsignedRange(x)
% Compute the range and if the input is a signed integer then return range 
% as an unsigned integer value.
ONE = coder.internal.indexInt(1);

xClass = coder.const(class(x));
if isa(x, 'integer') && strncmp('i',xClass,1)
    outputClass = ['u', xClass];
else
    outputClass = xClass;
end

if isempty(x)
    if size(x,1) == 0
        R = zeros(1,0,outputClass);
    else
        R = zeros(size(x),outputClass);
    end
    return
end

a = x(1,:);
b = x(1,:);

for jj = ONE:size(x,2)
    for ii = coder.internal.indexInt(2):size(x,1)
        if (x(ii,jj) > a(jj)) || (isnan(a(jj)) && ~isnan(x(ii,jj)))
            a(jj) = x(ii,jj);
        elseif (x(ii,jj) < b(jj)) || (isnan(b(jj)) && ~isnan(x(ii,jj)))
            % a value can only be simultaneously the max and the min if
            % there is only one row - that case skips this loop
            b(jj) = x(ii,jj);
        end
    end
end

if isa(x, 'integer') && strncmp('i',xClass,1)
    if sign(a) == sign(b)
        R = cast(a - b, outputClass);
    else % a is always greater than b, so a > 0, b < 0
        R = cast(a, outputClass) + cast(-b, outputClass);
        if b == intmin(xClass)
            R = R + 1;
        end
    end
else
    R = a - b;
end

end

%--------------------------------------------------------------------------
function [out, isNonNaN] = removeNaNs(in)
ONE = coder.internal.indexInt(1);
numelIn = coder.internal.indexInt(numel(in));
isNonNaN = coder.nullcopy(false(1,numelIn));
numNonNaN = coder.internal.indexInt(0);
for ii = ONE:numelIn
    if isnan(in(ii))
        isNonNaN(ii) = false;
    else
        isNonNaN(ii) = true;
        numNonNaN = numNonNaN + ONE;
    end
end
out = coder.nullcopy(zeros(numNonNaN,1,'like',in));
count = ONE;
for ii = ONE:numelIn
    if isNonNaN(ii)
        out(count) = in(ii);
        count = count + ONE;
    end
end
end

%--------------------------------------------------------------------------
function a = applyFunByGroup(grpStart,grpEnd,outGrpIdx,sortOrd,x_,numGroups,fun,fillval_,numMethodInput,tableFlag,methodPrefix,dataLabel)
% APPLYFUNBYGROUP Apply function to data using sorted groups

ONE = coder.internal.indexInt(1);
coder.internal.prefer_const(numGroups,fun,fillval_,numMethodInput,tableFlag,methodPrefix,dataLabel);

% Apply the function to the first group to get the type/size of the output
% to preallocate
if numMethodInput == 1
    multMethodInputs = false;
    x = x_{:};
else
    multMethodInputs = true;
    gvals = cell(1,numMethodInput);
    x = x_;
end

grpSz = grpEnd(1) - grpStart(1) + 1;
if multMethodInputs
    coder.unroll();
    for k = ONE:numMethodInput % A for loop is faster than using cellfun
        gvals{k} = x{k}(sortOrd(grpStart(1):grpEnd(1)),:); % ND arrays in tables are not supported
    end
    d = fun(gvals{:});
else
    szXiter = size(x,2); % ND matrices are not supported
    gvals = reshape(x(sortOrd(grpStart(1):grpEnd(1)),:), [grpSz, szXiter]);
    d = fun(gvals);
end

coder.internal.errorIf(iscell(d),'MATLAB:groupsummary:CodegenMethodCantReturnCell');
coder.internal.assert(ismatrix(d),'MATLAB:groupsummary:CodegenMethodCantReturnND');
coder.internal.errorIf(issparse(d),'MATLAB:groupsummary:CodegenMethodCantReturnSparse');

% Check to make sure method returned one element per group
% These error messages don't match the in-memory version, but that's ok
% because the in-memory calls this in a try-catch that ultimately throws
% these
if tableFlag
    coder.internal.errorIf(size(d,1) > 1, 'MATLAB:groupsummary:ApplyDataVarsError',methodPrefix,dataLabel);
else
    coder.internal.errorIf(~isempty(d) && ~isscalar(d), 'MATLAB:groupsummary:ApplyDataVecsError',methodPrefix,dataLabel);
end

sz = [numGroups, size(d,2)]; % ND arrays not supported
if isempty(fillval_)
    a = repmat(d,[numGroups 1]);
else
    fillvalSameSize = size(fillval_,1) == size(d,1) && size(fillval_,2) == size(d,2); % ND arrays not supported
    if ~(isscalar(fillval_) || fillvalSameSize)
        % fillval can't be <missing> when it's passed to this function so
        % this branch is the only place where it might become <missing>
        constOfTypeD = coder.internal.scalarEg(d);
        emptyD = constructEmpty(constOfTypeD,0,0);
        % Since <missing> isn't supported by coder, construct it
        % extrinsically and pass it to horzcat (also extrinsic). This
        % should let us calculate temp at compile time.
        temp = coder.const(feval('horzcat',emptyD,coder.const(feval('missing'))));
        fillval = temp(1);

        fillvalSameSize = false;
    else
        fillval = fillval_;
    end
    if fillvalSameSize
        % If the fillval is the same size as d, just expand first dimension
        a = repmat(fillval,[numGroups 1]);
    else
        % If fillval is a scalar expand in all dimensions to get output the
        % right size
        a = repmat(fillval,sz);
    end
    a(outGrpIdx(1),:) = d;
end
szD = size(d);

% Apply function to remaining groups
for g = coder.internal.indexInt(2):length(grpStart)
    grpSz = grpEnd(g) - grpStart(g) + 1;
    if multMethodInputs
        for k = 1:numMethodInput
            % Using ind here is faster than using reshape
            gvals{k} = x{k}(sortOrd(grpStart(g):grpEnd(g)),:); % ND arrays in tables are not supported
        end
        d = fun(gvals{:});
    else
        gvals = reshape(x(sortOrd(grpStart(g):grpEnd(g)),:), [grpSz, szXiter]);
        d = fun(gvals);
    end
    coder.internal.assert(ismatrix(d),'MATLAB:groupsummary:CodegenMethodCantReturnND');
    coder.internal.errorIf(issparse(d),'MATLAB:groupsummary:CodegenMethodCantReturnSparse');

    % Check to make sure method returned one element per group
    if tableFlag
        coder.internal.assert(isequal(size(d),szD),'MATLAB:groupsummary:ApplyDataVarsError',methodPrefix,dataLabel);
    else
        coder.internal.assert(isequal(size(d),szD),'MATLAB:groupsummary:ApplyDataVecsError',methodPrefix,dataLabel);
    end
    a(outGrpIdx(g),:) = d;
end
end

%--------------------------------------------------------------------------
function d = applyFunWrapper(x,gvIdx,f,numGroups,inclEmpty,currentMethodPrefix,grpStart,grpEnd,outGrpIdx,sortOrd,numMethodInput,tableFlag,dataLabel)
ONE = coder.internal.indexInt(1);
coder.varsize('d_',[inf inf],[1 1]); % This is needed in addition to the resizing 
% in the isempty branch to ensure that one variable sized version of d_ is
% created, rather than multiple temporary versions with different sizes.
if isempty(gvIdx)
    dTmp = f(x{:});
    % Most checks on the type and attributes of dTmp are omitted here because the non-empty gvIdx will be generated unless
    % the data is guaranteed to be empty (i.e. its size is compile-time constant and it is empty). A check for cells has to
    % be included because without it there would be a less helpful error message even for empty inputs.
    coder.internal.errorIf(iscell(dTmp),'MATLAB:groupsummary:CodegenMethodCantReturnCell');
    % The following is a workaround to force d_ to be varsized
    % (The varsize command is ignored by datetimes)
    d_ = [dTmp dTmp]; %#ok<NASGU> 
    d_ = dTmp;

    if tableFlag
        coder.internal.errorIf(size(d_,1) > 1, 'MATLAB:groupsummary:InvalidMethodOutputTable');
    else
        coder.internal.errorIf(~isempty(d_) && ~isscalar(d_), 'MATLAB:groupsummary:InvalidMethodOutputArray');
    end

    anyXIsEmpty = false;
    coder.unroll();
    for kk = ONE:numel(x)
        if isempty(x{kk})
            anyXIsEmpty = true;
            break
        end
    end
    if anyXIsEmpty
        c = coder.internal.scalarEg(d_);
        if (numGroups == 0)
            d__ = constructEmpty(c,size(x{1},1),size(x{1},2));
        elseif inclEmpty && isempty(d_)
            d__ = coder.const(constructEmpty(c,1,0));
        else
            d__ = d_;
        end
    else
        d__ = d_;
    end
    % Expand d to make sure we have the right number of groups
    % (which will all be empty in this case)
    d = repmat(d__,numGroups,1);
else
    % Otherwise apply function by group
    if inclEmpty
        fillval = groupGetFillVal(x,f,currentMethodPrefix);
    else
        fillval = [];
    end
    d = applyFunByGroup(grpStart,grpEnd,outGrpIdx,sortOrd,x,numGroups,f,fillval,numMethodInput,tableFlag,currentMethodPrefix,dataLabel);
end
end

% LocalWords:  cellstrs gv Varsize lang nx Ns grp Vecs xn Groupbins groupbin Func PARSEMETHODS alls
% LocalWords:  nummissing numunique secondofminute minuteofhour hourofday dayname dayofweek
% LocalWords:  dayofmonth dayofyear weekofmonth weekofyear monthofyear monthname quarterofyear
% LocalWords:  groupbins omitnan APPLYFUNBYGROUP fillval varsized varsize datetimes
