function y = movsortfun(op,x,kleft,kright,dim,nanflag,ep,fillval)
%MATLAB Code Generation Library Function

%    * OP is constant 'median', 'mad, 'max', or 'min'.
%    * X is the data array.
%    * KLEFT and KRIGHT are the elements of the equivalent 2-element vector
%      input for the window size.
%    * DIM is the dimension to operate along.
%    * NANFLAG must be 'o' for 'omitnan' or 'i' for 'includenan'. Longer
%      strings are OK, but only the first element is referenced.
%    * EP is the "endpoints" option to the mov* function, except that
%      here it must be 'f', 's', 'd' (i.e., not the numeric or logical fill
%      value--that value will be supplied via the FILLVAL input).
%    * FILLVAL is a scalar of the same type as X. It is used when ep == 'f'
%      to fill missing values for windows that overlap the edges of the
%      data.

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

narginchk(3,inf);
coder.internal.allowEnumInputs;
coder.internal.prefer_const(op,kleft,kright,dim,nanflag,ep,fillval);
coder.internal.assert(~isreal(x) || isreal(fillval), ...
    'MATLAB:movfun:complexFillValue');
switch coder.const(op)
    case 'max'
        yzero = coder.internal.scalarEg(x);
    case 'min'
        yzero = coder.internal.scalarEg(x);
    case 'mad'
        % Use scalarEg(x,x) to map logical to double.
        yzero = real(coder.internal.scalarEg(x,x));
    otherwise % 'median'
        % Use scalarEg(x,x) to map logical to double.
        yzero = coder.internal.scalarEg(x,x);
end
nx = coder.internal.indexInt(size(x,dim));
if ep == 'd'
    ny = nx - kleft - kright;
    if ny < 0
        ny = coder.internal.indexInt(0);
    elseif ny > nx
        ny = nx;
    end
else
    ny = nx;
end
vfun = @(x,ind2SubX,y,ind2SubY)vmovfun(x,ind2SubX,nx,y,ind2SubY,ny, ...
    op,kleft,kright,nanflag,ep,fillval);
y = coder.internal.applyVectorFunctionNoCopy(vfun,x,dim,ny,yzero);

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

function y = vmovfun(x,ind2SubX,nx,y,ind2SubY,ny, ...
    op,kleft,kright,nanflag,endopt,fillval)
coder.internal.prefer_const(op,nx,ny,kleft,kright,nanflag,endopt,fillval);

% endopt must be
%'s' for "shrink",
%'f' for "fill", or
%'d' for "discard".
FILL = endopt(1) == 'f';
DISCARD = endopt(1) == 'd';
% SHRINK = endopt(1) == 's';
if isempty(y)
    return
end
ONE = coder.internal.indexInt(1);
TWO = coder.internal.indexInt(2);
% Define xnew and xold avoid a compiler limitation.
XZERO = coder.internal.scalarEg(x);
xold = XZERO;
xnew = XZERO;
nw = kleft + kright + ONE;
s = coder.internal.SortedBuffer(nw,coder.internal.scalarEg(x),nanflag);
% Initialize the sorted buffer and compute the first element of the output.
[iLeftLast,iRightLast,nPadLeftLast,nPadRightLast] = movfunWindow( ...
    ONE,nx,kleft,kright,endopt);
for k = iLeftLast:iRightLast
    xidx = ind2SubX(k);
    xval = x(xidx{:});
    s = s.insert(xval);
end
if FILL
    for k = 1:nPadLeftLast
        s = s.insert(fillval);
    end
    for k = 1:nPadRightLast
        s = s.insert(fillval);
    end
end
yidx = ind2SubY(ONE);
y(yidx{:}) = applyOperation(op,s);
nw = kleft + kright + 1;
if nw > nx
    % The window is too large, so there is always padding or shrinkage on
    % the left or on the right or both.
    for k = TWO:ny
        [iLeft,iRight,nPadLeft,nPadRight] = movfunWindow( ...
            k,nx,kleft,kright,endopt);
        if iLeft > iLeftLast
            % Assumes iLeft == iLeftLast + 1.
            xidx = ind2SubX(iLeftLast);
            xold = x(xidx{:});
            remove = true;
            iLeftLast = iLeft;
        elseif nPadLeft < nPadLeftLast
            % Assumes nPadLeft = nPadLeftLast - 1.
            xold = fillval;
            remove = true;
            nPadLeftLast = nPadLeft;
        else
            remove = false;
        end
        if iRight > iRightLast
            % Assumes iRight = iRight + 1.
            xidx = ind2SubX(iRight);
            xnew = x(xidx{:});
            insert = true;
            iRightLast = iRight;
        elseif nPadRight > nPadRightLast
            % Assumes nPadRight = nPadRightLast + 1.
            xnew = fillval;
            insert = true;
            nPadRightLast = nPadRight;
        else
            insert = false;
        end
        if insert && remove
            s = s.replace(xold,xnew);
        elseif insert
            s = s.insert(xnew);
        elseif remove
            s = s.remove(xold);
        end
        yidx = ind2SubY(k);
        y(yidx{:}) = applyOperation(op,s);
    end
else
    % This is the more usual case where the window is smaller than the
    % range of data. Divide the output into segments to minimize the
    % complexity of the loop bodies.
    if DISCARD
        k0 = TWO;
        k1 = ny;
        % Mapping from index ix to the center of the window to index iy is
        % iy = ix - kleft ==> ix = iy + kleft.
        leavingShift = ONE;
        enteringShift = ONE - nw;
    else
        k0 = kleft + TWO;
        k1 = ny - kright;
        leavingShift = kleft + ONE;
        enteringShift = -kright;
    end
    % Ramp up to first full window with no filling. This loop body does not
    % execute in the DISCARD case, since the first full window has already
    % been processed.
    for k = TWO:k0 - ONE
        xidx = ind2SubX(k - enteringShift);
        xnew = x(xidx{:});
        if FILL
            s = s.replace(fillval,xnew);
        else
            s = s.insert(xnew);
        end
        yidx = ind2SubY(k);
        y(yidx{:}) = applyOperation(op,s);
    end
    % Now the first full window has been processed, and the sorted buffer
    % is fully initialized. In this loop an element will be inserted into
    % the buffer and an element removed from the buffer in each iteration.
    for k = k0:k1
        xidx = ind2SubX(k - leavingShift);
        xold = x(xidx{:});
        xidx = ind2SubX(k - enteringShift);
        xnew = x(xidx{:});
        s = s.replace(xold,xnew);
        yidx = ind2SubY(k);
        y(yidx{:}) = applyOperation(op,s);
    end
    % From here on out, an element is removed from the sorted buffer in
    % each iteration. In the FILL case, a fillval will be inserted to
    % replace it. In the SHRINK case, nothing will be inserted. The loop
    % body does not execute in the DISCARD case.
    for k = k1 + ONE:ny
        xidx = ind2SubX(k - leavingShift);
        xold = x(xidx{:});
        if FILL
            s = s.replace(xold,fillval);
        else
            s = s.remove(xold);
        end
        yidx = ind2SubY(k);
        y(yidx{:}) = applyOperation(op,s);
    end
end

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

function y = applyOperation(op,s)
coder.inline('always');
coder.internal.prefer_const(op);

switch coder.const(op)
    case 'max'
        y = s.max;
    case 'min'
        y = s.min;
    case 'mad'
        y = s.mad;
    otherwise
        y = s.median;
end

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

