function IA = ismissingKernel(A,indicators,stdize,isTable)
%MATLAB Code Generation Library Function

% Copyright 2018-2022 The MathWorks, Inc.
%#codegen

% ismissingKernel Helper function for ismissing and standardizeMissing
%
% IA is either a logical array (the output of ismissing) or an array
% containing standardized entries.

coder.internal.prefer_const(stdize);
coder.internal.prefer_const(isTable);

% Check inputs again in case they come from a table
if isTable && ~(isnumeric(A) || islogical(A) || ischar(A) || ...
        iscategorical(A) || isduration(A) || iscell(A) || isstring(A) || ...
        isdatetime(A))
    coder.internal.assert(isempty(indicators) || ~stdize, 'MATLAB:ismissing:InvalidInputsCodegen');  
    IA = false(size(A));
    return
end

if iscell(A) && ~isempty(A)
    validateattributes(A{1},{'char'},{});
    % Testing the first element does not guarantee A is a cellstr but this
    % is only a failsafe in case a calling function does not check
    % correctly
end
coder.internal.assert(~isstring(A) || (nargin > 1 && ~isempty(stdize)), 'MATLAB:ismissing:StringNeedsIndicatorCodegen');
if nargin <= 1 || isempty(stdize)
    % ismissing(A)
    if isTable
        IA = collapseIntoLogicalColumn(arraySwitch(A,isTable));
    else
        IA= arraySwitch(A,isTable);
    end
else
    % ismissing(A,indicators)
    % standardizeMissing(A,indicators)
    if isTable
        if ~iscell(indicators)
            indCell = {indicators};
        else
            indCell = indicators;
        end
        if stdize
            IA = A;
        else
            IA = false(size(A,1),1);
        end
        for ii = 1:numel(indCell)
            [indstruct,indout] = parseIndicators(indCell{ii});
            if checkClassMatchesInd(class(A),indstruct, class(indCell{ii}))
                if stdize
                    IA = arraySwitchIndicators(IA,isTable,indout,indstruct,stdize);
                else
                    IA = IA | collapseIntoLogicalColumn(arraySwitchIndicators(A,isTable,indout,indstruct,stdize));
                end
            end
        end
    else
        [indstruct,indout] = parseIndicators(indicators);
        IA = arraySwitchIndicators(A,isTable,indout,indstruct,stdize);
    end
end

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

function IA = arraySwitch(A,isTable)
% Used for 1-input ismissing for arrays
if isfloat(A) || isa(A,'duration')
    IA = isnan(A);
elseif ischar(A)
    if isTable
        IA = all(A == ' ',2); % blank char row
    else
        IA = false(size(A));
    end
elseif isa(A,'categorical')
    IA = isundefined(A);
elseif iscellstr(A) %#ok<ISCLSTR>
    IA = ismissingCellstr(A,''); % empty char
elseif isa(A,'datetime')
    IA = isnat(A);
else  %(isinteger(A) || islogical(A))
    IA = false(size(A));
end

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

function IA = arraySwitchIndicators(A,isTable,indicators,ind,stdize)
% Indicators-based ismissing and standardizeMissing for arrays
if isa(A,'double')
    IA = ismissingApply(A,indicators,ind,stdize,NaN,@ismissingNumeric,'Double');
elseif isa(A,'single')
    IA = ismissingApply(A,indicators,ind,stdize,NaN,@ismissingNumeric,'Single');
elseif ischar(A)
    if stdize && ~isTable
        IA = A; % Flow-through. Cannot standardize char arrays.
    else
        IA = ismissingApply(A,indicators,ind,stdize,' ',@ismissingChar,'Char');
    end
elseif isa(A,'categorical')
    IA = ismissingApply(A,indicators,ind,stdize,categorical(0,1:numel(categories(A)),categories(A),'Ordinal',isordinal(A)),@ismissingCategorical,'Categorical');
elseif isa(A,'duration')
    IA = ismissingApply(A,indicators,ind,stdize,milliseconds(nan),@ismissingDuration,'Duration');
elseif isstring(A)
    IA = ismissingApply(A,indicators,ind,stdize,[],@ismissingString,'String');
elseif iscellstr(A)
    emptyChar = char(zeros(0,0));
    IA = ismissingApply(A,indicators,ind,stdize,emptyChar,@ismissingCellstr,'Cellstr');
elseif isa(A,'datetime')
    IA = ismissingApply(A,indicators,ind,stdize,NaT,@ismissingDatetime,'Datetime');
else %(isinteger(A) || islogical(A))
    % Integer arrays match to double indicators or to integer indicators of
    % exactly the same type of integer -- same behavior as ISMEMBER.
    coder.internal.assert(ind.IntegerLogical == true,...
        'MATLAB:ismissing:IndicatorsIntegerLogical',class(A));
    coder.internal.assert(isa(indicators,'double') || isa(indicators,class(A)),...
        'MATLAB:ismissing:IndicatorsIntegerLogical',class(A));
    if ~stdize
        IA = ismissingNumeric(A,indicators);
    else
        IA = A; % Flow-through. Cannot standardize integer/logical arrays.
    end
end

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

function IA = ismissingApply(A,indicators,ind,stdize,missingValue,ismissingFun,typeid)
% Apply ismissing with indicators for arrays
coder.internal.assert(ind.(typeid) == true,['MATLAB:ismissing:Indicators',typeid]);

ONE = coder.internal.indexInt(1);
% Indicators of compatible type with A were provided
if ~stdize
    % IA = ismissing(A,indicators)
    IA = ismissingFun(A,indicators);
else
    % IA = standardizeMissing(A,indicators)
    IA = A;
    if iscell(A)
        tf = ismissingFun(A,indicators);
        for ii = ONE:numel(A)
            if tf(ii)
                IA{ii} = missingValue;
            end
        end
    else
        IA(ismissingFun(A,indicators)) = missingValue;
    end
end

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

function [indstruct,indout] = parseIndicators(ind)
% Separate indicators according to the array type they are compatible with
coder.internal.assert(isnumeric(ind) || islogical(ind) || ischar(ind) ||...
    isa(ind,'duration') || iscellstr(ind) || isstring(ind) || ...
    isa(ind,'datetime') ,'MATLAB:ismissing:IndicatorsInvalidType',class(ind));

coder.internal.assert(~issparse(ind), 'Coder:toolbox:SparseNotSupported',mfilename);
if (isnumeric(ind) || islogical(ind))
    indout = reshape(ind,[],1);
    if isa(ind,'double')
        % Double indicators supported for all numeric and logical arrays
        indstruct = struct( ...
            'Double',true, ...
            'Single',true, ...
            'IntegerLogical',true, ...
            'Char',false, ...
            'Categorical',false, ...
            'Duration',false, ...
            'String',false, ...
            'Cellstr',false, ...
            'Datetime',false ...
            );
    elseif isa(ind,'single')
        % Single indicators supported only for double and single arrays
        indstruct = struct( ...
            'Double',true, ...
            'Single',true, ...
            'IntegerLogical',false, ...
            'Char',false, ...
            'Categorical',false, ...
            'Duration',false, ...
            'String',false, ...
            'Cellstr',false, ...
            'Datetime',false ...
            );
    else
        % Integer indicators supported only for double and integer arrays
        indstruct = struct( ...
            'Double',true, ...
            'Single',false, ...
            'IntegerLogical',true, ...
            'Char',false, ...
            'Categorical',false, ...
            'Duration',false, ...
            'String',false, ...
            'Cellstr',false, ...
            'Datetime',false ...
            );
    end
elseif isa(ind,'duration')
    indout = reshape(ind,[],1);
    indstruct = struct( ...
        'Double',false, ...
        'Single',false, ...
        'IntegerLogical',false, ...
        'Char',false, ...
        'Categorical',false, ...
        'Duration',true, ...
        'String',false, ...
        'Cellstr',false, ...
        'Datetime',false ...
        );
elseif iscellstr(ind)
    indout = reshape(ind,[],1);
    indstruct = struct( ...
        'Double',false, ...
        'Single',false, ...
        'IntegerLogical',false, ...
        'Char',false, ...
        'Categorical',true, ...
        'Duration',false, ...
        'String',false, ...
        'Cellstr',true, ...
        'Datetime',false ...
        );
elseif isstring(ind)
    indout = reshape(ind,[],1);
    indstruct = struct( ...
        'Double',false, ...
        'Single',false, ...
        'IntegerLogical',false, ...
        'Char',false, ...
        'Categorical',false, ...
        'Duration',false, ...
        'String',true, ...
        'Cellstr',false, ...
        'Datetime',false ...
        );
elseif isa(ind,'datetime')
    indout = reshape(ind,[],1);
    indstruct = struct( ...
        'Double',false, ...
        'Single',false, ...
        'IntegerLogical',false, ...
        'Char',false, ...
        'Categorical',false, ...
        'Duration',false, ...
        'String',false, ...
        'Cellstr',false, ...
        'Datetime',true ...
        );
else % ischar(ind)
    indout = reshape(ind,[],1);
    coder.internal.assert(coder.internal.isTextRow(ind),'MATLAB:ismissing:IndicatorsCharRowVector');
    indstruct = struct( ...
        'Double',false, ...
        'Single',false, ...
        'IntegerLogical',false, ...
        'Char',true, ...
        'Categorical',true, ...
        'Duration',false, ...
        'String',false, ...
        'Cellstr',true, ...
        'Datetime',false ...
        );
end

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

function IA = ismissingNumeric(A,ind)
% A is numeric or logical, ind is numeric or logical
coder.noImplicitExpansionInFunction; % use of | below with same size operands: all have size(A)
IA = false(size(A));
mi = isnan(ind);
if any(mi)
    IA = IA | ismember(A,ind(~mi)) | isnan(A);
else
    IA = IA | ismember(A,ind);
end

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

function IA = ismissingChar(A,ind)
% A and ind are char arrays
indColumn = ind(:);
IA = ismember(A,indColumn);

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

function IA = ismissingCategorical(A,ind)
% A is categorical, ind is cellstr
ONE = coder.internal.indexInt(1);
hasUndefInd = false;
IA = coder.nullcopy(false(size(A)));

if iscell(ind)
    for ii = ONE:numel(ind)
        hasUndefInd = isempty(ind{ii})||strcmp(categorical.undefLabel,ind{ii});
        if hasUndefInd
            break;
        end
    end
else
    hasUndefInd = isempty(ind)||strcmp(categorical.undefLabel,ind);
end
cats = categories(A);

%Codegen only supports comparison between a
% categorical and another categorical
if iscell(ind)
    catIndTmp = categorical(ind,cats,'Ordinal',isordinal(A));
else
    catIndTmp = categorical({ind},cats,'Ordinal',isordinal(A));
end

catInd = catIndTmp(~isundefined(catIndTmp));
if isempty(catInd)
    IA = false(size(A));
    return
end


for ii = ONE:numel(A)
    IA(ii) = false;
    for jj = ONE:numel(catInd)
        if hasUndefInd
            IA(ii) = A(ii)==catInd(jj) || isundefined(A(ii));
        else
            IA(ii) = A(ii)==catInd(jj);
        end
        if IA(ii)
            break
        end
    end
end

if hasUndefInd
    for ii = ONE:numel(A)
        IA(ii) = false;
        for jj = ONE:numel(catInd)
            IA(ii) = A(ii)==catInd(jj) || isundefined(A(ii));
            if IA(ii)
                break
            end
        end
    end
else
    for ii = ONE:numel(A)
        IA(ii) = false;
        for jj = ONE:numel(catInd)
            IA(ii) = A(ii)==catInd(jj);
            if IA(ii)
                break
            end
        end
    end
end

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

function IA = ismissingDuration(A,ind)
% A and ind are durations
ONE = coder.internal.indexInt(1);
IA = coder.nullcopy(false(size(A)));
hasNaNs = false;
for ii = ONE:numel(ind)
    if isnan(ind(ii))
        hasNaNs = true;
        break
    end
end
% ismember not supported for durations in codegen
if hasNaNs
    for ii = ONE:numel(A)
        for jj = ONE:numel(ind)
            IA(ii) = A(ii)==ind(jj) || isnan(A(ii));
            if(IA(ii))
                break
            end
        end
    end
else
    for ii = ONE:numel(A)
        for jj = ONE:numel(ind)
            IA(ii) = A(ii)==ind(jj);
            if(IA(ii))
                break
            end
        end
    end
end

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

function IA = ismissingString(A,ind)
% A and ind are durations
ONE = coder.internal.indexInt(1);
IA = coder.nullcopy(false(size(A)));
for ii = ONE:numel(A)
    IA(ii) = strcmp(A(ii),ind);
end

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

function IA = ismissingCellstr(A,ind)
% A is a cellstr and ind is a char or a cellstr
ONE = coder.internal.indexInt(1);
IA = coder.nullcopy(false(size(A)));
if ischar(ind)
    for ii = ONE:numel(A)
        if isempty(ind)
            IA(ii) = isempty(A{ii});
        else
            IA(ii) = strcmp(A{ii},ind);
        end
    end
else
    for ii = ONE:numel(A)
        for jj = ONE:numel(ind)
            if isempty(ind{jj})
                IA(ii) = isempty(A{ii});
            else
                IA(ii) = strcmp(A{ii},ind{jj});
            end
            if IA(ii)
                break
            end
        end
    end
end

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

function IA = ismissingDatetime(A,ind)
% A and ind are datetimes
ONE = coder.internal.indexInt(1);
IA = coder.nullcopy(false(size(A)));
hasNaTs = false;
for ii = ONE:numel(ind)
    if isnat(ind(ii))
        hasNaTs = true;
        break
    end
end
% ismember not supported for datetimes in codegen
if hasNaTs
    for ii = ONE:numel(A)
        for jj = ONE:numel(ind)
            IA(ii) = A(ii)==ind(jj) || isnat(A(ii));
            if(IA(ii))
                break
            end
        end
    end
else
    for ii = ONE:numel(A)
        for jj = ONE:numel(ind)
            IA(ii) = A(ii)==ind(jj);
            if(IA(ii))
                break
            end
        end
    end
end

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

function tf = checkClassMatchesInd(classA,indstruct,classInd)
switch classA
    case 'double'
        tf = indstruct.Double;
    case 'single'
        tf = indstruct.Single;
    case 'char'
        tf = indstruct.Char;
    case 'categorical'
        tf = indstruct.Categorical;
    case 'duration'
        tf = indstruct.Duration;
    otherwise % integer or logical
        % the indstruct.IntegerLogical flag is not sufficient here
        tf = strcmp(classInd,classA) || strcmp(classInd,'double');
end
