function y = prctile(x,p,dim) %#codegen
%MATLAB Code Generation Toolbox Function

%   Limitations:
%   1. Does not match MATLAB when x = [].
%   2. If x is a variable-size array that is not necessarily a vector and x
%      becomes a vector at run-time, then the orientation of the output
%      vector y will match the orientation of x, not the orientation of p.
%   3. If x is a variable-size array and the leading non-singleton
%      dimension varies at run-time, then the DIM argument must be
%      supplied.
%   4. If supplied, the DIM input must be a compile-time constant.
%   5. Does not support complex inputs.

%   Copyright 1993-2021 The MathWorks, Inc.

coder.internal.assert(nargin >= 2,'MATLAB:minrhs');
coder.internal.assert(isa(x,'float'), ...
    'Coder:toolbox:unsupportedClass','prctile',class(x));
coder.internal.assert(isreal(x),'Coder:common:NotRealInput');
coder.internal.assert(isvalid_percents_vector(p), ...
    'MATLAB:prctile:BadPercents');
if nargin < 3
    if eml_is_const(size(x)) && isequal(size(x),[0,0])
        y = repmat(coder.internal.nan(class(coder.internal.scalarEg(x,p))),size(p));
        return
    end
    dim = coder.internal.constNonSingletonDim(x);
    % We determine the active/working dimension at compilation time in
    % order to generate efficient C code. If sizes are variable, we will
    % select the dim = 1 for an m-by-n-by-k array, where m, n, and k are
    % variable. However, if m = 1 at run-time the correct choice is dim =
    % 2, unless n = 1 at run-time, in which case dim = 3 is the correct
    % choice, unless the input is scalar (k = 1, too). Here we check that
    % either size(x,dim) isn't variable (in which case dim is correct
    % already), or the input is scalar (in which case dim doesn't matter),
    % or size(x,dim) ~= 1 (in which case we would still have selected the
    % same dim if we had selected it at run-time). Otherwise we issue a
    % run-time error.
    coder.internal.assert(eml_is_const(size(x,dim)) || ...
        isscalar(x) || size(x,dim) ~= 1, ...
        'Coder:toolbox:autoDimIncompatibility');
else
    coder.internal.assert(eml_is_const(dim),'Coder:toolbox:dimNotConst');
    coder.internal.assertValidDim(dim);
end
if eml_is_const(isvector(x)) && isvector(x) && nargin < 3
    % Handle the special case of matching the orientation of p with
    % vector x input. Note that this only kicks in for inputs that are
    % known to be vectors at compilation time. If the input x has some
    % arbitrary shape that degenerates into a vector because dimensions
    % collapse to length 1, the orientation of the output will be not
    % be forcibly matched to the orientation of p.
    y = percentile_vector(x,p);
else
    y = percentile_array(x,p,dim);
end

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

function pok = isvalid_percents_vector(p)
% Compute pok = isreal(p) & all(p >= 0 & p <= 100) without introducing
% any temporary arrays.
pok = isreal(p) && isvector(p);
if pok
    for k = 1:numel(p)
        if ~(p(k) >= 0 && p(k) <= 100)
            pok = false;
            break
        end
    end
end

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

function pct = percentile_array(x,p,dim)
% Calculate percentiles of an arbitrarily-sized array along dimension dim.
coder.internal.prefer_const(dim);
cls = class(coder.internal.scalarEg(x,p));
np = coder.internal.indexInt(numel(p));
sz = size(x);
nsz = numel(sz);
if dim <= nsz
    sz(dim) = np;
    pct = coder.nullcopy(zeros(sz,cls));
else
    pct = coder.nullcopy(zeros([sz,ones(1,dim-nsz-1),np],cls));
end
if coder.const(eml_is_const(isvector(x)) && isvector(x) && ( ...
        (dim == 1 && eml_is_const(size(x,2)) && size(x,2) == 1) || ...
        (dim == 2 && eml_is_const(size(x,1)) && size(x,1) == 1)))
    pct(:) = percentile_vector(x(:),p(:));
elseif isempty(x) || isempty(pct)
    pct(:) = coder.internal.nan(class(pct));
else
    vlen = coder.internal.indexInt(size(x,dim));
    wk = coder.nullcopy(zeros(vlen,1,'like',pct));
    vstride = coder.internal.prodsize(x,'below',dim);
    npages = coder.internal.prodsize(x,'above',dim);
    ix = coder.internal.indexInt(0);
    iy = coder.internal.indexInt(0);
    for xi = 1:npages
        ixstart = ix;
        iystart = iy;
        for xj = 1:vstride
            ixstart = ixstart + 1;
            iystart = iystart + 1;
            ix = ixstart;
            wk(1) = x(ix);
            for k = 2:vlen
                ix = ix + vstride;
                wk(k) = x(ix);
            end
            iy = iystart;
            pctv = percentile_vector(wk,p);
            pct(iy) = pctv(1);
            for k = 2:np
                iy = iy + vstride;
                pct(iy) = pctv(k);
            end
        end
    end
end

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

function pct = percentile_vector(x,p)
% Compute percentiles of a non-empty vector.
np = numel(p);
pct = coder.nullcopy(zeros(size(p),class(x)));
if isempty(x) || isempty(p)
    pct(:) = coder.internal.nan(class(pct));
else
    idx = coder.internal.sortIdx(x,'a');
    % Use only the non-NaN values.
    nj = lastnonnan(x,idx);
    if nj < 1
        pct(:) = coder.internal.nan(class(pct));
    elseif nj == 1
        pct(:) = x(idx(1));
    else
        for k = 1:np
            r = (p(k)/100)*double(nj);
            i = round(r); % floor(r + 0.5);
            if i < 1
                pct(k) = x(idx(1));
            elseif i >= nj
                pct(k) = x(idx(nj));
            else
                r = r - i;
                pct(k) = (0.5 - r)*x(idx(i)) + (0.5 + r)*x(idx(i+1));
            end
        end
    end
end

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

function k = lastnonnan(x,idx)
% Index of last non-NaN value in column j of sorted matrix x(idx).
% The return value is 0 if all elements are NaN.
coder.inline('always');
k = coder.internal.indexInt(numel(x));
while k > 0
    if ~isnan(x(idx(k)))
        return
    end
    k = k - 1;
end

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