function [c,ia,ib] = eml_setop(op,a,b,varargin)
%MATLAB Code Generation Library Function

%   Shared implementation for INTERSECT, SETDIFF, SETXOR, and UNION.
%
%   Notes:
%   1. When 'rows' is not specified:
%      * Inputs must be vectors. If a vector is variable-size, either the
%        first dimension or the second dimension must have a fixed length
%        of 1. In legacy mode, the inputs must be row vectors. In R2012a
%        mode inputs can be column vectors, but for UNION and SETXOR the
%        orientations of a and b must match.
%      * The input [] is not supported. Use a 1-by-0 or 0-by-1 input
%        (e.g., zeros(1,0)) to represent the empty set.
%      * In legacy mode, empty outputs are always 1-by-0, never 0-by-0.
%   2. In legacy mode when 'rows' is specified:
%      Outputs IA and IB are always column vectors, 0-by-1 if empty.
%   3. In legacy mode or in R2012a 'sorted' mode, inputs must already be
%      sorted in ascending order, use SORT or SORTROWS.
%   4. Complex inputs must be 'single' or 'double'.
%   5. If one input is complex and the other is real, the real input must
%      be sorted in complex order (unless the 'stable' option is used).
%   6. Cell array input for first or second argument not supported.

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

coder.internal.allowEnumInputs;
coder.inline('always');
eml_invariant(nargin >= 3, ...
    'MATLAB:minrhs');
eml_invariant(nargin <= 5, ...
    'Coder:toolbox:eml_setop_2', ...
    'IfNotConst','Fail');
eml_invariant(eml_is_const(op) && coder.internal.isCharOrScalarString(op), ...
    'Coder:toolbox:eml_setop_3', ...
    'IfNotConst','Fail');
eml_invariant(~iscell(a) && ~iscell(b), ...
    'Coder:toolbox:CellArraysNotSupported', mfilename);
eml_invariant( ...
    (isa(a,'float') || isreal(a)) && ...
    (isa(b,'float') || isreal(b)), ...
    'Coder:toolbox:eml_setop_4', ...
    'IfNotConst','Fail');
eml_invariant((~isenum(a) && ~isenum(b)) || isa(a,class(b)), ...
    'Coder:toolbox:eml_setop_5', ...
    'IfNotConst','Fail');
if strcmp(op,'intersect')
    opcode = INTERSECT;
elseif strcmp(op,'setdiff')
    opcode = SETDIFF;
elseif strcmp(op,'setxor')
    opcode = SETXOR;
elseif strcmp(op,'union')
    opcode = UNION;
else
    eml_invariant(false, ...
        'Coder:toolbox:eml_setop_6', ...
        'IfNotConst','Fail');
end
eml_invariant( ...
    (coder.internal.isBuiltInNumeric(a) || ischar(a) || islogical(a)) && ...
    (coder.internal.isBuiltInNumeric(b) || ischar(b) || islogical(b)), ...
    'Coder:toolbox:eml_setop_7', ...
    'IfNotConst','Fail');
eml_invariant(isa(a, 'double') || isa(b, 'double') || isequal(class(a), class(b)), ...
    'Coder:toolbox:eml_setop_10', class(a), class(b), ...
    'IfNotConst','Fail');
if coder.const( ...
        ~coder.target('MATLAB') && ... % This is just for x-mode testing.
        coder.internal.isConst(a) && ...
        coder.internal.isConst(b) && ...
        coder.internal.isConst(varargin))
    if nargout <= 1
        c1 = coder.const(@feval,op,a,b,varargin{:});
    elseif nargout == 2
        [c1,ia] = coder.const(@feval,op,a,b,varargin{:});
    else
        [c1,ia,ib] = coder.const(@feval,op,a,b,varargin{:});
    end
    if opcode == SETDIFF
        MAKECOMPLEX = coder.const(isreal(c1) && ~isreal(a));
    else
        MAKECOMPLEX = coder.const(isreal(c1) && ~(isreal(a) && isreal(b)));
    end
    if MAKECOMPLEX
        c = complex(c1);
    else
        c = c1;
    end
    return
end
[byrow,legacyp,stablep] = parse_flags(op,varargin{:});
if byrow
    [c,ia,ib] = do_rows(opcode,a,b,stablep,legacyp);
else
    [c,ia,ib] = do_vectors(opcode,a,b,stablep,legacyp);
end

%--------------------------------------------------------------------------

function p = legacy_mode_default_value
% Infer whether the MATLAB default mode is legacy.
tmp = coder.const(feval('union',[],[]));
p = coder.const(size(tmp,2)==0);

%--------------------------------------------------------------------------

function [byrow,legacyp,stablep] = parse_flags(op,varargin)
coder.internal.prefer_const(op);
coder.internal.prefer_const(varargin);
error_prefix = coder.const(['MATLAB:',upper(op),':']);
byrow = false;
legacyp = false;
r2012ap = false;
stablep = false;
sortedp = false;
for k = coder.unroll(nargin-1:-1:1)
    eml_invariant(eml_is_const(varargin{k}) && coder.internal.isCharOrScalarString(varargin{k}), ...
        'Coder:toolbox:unique_5', ...
        'IfNotConst','Fail');
    lenvk = coder.const(strlength(varargin{k}));
    len1 = coder.const(eml_max(1,lenvk));
    len2 = coder.const(eml_max(2,lenvk));
    if strncmpi(varargin{k},'legacy',len1)
        legacyp = true;
        eml_invariant(k == nargin-1, ...
            coder.const([error_prefix,'LegacyTrailing']), ...
            'IfNotConst','Fail');
    elseif strncmpi(varargin{k},'r2012a',len2)
        r2012ap = true;
        eml_invariant(k == nargin-1, ...
            coder.const([error_prefix,'R2012aTrailing']), ...
            'IfNotConst','Fail');
    elseif strncmpi(varargin{k},'rows',len2)
        byrow = true;
    elseif strncmpi(varargin{k},'sorted',len2)
        sortedp = true;
    elseif strncmpi(varargin{k},'stable',len2)
        stablep = true;
    else
        eml_invariant(false, ...
            coder.const([error_prefix,'UnknownFlag']), ...
            varargin{k},'IfNotConst','Fail');
    end
end
if legacyp || r2012ap
    % Either the legacy or the R2012a flag was supplied, so 'sorted' and
    % 'stable' flags are not permitted.
    eml_invariant(~(sortedp || stablep), ...
        coder.const([error_prefix,'SetOrderBehavior']), ...
        'IfNotConst','Fail');
elseif sortedp || stablep
    eml_invariant(~(sortedp && stablep), ...
        coder.const([error_prefix,'SetOrderConflict']), ...
        'IfNotConst','Fail');
    legacyp = false;
else
    legacyp = legacy_mode_default_value;
end

%--------------------------------------------------------------------------

function [c,ia,ib] = do_vectors(opcode,a,b,stablep,legacyp)
% Union, intersect, setdiff, or setxor of the vectors A and B.
coder.inline('never');
ZERO = zeros(coder.internal.indexIntClass);
ONE = ones(coder.internal.indexIntClass);
na = cast(numel(a),coder.internal.indexIntClass);
nb = cast(numel(b),coder.internal.indexIntClass);
isVectorA = coder.internal.isConstTrue(isvector(a));
isVectorB = coder.internal.isConstTrue(isvector(b));
isScalarA = coder.internal.isConstTrue(isscalar(a));
isScalarB = coder.internal.isConstTrue(isscalar(b));
isRowA = coder.internal.isConstTrue(size(a,1) == 1);
isRowB = coder.internal.isConstTrue(size(b,1) == 1);
if legacyp
    eml_invariant(isRowA && isRowB, ...
        'Coder:toolbox:eml_setop_9', ...
        'IfNotConst','Fail');
else
    eml_invariant(isVectorA && isVectorB , ...
        'Coder:toolbox:setopInputsMustBeVectors', ...
        'IfNotConst','Fail');
end
if opcode == INTERSECT
    CZERO = coder.internal.scalarEg([coder.internal.scalarEg(a),coder.internal.scalarEg(b)]);
    ncmax = eml_min(na,nb);
    niamax = ncmax;
    nibmax = ncmax;
elseif opcode == SETDIFF
    CZERO = coder.internal.scalarEg(a);
    ncmax = na;
    niamax = na;
    nibmax = ZERO;
else % opcode == SETXOR || opcode == UNION
    eml_invariant(isRowA == isRowB || isScalarA || isScalarB, ...
        'Coder:toolbox:vectorOrientationMustMatch');
    CZERO = coder.internal.scalarEg([coder.internal.scalarEg(a),coder.internal.scalarEg(b)]);
    ncmax = coder.internal.indexPlus(na,nb);
    niamax = na;
    nibmax = nb;
end
if opcode == SETDIFF
    isRow = isRowA;
else
    isRow = isRowA && isRowB;
end
if coder.const(isRow)
    coder.varsize('c',[],[0,1]);
    c = coder.nullcopy(eml_expand(CZERO,[1,ncmax]));
    if legacyp
        coder.varsize('ia','ib',[],[0,1]);
        ia = coder.nullcopy(zeros(1,niamax,coder.internal.indexIntClass));
        ib = coder.nullcopy(zeros(1,nibmax,coder.internal.indexIntClass));
    else
        coder.varsize('ia','ib',[],[1,0]);
        ia = coder.nullcopy(zeros(niamax,1,coder.internal.indexIntClass));
        ib = coder.nullcopy(zeros(nibmax,1,coder.internal.indexIntClass));
    end
else
    coder.varsize('c','ia','ib',[],[1,0]);
    c = coder.nullcopy(eml_expand(CZERO,[ncmax,1]));
    ia = coder.nullcopy(zeros(niamax,1,coder.internal.indexIntClass));
    ib = coder.nullcopy(zeros(nibmax,1,coder.internal.indexIntClass));
end
if stablep
    if isreal(a) == isreal(b)
        aperm = coder.internal.sortIdx(a,'a');
        bperm = coder.internal.sortIdx(b,'a');
    elseif isreal(a)
        aperm = coder.internal.sortIdx(complex(a),'a');
        bperm = coder.internal.sortIdx(b,'a');
    else % if isreal(b)
        aperm = coder.internal.sortIdx(a,'a');
        bperm = coder.internal.sortIdx(complex(b),'a');
    end
else
    % Require sorted input.
    if isreal(a) == isreal(b)
        if ~issorted(a)
            coder.internal.error('Coder:toolbox:eml_setop_unsortedA');
        end
        if ~issorted(b)
            coder.internal.error('Coder:toolbox:eml_setop_unsortedB');
        end
    elseif isreal(a)
        if ~issorted(complex(a))
            coder.internal.error('Coder:toolbox:SetOpUnsortedRealAWithComplexB');
        end
        if ~issorted(b)
            coder.internal.error('Coder:toolbox:eml_setop_unsortedB');
        end
    else % if isreal(b)
        if ~issorted(a)
            coder.internal.error('Coder:toolbox:eml_setop_unsortedA');
        end
        if ~issorted(complex(b))
            coder.internal.error('Coder:toolbox:SetOpUnsortedRealBWithComplexA');
        end
    end
end
nc = ZERO;
nia = ZERO;
nib = ZERO;
iafirst = ONE;
ialast = ONE;
ibfirst = ONE;
iblast = ONE;
while ialast <= na && iblast <= nb
    if stablep
        [ialast,ak] = skip_to_last_equal_value(ialast,a,aperm);
        [iblast,bk] = skip_to_last_equal_value(iblast,b,bperm);
    else
        [ialast,ak] = skip_to_last_equal_value(ialast,a);
        [iblast,bk] = skip_to_last_equal_value(iblast,b);
    end
    if equal(ak,bk)
        if opcode == INTERSECT
            nc = coder.internal.indexPlus(nc,1);
            if legacyp
                c(nc) = ak;
                ia(nc) = ialast;
                ib(nc) = iblast;
            elseif stablep
                ia(nc) = aperm(iafirst);
                ib(nc) = bperm(ibfirst);
            else
                c(nc) = ak;
                ia(nc) = iafirst;
                ib(nc) = ibfirst;
            end
        elseif opcode == UNION
            nc = coder.internal.indexPlus(nc,1);
            if legacyp
                c(nc) = bk;
                nib = coder.internal.indexPlus(nib,1);
                ib(nib) = iblast;
            elseif stablep
                nia = coder.internal.indexPlus(nia,1);
                ia(nia) = aperm(iafirst);
            else
                c(nc) = ak;
                nia = coder.internal.indexPlus(nia,1);
                ia(nia) = iafirst;
            end
        end
        ialast = coder.internal.indexPlus(ialast,1);
        iafirst = ialast;
        iblast = coder.internal.indexPlus(iblast,1);
        ibfirst = iblast;
    elseif coder.internal.sortLT(ak,bk) % ak < bk
        if opcode == SETDIFF || opcode == SETXOR || opcode == UNION
            nc = coder.internal.indexPlus(nc,1);
            nia = coder.internal.indexPlus(nia,1);
            if legacyp
                c(nc) = ak;
                ia(nia) = ialast;
            elseif stablep
                ia(nia) = aperm(iafirst);
            else
                c(nc) = ak;
                ia(nia) = iafirst;
            end
        end
        ialast = coder.internal.indexPlus(ialast,1);
        iafirst = ialast;
    else
        if opcode == SETXOR
            nc = coder.internal.indexPlus(nc,1);
            nib = coder.internal.indexPlus(nib,1);
            if legacyp
                c(nc) = bk;
                ib(nib) = iblast;
            elseif stablep
                ib(nib) = bperm(ibfirst);
            else
                c(nc) = bk;
                ib(nib) = ibfirst;
            end
        elseif opcode == UNION
            nc = coder.internal.indexPlus(nc,1);
            nib = coder.internal.indexPlus(nib,1);
            if legacyp
                c(nc) = bk;
                ib(nib) = iblast;
            elseif stablep
                ib(nib) = bperm(ibfirst);
            else
                c(nc) = bk;
                ib(nib) = ibfirst;
            end
        end
        iblast = coder.internal.indexPlus(iblast,1);
        ibfirst = iblast;
    end
end
if opcode == SETDIFF || opcode == SETXOR || opcode == UNION
    % Remaining elements of A are not present in B.
    while ialast <= na
        if stablep
            [ialast,ak] = skip_to_last_equal_value(ialast,a,aperm);
        else
            [ialast,ak] = skip_to_last_equal_value(ialast,a);
        end
        nc = coder.internal.indexPlus(nc,1);
        nia = coder.internal.indexPlus(nia,1);
        if legacyp
            c(nc) = ak;
            ia(nia) = ialast;
        elseif stablep
            ia(nia) = aperm(iafirst);
        else
            c(nc) = ak;
            ia(nia) = iafirst;
        end
        ialast = coder.internal.indexPlus(ialast,1);
        iafirst = ialast;
    end
end
if opcode == SETXOR || opcode == UNION
    % Remaining elements of B are not present in A.
    while iblast <= nb
        if stablep
            [iblast,bk] = skip_to_last_equal_value(iblast,b,bperm);
        else
            [iblast,bk] = skip_to_last_equal_value(iblast,b);
        end
        nc = coder.internal.indexPlus(nc,1);
        nib = coder.internal.indexPlus(nib,1);
        if legacyp
            c(nc) = bk;
            ib(nib) = iblast;
        elseif stablep
            ib(nib) = bperm(ibfirst);
        else
            c(nc) = bk;
            ib(nib) = ibfirst;
        end
        iblast = coder.internal.indexPlus(iblast,1);
        ibfirst = iblast;
    end
end
if opcode == INTERSECT
    nia = nc;
    nib = nc;
end
if niamax > 0
    assert(nia <= niamax); %<HINT>
    if legacyp
        % Since we only support row vector input in legacy mode, ia is
        % a row-vector.
        ia = ia(1,1:nia);
    else
        ia = ia(1:nia,1);
    end
end
if opcode == INTERSECT || opcode == SETXOR || opcode == UNION
    if nibmax > 0
        assert(nib <= nibmax); %<HINT>
        if legacyp
            % Since we only support row vector input in legacy mode, ib is
            % a row-vector.
            ib = ib(1,1:nib);
        else
            ib = ib(1:nib,1);
        end
    end
end
if stablep
    % Write output array.
    [ia,iaperm] = sort(ia);
    for k = 1:nia
        c(k) = a(ia(k));
    end
    if opcode == SETXOR || opcode == UNION
        ib = sort(ib);
        for k = 1:nib
            c(coder.internal.indexPlus(k,nia)) = b(ib(k));
        end
    elseif opcode == INTERSECT || opcode == SETXOR
        ib = ib(iaperm);
    end
end
% Trim the output arrays.
if ncmax > 0
    assert(nc <= ncmax); %<HINT>
    if isRow
        c = c(1,1:nc);
    else
        c = c(1:nc,1);
    end
end

%--------------------------------------------------------------------------

function [k,xk] = skip_to_last_equal_value(k,x,perm)
% Skip index k forward to the last index of values identical to x(k). The
% input array x must be sorted ascending if nargin < 3, else x(perm) must
% be sorted ascending. For convenience, since it must be looked up anyway,
% the second output is x(k).

useperm = nargin == 3;
nx = cast(numel(x),coder.internal.indexIntClass);
if useperm
    xk = x(perm(k));
else
    xk = x(k);
end
while k < nx
    if useperm
        xkplus = x(perm(coder.internal.indexPlus(k,1)));
    else
        xkplus = x(coder.internal.indexPlus(k,1));
    end
    if equal(xkplus,xk)
        k = coder.internal.indexPlus(k,1);
    else
        break
    end
end

%--------------------------------------------------------------------------

function k = skip_to_last_equal_row(k,x,perm)
% Skip index k forward to the last index of the row identical to x(k,:).
% The input matrix x must be SORTROWS sorted ascending if nargin < 3, else
% x(perm) must be SORTROWS sorted ascending.
useperm = nargin == 3;
nx = cast(size(x,1),coder.internal.indexIntClass);
if useperm
    j = perm(k);
else
    j = k;
end
while k < nx
    if useperm
        jplus = perm(coder.internal.indexPlus(k,1));
    else
        jplus = coder.internal.indexPlus(k,1);
    end
    if relop_rows(x,j,x,jplus) == ROWS_ARE_EQUAL
        k = coder.internal.indexPlus(k,1);
    else
        break
    end
end

%--------------------------------------------------------------------------

function p = relop_rows(a,arow,b,brow)
% Returns ROWS_ARE_EQUAL if a(arow,:) is equal to b(brow,:).
% Returns ROW_LESS_THAN if a(arow,:) is less than b(brow,:) in sorted order.
% Returns ROW_GREATER_THAN if a(arow,:) is greater than b(brow,:) in sorted
% order.
if ischar(a) && ischar(b)
    for k = ones(coder.internal.indexIntClass):eml_min(size(a,2),size(b,2))
        if equal(a(arow,k),b(brow,k))
        elseif coder.internal.sortLT(a(arow,k),b(brow,k))
            p = ROW_LESS_THAN;
            return
        else
            p = ROW_GREATER_THAN;
            return
        end
    end
    for k = coder.internal.indexPlus(size(b,2),1):size(a,2)
        if a(arow,k) ~= ' '
            p = ROW_GREATER_THAN;
            return
        end
    end
    for k = coder.internal.indexPlus(size(a,2),1):size(b,2)
        if b(brow,k) ~= ' '
            p = ROW_LESS_THAN;
            return
        end
    end
    p = ROWS_ARE_EQUAL;
else
    CAN_BE_EQUAL = true;
    for k = ones(coder.internal.indexIntClass):size(a,2)
        if equal(a(arow,k),b(brow,k))
        elseif coder.internal.sortLT(a(arow,k),b(brow,k))
            p = ROW_LESS_THAN;
            return
        elseif isnan(a(arow,k)) && isnan(b(brow,k))
            CAN_BE_EQUAL = false;
        else
            p = ROW_GREATER_THAN;
            return
        end
    end
    if CAN_BE_EQUAL
        p = ROWS_ARE_EQUAL;
    else
        p = ROW_LESS_THAN;
    end
end


%--------------------------------------------------------------------------

function [c,ia,ib] = do_rows(opcode,a,b,stablep,legacyp)
% Union, intersect, setdiff, or setxor of the rows of matrices A and B.
coder.inline('never');
ZERO = zeros(coder.internal.indexIntClass);
ONE = ones(coder.internal.indexIntClass);
eml_invariant(ismatrix(a) && ismatrix(b), ...
    'Coder:toolbox:eml_setop_12', ...
    'IfNotConst','Fail');
charinput = ischar(a) && ischar(b);
eml_invariant(charinput || size(a,2) == size(b,2), ...
    'Coder:MATLAB:setxor_AandBColnumAgree');
na = cast(size(a,1),coder.internal.indexIntClass);
nacols = cast(size(a,2),coder.internal.indexIntClass);
nb = cast(size(b,1),coder.internal.indexIntClass);
nbcols = cast(size(b,2),coder.internal.indexIntClass);
mincols = eml_min(nacols,nbcols);
if opcode == INTERSECT
    CZERO = coder.internal.scalarEg([coder.internal.scalarEg(a),coder.internal.scalarEg(b)]);
    ncmax = eml_min(na,nb);
    niamax = ncmax;
    nibmax = ncmax;
    ccols = mincols;
elseif opcode == SETDIFF
    CZERO = coder.internal.scalarEg(a);
    ncmax = na;
    niamax = na;
    nibmax = ZERO;
    ccols = nacols;
else % opcode == SETXOR || opcode == UNION
    CZERO = coder.internal.scalarEg([coder.internal.scalarEg(a),coder.internal.scalarEg(b)]);
    ncmax = coder.internal.indexPlus(na,nb);
    niamax = na;
    nibmax = nb;
    if opcode == UNION
        ccols = eml_max(nacols,nbcols);
    else
        ccols = mincols;
    end
end
if charinput
    coder.varsize('c',[],[1,1]);
else
    constncols = eml_is_const(nacols) && eml_is_const(nbcols);
    coder.varsize('c',[],[1,~constncols]);
end
coder.varsize('ia','ib',[],[1,0]);
c = coder.nullcopy(eml_expand(CZERO,[ncmax,ccols]));
if charinput
    c(:) = ' ';
end
nc = ZERO;
ia = coder.nullcopy(zeros(niamax,1,coder.internal.indexIntClass));
nia = ZERO;
ib = coder.nullcopy(zeros(nibmax,1,coder.internal.indexIntClass));
nib = ZERO;
if stablep
    cola = coder.nullcopy(zeros(1,nacols,coder.internal.indexIntClass));
    for k = ONE:nacols
        cola(k) = k;
    end
    colb = coder.nullcopy(zeros(1,nbcols,coder.internal.indexIntClass));
    for k = ONE:nbcols
        colb(k) = k;
    end
    if isreal(a) == isreal(b)
        aperm = coder.internal.sortIdx(a,cola);
        bperm = coder.internal.sortIdx(b,colb);
    elseif isreal(a)
        aperm = coder.internal.sortIdx(complex(a),cola);
        bperm = coder.internal.sortIdx(b,colb);
    else % if isreal(b)
        aperm = coder.internal.sortIdx(a,cola);
        bperm = coder.internal.sortIdx(complex(b),colb);
    end
else
    % Require sorted input.
    if isreal(a) == isreal(b)
        if ~issorted(a,'rows')
            coder.internal.error('Coder:toolbox:eml_setop_unsortedRowsA');
        end
        if ~issorted(b,'rows')
            coder.internal.error('Coder:toolbox:eml_setop_unsortedRowsB');
        end
    elseif isreal(a)
        if ~issorted(complex(a),'rows')
            coder.internal.error('Coder:toolbox:SetOpUnsortedRowsRealAWithComplexB');
        end
        if ~issorted(b,'rows')
            coder.internal.error('Coder:toolbox:eml_setop_unsortedRowsB');
        end
    else % if isreal(b)
        if ~issorted(a,'rows')
            coder.internal.error('Coder:toolbox:eml_setop_unsortedRowsA');
        end
        if ~issorted(complex(b),'rows')
            coder.internal.error('Coder:toolbox:SetOpUnsortedRowsRealBWithComplexA');
        end
    end
end
iafirst = ONE;
ibfirst = ONE;
ialast = ONE;
iblast = ONE;
while iafirst <= na && ibfirst <= nb
    % Find the last row identical to this one.
    if stablep
        ialast = skip_to_last_equal_row(ialast,a,aperm);
        iblast = skip_to_last_equal_row(iblast,b,bperm);
        r = relop_rows(a,aperm(iafirst),b,bperm(ibfirst));
    else
        ialast = skip_to_last_equal_row(ialast,a);
        iblast = skip_to_last_equal_row(iblast,b);
        r = relop_rows(a,iafirst,b,ibfirst);
    end
    if r == ROWS_ARE_EQUAL
        if opcode == INTERSECT
            nc = coder.internal.indexPlus(nc,1);
            if legacyp
                for k = 1:nacols
                    c(nc,k) = a(ialast,k);
                end
                ia(nc) = ialast;
                ib(nc) = iblast;
            elseif stablep
                ia(nc) = aperm(iafirst);
                ib(nc) = bperm(ibfirst);
            else
                for k = 1:nacols
                    c(nc,k) = a(iafirst,k);
                end
                ia(nc) = iafirst;
                ib(nc) = ibfirst;
            end
        elseif opcode == UNION
            nc = coder.internal.indexPlus(nc,1);
            if legacyp
                for k = 1:nbcols
                    c(nc,k) = b(iblast,k);
                end
                nib = coder.internal.indexPlus(nib,1);
                ib(nib) = iblast;
            elseif stablep
                nia = coder.internal.indexPlus(nia,1);
                ia(nia) = aperm(iafirst);
            else
                for k = 1:nacols
                    c(nc,k) = a(iafirst,k);
                end
                nia = coder.internal.indexPlus(nia,1);
                ia(nia) = iafirst;
            end
        end
        ialast = coder.internal.indexPlus(ialast,1);
        iafirst = ialast;
        iblast = coder.internal.indexPlus(iblast,1);
        ibfirst = iblast;
    elseif r == ROW_LESS_THAN
        if opcode == SETDIFF || opcode == SETXOR || opcode == UNION
            nc = coder.internal.indexPlus(nc,1);
            nia = coder.internal.indexPlus(nia,1);
            if legacyp
                for k = 1:nacols
                    c(nc,k) = a(ialast,k);
                end
                ia(nia) = ialast;
            elseif stablep
                ia(nia) = aperm(iafirst);
            else
                for k = 1:nacols
                    c(nc,k) = a(iafirst,k);
                end
                ia(nia) = iafirst;
            end
        end
        ialast = coder.internal.indexPlus(ialast,1);
        iafirst = ialast;
    else
        if opcode == SETXOR
            nc = coder.internal.indexPlus(nc,1);
            nib = coder.internal.indexPlus(nib,1);
            if legacyp
                for k = 1:nbcols
                    c(nc,k) = b(iblast,k);
                end
                ib(nib) = iblast;
            elseif stablep
                ib(nib) = bperm(ibfirst);
            else
                for k = 1:nbcols
                    c(nc,k) = b(ibfirst,k);
                end
                ib(nib) = ibfirst;
            end
        elseif opcode == UNION
            nc = coder.internal.indexPlus(nc,1);
            nib = coder.internal.indexPlus(nib,1);
            if legacyp
                for k = 1:nbcols
                    c(nc,k) = b(iblast,k);
                end
                ib(nib) = iblast;
            elseif stablep
                ib(nib) = bperm(ibfirst);
            else
                for k = 1:nbcols
                    c(nc,k) = b(ibfirst,k);
                end
                ib(nib) = ibfirst;
            end
        end
        iblast = coder.internal.indexPlus(iblast,1);
        ibfirst = iblast;
    end
end
if opcode == SETDIFF || opcode == SETXOR || opcode == UNION
    % Remaining rows of A are not present in B.
    while ialast <= na
        if stablep
            ialast = skip_to_last_equal_row(ialast,a,aperm);
        else
            ialast = skip_to_last_equal_row(ialast,a);
        end
        nc = coder.internal.indexPlus(nc,1);
        nia = coder.internal.indexPlus(nia,1);
        if legacyp
            for k = 1:nacols
                c(nc,k) = a(ialast,k);
            end
            ia(nia) = ialast;
        elseif stablep
            ia(nia) = aperm(iafirst);
        else
            for k = 1:nacols
                c(nc,k) = a(iafirst,k);
            end
            ia(nia) = iafirst;
        end
        ialast = coder.internal.indexPlus(ialast,1);
        iafirst = ialast;
    end
end
if opcode == SETXOR || opcode == UNION
    % Remaining elements of B are not present in A.
    while iblast <= nb
        if stablep
            iblast = skip_to_last_equal_row(iblast,b,bperm);
        else
            iblast = skip_to_last_equal_row(iblast,b);
        end
        nc = coder.internal.indexPlus(nc,1);
        nib = coder.internal.indexPlus(nib,1);
        if legacyp
            for k = 1:nbcols
                c(nc,k) = b(iblast,k);
            end
            ib(nib) = iblast;
        elseif stablep
            ib(nib) = bperm(ibfirst);
        else
            for k = 1:nbcols
                c(nc,k) = b(ibfirst,k);
            end
            ib(nib) = ibfirst;
        end
        iblast = coder.internal.indexPlus(iblast,1);
        ibfirst = iblast;
    end
end
assert(nc <= ncmax); %<HINT>
if opcode == INTERSECT
    nia = nc;
    nib = nc;
end
assert(nia <= niamax); %<HINT>
ia = ia(1:nia,1);
if opcode == INTERSECT || opcode == SETXOR || opcode == UNION
    assert(nib <= nibmax); %<HINT>
    ib = ib(1:nib,1);
end
if stablep
    % Write output array.
    [ia,iaperm] = sort(ia);
    for j = 1:nacols
        for i = 1:nia
            c(i,j) = a(ia(i),j);
        end
    end
    if opcode == SETXOR || opcode == UNION
        ib = sort(ib);
        for j = 1:nbcols
            for i = 1:nib
                c(coder.internal.indexPlus(i,nia),j) = b(ib(i),j);
            end
        end
    elseif opcode == INTERSECT || opcode == SETXOR
        ib = ib(iaperm);
    end
end
if charinput && ...
        (opcode == INTERSECT || opcode == SETDIFF || opcode == SETXOR)
    % Trim trailing columns if they are all spaces.
    col = ccols;
    while col >= 1
        if col_is_all_spaces(c,nc,col)
            col = coder.internal.indexMinus(col,1);
        else
            break
        end
    end
    assert(col <= ccols)
    c = c(1:nc,1:col);
else
    c = c(1:nc,:);
end

%--------------------------------------------------------------------------

function p = col_is_all_spaces(c,nrows,col)
coder.inline('always');
for k = 1:nrows
    if c(k,col) ~= ' '
        p = false;
        return
    end
end
p = true;

%--------------------------------------------------------------------------

function y = ROWS_ARE_EQUAL
coder.inline('always');
y = uint8(0);

function y = ROW_LESS_THAN
coder.inline('always');
y = uint8(1);

function y = ROW_GREATER_THAN
coder.inline('always');
y = uint8(2);

%--------------------------------------------------------------------------

function y = INTERSECT
coder.inline('always');
y = uint8(1);

function y = SETDIFF
coder.inline('always');
y = uint8(2);

function y = SETXOR
coder.inline('always');
y = uint8(3);

function y = UNION
coder.inline('always');
y = uint8(4);

%--------------------------------------------------------------------------

function p = equal(a,b)
% Local function to use for equality comparisons.
coder.inline('always');
p = (a == b);

%--------------------------------------------------------------------------
