function b = standardizeMissing(a, varargin)
%STANDARDIZEMISSING Insert standard missing data indicators into a codistributed table.
%   B = STANDARDIZEMISSING(A,INDICATORS)
%   B = STANDARDIZEMISSING(...,'DataVariables',DATAVARS)
%   B = STANDARDIZEMISSING(...,'ReplaceValues',TF)
%   
%   See also STANDARDIZEMISSING, CODISTRIBUTED.
%   


%   Copyright 2016-2022 The MathWorks, Inc.

% Must have two inputs and trailing inputs must come in pairs
narginchk(2,inf)
if mod(nargin,2) ~= 0
    error(message('MATLAB:standardizeMissing:NameValuePairs'));
end

if nargin>=2
    % If data was not codistributed, run replicated.
    varargin = distributedutil.CodistParser.gatherElements(varargin);
    if ~isa(a, 'codistributed')
        b = standardizeMissing(a, varargin{:});
        return;
    end
end

% Use the in-memory function to check the syntax is OK
distributedutil.validateSyntax(@standardizeMissing, [{a}, varargin]);

% We need to know if we are operating in-place or creating new variables.
if iIsAppendingVars(varargin)
    % Work on blocks of rows of the input.
    b = codistributed.pSlicewiseOp(@standardizeMissing, a, varargin{:});

else
    % Operate in-place.
    % If distributed in the second dimension, we need to take care over which
    % variables to examine. For other dimensions this is just element-wise.
    codistr = getCodistributor(a);
    if codistr.Dimension == 2
        % We need to work out which columns are to be standardized and which of
        % those are on this worker. This helper will replace the DataVariables
        % input (if any) with local indices.
        try
            args = iGetDataVariables(a, varargin);
        catch err
            error(message('MATLAB:standardizeMissing:DataVariablesTableSubscript'));
        end

    else
        args = varargin;

    end

    b = codistributed.pElementwiseUnaryOp(@standardizeMissing, a, args{:});
end
end

function appending = iIsAppendingVars(args)
% We don't need to check the args - they have already been validated. Just
% see if "ReplaceValues" was specified as false.
appending = false;
idx = find(cellfun(@(x) iIsParam(x,"ReplaceValues"), args), 1);
if ~isempty(idx) && idx<numel(args)
    appending = args{idx+1} == 0;
end

end

function tf = iIsParam(arg, paramName)
tf = matlab.internal.datatypes.isScalarText(arg) ...
    && startsWith(paramName, arg, "IgnoreCase", true);
end

function args = iGetDataVariables(t,args)
% Determine which variables to operate on. We also strip DataVariables from
% the list of arguments.
idx = find( cellfun( @matchesDataVariables, args ), 1, 'last' );
if isempty(idx) || idx>=numel(args)
    % Not specified or no value. Leave args in place.
    return
end

selectedVars = args{idx+1};
tmpl = getTableTemplate(t);
if isa(selectedVars, "vartype")
    varIdxs = vartype2idx(tmpl, selectedVars);
else
    allVars = tmpl.Properties.VariableNames;
    [~,varIdxs] = distributedutil.lookupTableVars(selectedVars, allVars);
end

% Now work out which ones are on this worker
codistr = getCodistributor(t);
if spmdIndex == 1
    first = 0;
else
    first = sum(codistr.Partition(1:spmdIndex-1));
end
myVarIdxs = first + (1:codistr.Partition(spmdIndex));
validIdxs = intersect(varIdxs, myVarIdxs);

% Subtract the previous worker variables to get the indices into the local
% part.
args{idx+1} = validIdxs - first;
end

function tf = matchesDataVariables(in)
% True if the input is a string that matches DataVariables (partial,
% case-insensitive)
if isempty(in) || ~(ischar(in) || (isstring(in) && isscalar(in)))
    tf = false;
    return;
end

tf = strncmpi(in,'DataVariables',min(13,length(in)));
end
