function [b,tf,lthresh,uthresh,center] = filloutliers(a,fill,varargin)
%MATLAB Code Generation Library Function

%   Limitations:
%   'spline' and 'pchip' fill methods require variable-sizing.
%   'makima' fill method is not supported
%   'movmean' and 'movmedian' find methods not supported for datetime or
%   duration sample points and timetables
%   Only constant scalar, 'center', and 'clip' fill methods are supported
%   for datetime or duration sample points or timetables

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

narginchk(2,inf);
coder.internal.prefer_const(fill,varargin);
ZERO = coder.internal.indexInt(0);
ONE = coder.internal.indexInt(1);
TWO = coder.internal.indexInt(2);
THREE = coder.internal.indexInt(3);
ISTABLEORTIMETABLE = isa(a,'table') || isa(a,'timetable');
ISTIMETABLE = isa(a,'timetable');
% Validate A.
validateattributes(a,{'single','double','table','timetable'},{'real'},mfilename,'A',1);
% Validate FILL.
if ischar(fill) || isstring(fill)
    coder.internal.assert(coder.internal.isConst(fill), ...
        'Coder:toolbox:OptionStringsMustBeConstant');
    FILL = coder.const(validatestring(fill,{ ...
        'center','clip','previous','next','nearest', ...
        'linear','spline','pchip','makima' ...
        },mfilename,'Fill',2));
    coder.internal.assert(~strcmp(FILL,'makima'), ...
        'MATLAB:filloutliers:makimaNotSupported');
else
    validateattributes(fill,{'numeric'},{'scalar'},mfilename,'Fill',2);
    FILL = fill;
end
nargs = coder.internal.indexInt(nargin - 2);
nextArg = ONE;
lastArg = nargs;
methodChosen = false;
if nargs >= 1
    if ischar(varargin{1}) || isstring(varargin{1})
        coder.internal.assert(coder.internal.isConst(varargin{1}), ...
            'Coder:toolbox:OptionStringsMustBeConstant');
        str = validatestring(varargin{1},{ ...
            'median','mean','quartiles','percentiles','grubbs',...
            'gesd','movmedian','movmean','SamplePoints',...
            'DataVariables','ThresholdFactor','MaxNumOutliers',...
            'OutlierLocations','ReplaceValues'},mfilename,'',3);
        if strcmp(str,'median') || strcmp(str,'mean') || ...
                strcmp(str,'quartiles') || strcmp(str,'grubbs') || ...
                strcmp(str,'gesd')
            method = str;
            wl = [];
            methodChosen = true;
            nextArg = TWO;
        elseif strcmp(str,'movmedian') || strcmp(str,'movmean')
            method = str;
            coder.internal.assert(nargs >= 2, ...
                'MATLAB:filloutliers:MissingWindowLength',str);
            wl = varargin{2};
            coder.internal.assert(isnumeric(wl) && isreal(wl) || ...
                islogical(wl) || isa(wl,'datetime') || isa(wl,'duration'), ...
                'MATLAB:filloutliers:WindowLengthInvalidSizeOrClass');
            nwl = coder.internal.indexInt(numel(wl));
            coder.internal.assert(nwl == 1 || nwl == 2, ...
                'MATLAB:filloutliers:WindowLengthInvalidSizeOrClass');
            for k = 1:nwl
                coder.internal.assert(wl(k) > 0 && isfinite(wl(k)), ...
                    'MATLAB:filloutliers:WindowLengthInvalidSizeOrClass');
            end
            methodChosen = true;
            nextArg = THREE;
        elseif strcmp(str,'percentiles')
            method = str;
            coder.internal.assert(nargs >= 2, ...
                'MATLAB:filloutliers:MissingThreshold');
            wl = varargin{2};
            validateattributes(wl,{'numeric'}, {'real','vector', ...
                'size',[1 2],'finite','>=',0,'<=',100,'increasing'}, ...
                mfilename,'threshold',3);
            methodChosen = true;
            nextArg = THREE;
        else
            methodChosen = false;
            wl = [];
            nextArg = ONE;
        end
    end
    if nargs >= nextArg && eml_bitand(nargs - nextArg + ONE,ONE) == ONE
        dim = coder.internal.indexInt(varargin{nextArg});
        coder.internal.assert(~ISTABLEORTIMETABLE || coder.internal.isConstTrue(dim == ONE),'MATLAB:filloutliers:TableDimCodegen')
        coder.internal.assertValidDim(dim);
        nextArg = nextArg + 1;
    elseif ISTABLEORTIMETABLE
        dim = ONE;
    else
        dim = coder.internal.preferConstNonSingletonDim(a);
    end
elseif ISTABLEORTIMETABLE
    dim = ONE;
else
    dim = coder.internal.preferConstNonSingletonDim(a);
end
coder.internal.assert(eml_bitand(lastArg - nextArg + ONE,ONE) == ZERO, ...
    'MATLAB:filloutliers:ArgNameValueMismatch');
spidx = ZERO;
pidx = ZERO;
maxoidx = ZERO;
olocidx = ZERO;
dvaridx = ZERO;
repvidx = ZERO;
for k = coder.unroll(nextArg:TWO:lastArg - ONE)
    coder.internal.assert(coder.internal.isConst(varargin{k}), ...
        'Coder:toolbox:OptionStringsMustBeConstant');
    name = validatestring(varargin{k},{'DataVariables','SamplePoints', ...
        'ThresholdFactor','MaxNumOutliers','OutlierLocations','ReplaceValues'}, ...
        mfilename,'',k + 2);
    if strcmp(name,'SamplePoints')
        spidx = k + 1;
    elseif strcmp(name,'ThresholdFactor')
        pidx = k + 1;
    elseif strcmp(name,'MaxNumOutliers')
        maxoidx = k + 1;
    elseif strcmp(name,'OutlierLocations')
        olocidx = k + 1;
    elseif strcmp(name,'DataVariables')
        dvaridx = k + 1;
    elseif strcmp(name,'ReplaceValues')
        repvidx = k + 1;
    end
end
SAMPLEPOINTS_SUPPLIED = spidx > ZERO;
THRESHOLDFACTOR_SUPPLIED = pidx > ZERO;
MAXNUMOUTLIERS_SUPPLIED = maxoidx > ZERO;
OUTLIERLOCATIONS_SUPPLIED = olocidx > ZERO;
DATAVARIABLES_SUPPLIED = dvaridx > ZERO;
REPLACEVALUES_SUPPLIED = repvidx > ZERO;
if SAMPLEPOINTS_SUPPLIED || ISTIMETABLE
    coder.internal.assert(~SAMPLEPOINTS_SUPPLIED || ~ISTIMETABLE, ...
        'MATLAB:filloutliers:SamplePointsTimeTable');
    if ISTIMETABLE
        sp = a.Properties.RowTimes;
    else
        sp = varargin{spidx};
    end
    coder.internal.assert(isfloat(sp) || isa(sp,'duration') || ...
        isa(sp,'datetime'), 'MATLAB:filloutliers:SamplePointsInvalidDatatype');
    if methodChosen
        coder.internal.assert(~(isa(sp,'duration') || isa(sp,'datetime')) || ...
            ~(strcmp(method,'movmean') || strcmp(method,'movmedian')), ...
            'MATLAB:filloutliers:DatetimeOrDurationWithMovMethod');
        if ~isempty(wl) && (isa(sp,'duration') || isa(sp,'datetime'))
            coder.internal.assert(isa(wl,'duration'), ...
                'MATLAB:filloutliers:SamplePointsNonDuration');
        end
    end
    if isfloat(sp)
        validateattributes(sp,{'double','single'}, ...
            {'vector','increasing','finite','real'},...
            mfilename,'SamplePoints',spidx + 2)
    else
        validateattributes(sp,{'duration','datetime'}, ...
            {'vector','finite','real'},...
            mfilename,'SamplePoints',spidx + 2)
        % Check that sample points are strictly increasing
        checkSPSortingDuplicates(sp,'filloutliers');
    end
else
    sp = [];
end
if MAXNUMOUTLIERS_SUPPLIED
    validateattributes(varargin{maxoidx},{'numeric'}, ...
        {'scalar','positive','integer'}, ...
        mfilename,'MaxNumOutliers',maxoidx + 2);
    maxoutliers = coder.internal.indexInt(varargin{maxoidx});
else
    maxoutliers = [];
end
if OUTLIERLOCATIONS_SUPPLIED
    coder.internal.assert(~methodChosen, ...
        'MATLAB:filloutliers:MethodNotAllowed');
    coder.internal.assert( ...
        ~strcmp(FILL,'center') && ~strcmp(FILL,'clip'), ...
        'MATLAB:filloutliers:UnsupportedFill');
    method = varargin{olocidx};
    validateattributes(method,{'logical'}, ...
        {'size',size(a)},...
        mfilename,'OutlierLocations',olocidx + 2);
    methodChosen = true;
    wl = [];
end
if ~methodChosen
    method = 'median';
    wl = [];
end
if THRESHOLDFACTOR_SUPPLIED
    validateattributes(varargin{pidx},{'numeric'}, ...
        {'real','scalar','nonnegative','nonnan'}, ...
        mfilename,'ThresholdFactor',pidx + 2);
    p = double(varargin{pidx});
    if strcmp(method,'grubbs') || strcmp(method,'gesd')
        coder.internal.assert(p <= 1, ...
            'MATLAB:filloutliers:AlphaOutOfRange');
    end
elseif OUTLIERLOCATIONS_SUPPLIED
    p = 0;
else
    switch method
        case {'median','mean','movmedian','movmean'}
            p = 3;
        case 'quartiles'
            p = 1.5;
        otherwise % grubbs, gesd
            p = 0.05;
    end
end
if DATAVARIABLES_SUPPLIED
    coder.internal.assert(ISTABLEORTIMETABLE,'MATLAB:filloutliers:DataVariablesArray');
        dataVars = varargin{dvaridx}; % dataVars are validated in coder.internal.applyToTableVariables
elseif ISTABLEORTIMETABLE
    dataVars = ONE:coder.internal.indexInt(width(a));
else
    dataVars = [];
end
if REPLACEVALUES_SUPPLIED
    coder.internal.assert(ISTABLEORTIMETABLE,'MATLAB:filloutliers:ReplaceValuesArray');
    replaceValues = varargin{repvidx};
    coder.internal.assert(coder.internal.isConst(replaceValues), ...
        'Coder:toolbox:OptionInputsMustBeConstant','ReplaceValues');
    validateattributes(replaceValues,{'logical'}, ...
        {'scalar'},mfilename,'ReplaceValues',k + 2);
else
    replaceValues = true;
end

% Perform the calculation.
if ISTABLEORTIMETABLE
    [tfTmp,lthreshTmp,uthreshTmp,centerTmp,aTmp] = coder.internal.applyToTableVariables ...
        (a, @(x) validateAndLocateOutliers(x,ONE,method,wl,p,sp,maxoutliers,...
        FILL,true), dataVars, [false true true true true], ...
        [true false false false true],[replaceValues true true true replaceValues],'filled','MATLAB:filloutliers:DataVariablesTableSubscript');
    if isempty(tfTmp)
        % Either a is empty or dataVars is empty - ensure the output is the
        % correct size
        tf = false(size(a,1),size(a,2));
        if size(tf,2) == 0
            lthreshTable = table.init({}, 1, {}, 0, {}, a.Properties.DimensionNames); % 1x0 table
            % Row names do not need to be preserved because there are no
            % rows
            uthreshTable = lthreshTable;
            centerTable = lthreshTable;
        else
            lthreshTable = lthreshTmp;
            uthreshTable = uthreshTmp;
            centerTable = centerTmp;
        end
    else
        tf = tfTmp;
        lthreshTable = lthreshTmp;
        uthreshTable = uthreshTmp;
        centerTable = centerTmp;
    end
    if istimetable(a)
        b = table2timetableWithDimNames(aTmp,a.Properties.RowTimes,a.Properties.DimensionNames);
        lthresh = table2timetableWithDimNames(lthreshTable,a.Properties.RowTimes(1:height(lthreshTmp),:),a.Properties.DimensionNames);
        uthresh = table2timetableWithDimNames(uthreshTable,a.Properties.RowTimes(1:height(uthreshTmp),:),a.Properties.DimensionNames);
        center = table2timetableWithDimNames(centerTable,a.Properties.RowTimes(1:height(centerTmp),:),a.Properties.DimensionNames);
    else
        b = aTmp;
        lthresh = lthreshTable;
        uthresh = uthreshTable;
        center = centerTable;
    end
else
    [tf,lthresh,uthresh,center,b] = validateAndLocateOutliers(a,dim,...
        method,wl,p,sp,maxoutliers,FILL,false);
end

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

function [tf,lb,ub,center,a] = validateAndLocateOutliers(a,dim,...
    method,wl,p,sp,maxoutliers,replace,isTable)
coder.inline('always');
coder.internal.prefer_const(dim,method,wl,p,sp,maxoutliers,replace);
validateattributes(a,{'single','double'},{'real'},mfilename,'A',1);
coder.internal.assert(~isTable || iscolumn(a) || isempty(a),'MATLAB:filloutliers:NonColumnTableVar')
[tf,lb,ub,center,a] = locateoutliers(a,dim,method,...
    wl,p,sp,maxoutliers,replace);