function [gidx,ng,gdata] = grp2idx(groupIn,inclnan,inclempty)
%MATLAB Code Generation Library Function

% GRP2IDX  Create index vector from a grouping variable.
%
%   [GIDX,NG,GDATA] = GRP2IDX(GROUP,INCLNAN,INCLEMPTY) creates an index  
%   vector from the grouping variable GROUP. 
%
%   Inputs:
%       GROUP is a single vector variable. It can be a categorical,
%       numeric, logical, datetime or duration vector; a cell vector of 
%       strings; or a character matrix with each row representing a group 
%       label. 
%
%       INCLNAN is a true/false flag about whether to include NaNs present 
%       in the group.
%
%       INCLEMPTY is a true/false flag about whether to include empty 
%       categorical or logical entries when counting GIDX and in GDATA
%
%   Outputs:
%       GIDX is a vector taking integer values from 1 up to the number NG  
%       of distinct groups or NaN for excluded values. 
%
%       NG is an integer containing the number of groups
%
%       GDATA is a vector of the same type as GROUP containing the unique
%       values within GROUP
%
%   Limitations:
%       This only supports things that can flow through codegen unique. Specifically,
%       this excludes all cell arrays.

%   Copyright 2021-2023 The MathWorks, Inc.
%#codegen

coder.internal.prefer_const(inclnan,inclempty);
ONE = coder.internal.indexInt(1);

% If group is char matrix or row vector throw nice error pointing to 
% cellstrs
coder.internal.errorIf(ischar(groupIn) && coder.internal.isConstTrue(size(groupIn,2) > 1), ...
    'MATLAB:findgroups:CharData');

% If group not vector error
coder.internal.errorIf(~isvector(groupIn), 'MATLAB:findgroups:GroupingVarNotVector');

% If we have row input move to column for easier processing
if coder.internal.isConstTrue(isrow(groupIn)) && ~isa(groupIn,'tabular')
    if iscell(groupIn)
        group = {groupIn{:}};
    else
        group = groupIn(:);
    end
else
    group = groupIn;
end

if iscategorical(group)
    % Note that, like the in-memory version, this function has the unenforced
    % requirement that group must either be a vector or an empty matrix. Unlike
    % the in-memory version, this function will not hit an indexing error if
    % that requirement is violated - incorrectly sized groups will flow
    % through, but the result may not be meaningful.
    
    [catCodes,cats] = codesAndCats(group);
    ncats = length(cats);
    %missings = ~catCodes; % find zero codes
    %anyMissings = any(missings);
    missings = coder.nullcopy(false(size(catCodes)));
    anyMissings = false;
    for ii = coder.internal.indexInt(1) : numel(missings)
        iiIsMissing = catCodes(ii) == 0;
        missings(ii) = iiIsMissing;
        if iiIsMissing
            anyMissings = true;
        end
    end

    % The missing-group exists only if requested and missings present in group.
    haveMissingGroup = inclnan && anyMissings;

    % Adjust the zero indices corresponding to missing values if there are any.
    if haveMissingGroup
        % If missing elements form their own group, create a new missing-group
        % at the end, and return the index of that group for those missing elements.
        catCodes(missings) = ncats + 1;
    elseif anyMissings % but ~inclnan
        % If missing elements are being treated as missing (not their own
        % group), we will ultimately replace the zero internal codes and return
        % NaN group indices for missing elements. But that happens after catCodes
        % becomes double gidx.
    end

    % At this point catCodes is still uintNN and elements corresponding to missing data
    % in group contain ...
    %                           |      inclempty
    %      anyMissings  inclnan |   true       false
    %      ---------------------|--------------------
    %             true     true | ncats+1     ncats+1
    %                     false |    0        ncats+1 <- this row will become NaNs
    %            false     true |   N/A         N/A
    %                     false |   N/A         N/A
    %

    % Figure out how many groups we have.
    if inclempty
        % If empty groups are being preserved, it's the number of categories,
        % plus maybe one more for the missing-group.
        ng = ncats + haveMissingGroup;
    else
        % Otherwise find how many non-empty categories, plus maybe the new category
        % for missings (if requested). Don't count zero codes.
        %counts = accumarray(catCodes,1,[ncats+1 1]);
        counts = zeros([ncats+1 1]);
        for ii = coder.internal.indexInt(1) : numel(catCodes)
            code = catCodes(ii);
            if code > 0
                counts(code) = counts(code) + 1;
            end
        end
        
        %nonEmptyGroups = (counts > 0);
        %g = sum(nonEmptyGroups);
        nonEmptyGroups = coder.nullcopy(false(size(counts)));
        ng = 0;
        for ii = coder.internal.indexInt(1) : numel(nonEmptyGroups)
            iiIsNonEmpty = counts(ii) > 0;
            nonEmptyGroups(ii) = iiIsNonEmpty;
            if iiIsNonEmpty
                ng = ng + 1;
            end
        end
    end

    % Turn the integer codes into double group indices.
    allGroupsNonEmpty = (ng == ncats + haveMissingGroup);
    if inclempty || allGroupsNonEmpty
        % If including empty groups, or if all groups are represented (including
        % the missing-group when requested), no need to squeeze out empty categories
        % in the category codes -- the codes _are_ the group indices.
        gidx = double(catCodes);
        if haveMissingGroup
            % catCodes(missings) was already adjusted to ng == ncats+1.
        elseif anyMissings % but ~inclnan
            gidx(missings) = NaN; % replace zeros from the original codes
        end
    else
        % Reassign the non-empty categories' indices to make them contiguous,
        % thus ignoring categories not present in the data
        groupIdxMap = cumsum(nonEmptyGroups);
        if anyMissings && ~inclnan
            % If missing values are being treated as missing, not their own group,
            % replace zero codes with NaN indices.
            catCodes(missings) = ncats + 1;
            groupIdxMap(ncats+1) = NaN;
        end
        gidx = groupIdxMap(catCodes); % convert uintNN to double
    end

    if nargout > 2
        % Create gdata as a categorical with exactly the same categories as in
        % the input, and one element for each group in the output.
        if inclempty
            gdataCodes = (1:ng)'; % elements for all of the original categories, plus maybe one more missing element
        else
            coder.assertDefined(nonEmptyGroups); % nonEmptyGroups is defined conditionally above - Coder cannot infer that
            %gdataCodes = find(nonEmptyGroups); % elements only for non-empty categories, plus maybe one more missing element
            %gdataCodes = gdataCodes(:); % force 0x1 col vector when gdataCodes is empty
            gdataCodes = zeros(0,1);
            for ii = coder.internal.indexInt(1) : numel(nonEmptyGroups)
                if nonEmptyGroups(ii)
                    gdataCodes = [gdataCodes; double(ii)]; %#ok<AGROW>
                end
            end
        end
        
        if haveMissingGroup
            % When a missing group if requested and missings are present, set the
            % element in gdata that represents that group to missing.
            gdataCodes(ng) = 0;
        end

        gdata = fastCtor(group,gdataCodes);
    end
        
elseif islogical(group) % logical has easy option
    if inclempty
        % If so the groups are easily defined
        gidx = cast(group,'like',1) + 1;
        ng = 2;
        gdata = [false; true];
    else
        % if group is empty set categories
        if isempty(group)
            gidx = cast(group,'like',1); % Avoiding explicit cast to double
            ng = 0;
            gdata = group;
        else
            % If all are true or false only have one group
            allTrueOrFalse = true;
            for ii = coder.internal.indexInt(2):numel(group)
                if group(ii) ~= group(ONE)
                    allTrueOrFalse = false;
                    break;
                end
            end
            if allTrueOrFalse
                gidx = ones(size(group));
                ng = 1;
                gdata = group(ONE);
            else
                % Otherwise same easy definition as above
                gidx = cast(group,'like',1) + 1;
                ng = 2;
                gdata = [false; true];
            end
        end
    end

else
    [gdata,~,gidx] = uniquehelper(group,[false false false true false],true,logical(inclnan));
    % The codegen version of this helper just calls unique, (for these options anyway)
    % so we can let everything flow through except unsupported types
    
    % Compute number of groups
    if iscellstr(groupIn) %#ok<ISCLSTR> 
        ng = size(gdata,2);
    else
        ng = size(gdata,1);
    end
end
% No additonal branches are needed because cells and strings are not supported
end