## Copyright (C) 1995-2023 The Octave Project Developers
##
## This file is part of the statistics package for GNU Octave.
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
## Foundation; either version 3 of the License, or (at your option) any later
## version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public License along with
## this program; if not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {statistics} {@var{TF} =} ismissing (@var{A})
## @deftypefnx {statistics} {@var{TF} =} ismissing (@var{A}, @var{indicator})
##
## Find missing data in a numeric or string array.
##
## Given an input numeric data array, char array, or array of cell strings
## @var{A}, @code{ismissing} returns a logical array @var{TF} with
## the same dimensions as @var{A}, where @code{true} values match missing
## values in the input data.
##
## The optional input @var{indicator} is an array of values that represent
## missing values in the input data.  The values which represent missing data
## by default depend on the data type of @var{A}:
##
## @itemize
## @item
## @qcode{NaN}: @code{single}, @code{double}.
##
## @item
## @qcode{' '} (white space): @code{char}.
##
## @item
## @qcode{@{''@}}: string cells.
## @end itemize
##
## Note: logical and numeric data types may be used in any combination
## for @var{A} and @var{indicator}. @var{A} and the indicator values will be
## compared as type double, and the output will have the same class as @var{A}.
## Data types other than those specified above have no defined 'missing' value.
## As such, the TF output for those inputs will always be
## @code{false(size(@var{A}))}. The exception to this is that @var{indicator}
## can be specified for logical and numeric inputs to designate values that
## will register as 'missing'.
##
## @seealso{fillmissing, rmmissing, standardizeMissing}
## @end deftypefn

function TF = ismissing (A, indicator)

  if (nargin < 1) || (nargin > 2)
    print_usage ();
  endif

  ## check "indicator"
  if (nargin != 2)
     indicator = [];
  endif

  ## if A is an array of cell strings and indicator just a string,
  ## convert indicator to a cell string with one element
  if (iscellstr (A) && ischar (indicator) && ! iscellstr (indicator))
    indicator = {indicator};
  endif

  if ((! isempty (indicator)) &&
      ((isnumeric (A) && ! (isnumeric (indicator) || islogical (indicator))) ||
       (ischar (A) && ! ischar (indicator)) ||
       (iscellstr (A) && ! (iscellstr (indicator)))))
    error ("ismissing: 'indicator' and 'A' must have the same data type");
  endif

  ## main logic
  if (isempty (indicator))

    if (isnumeric (A))
      ## numeric matrix: just find the NaNs
      ## integer types have no missing value, but isnan will return false
      TF = isnan (A);

    elseif (iscellstr (A))
      ## cell strings - find empty cells
      TF = cellfun ('isempty', A);

    elseif (ischar (A))
      ## char matrix: find the white spaces
      TF = isspace (A);

    else
      ##no missing type defined, return false
      TF = false (size (A));
    endif

  else
    ## indicator specified for missing data
    TF = false (size (A));
    if (isnumeric(A) || ischar (A) || islogical (A))
      for iter = 1 : numel (indicator)
        if (isnan (indicator(iter)))
          TF(isnan(A)) = true;
        else
          TF(A == indicator(iter)) = true;
        endif
      endfor
    elseif (iscellstr (A))
      for iter = 1 : numel (indicator)
        TF(strcmp (A, indicator(iter))) = true;
      endfor
    else
      error ("ismissing: indicators not supported for data type '%s'", ...
               class(A));
    endif
  endif
endfunction

%!assert (ismissing ([1,NaN,3]), [false,true,false])
%!assert (ismissing ('abcd f'), [false,false,false,false,true,false])
%!assert (ismissing ({'xxx','','xyz'}), [false,true,false])
%!assert (ismissing ({'x','','y'}), [false,true,false])
%!assert (ismissing ({'x','','y';'z','a',''}), logical([0,1,0;0,0,1]))
%!assert (ismissing ([1,2;NaN,2]), [false,false;true,false])
%!assert (ismissing ([1,2;NaN,2], 2), [false,true;false,true])
%!assert (ismissing ([1,2;NaN,2], [1 2]), [true,true;false,true])
%!assert (ismissing ([1,2;NaN,2], NaN), [false,false;true,false])

## test nD array data
%!assert (ismissing (cat(3,magic(2),magic(2))), logical (zeros (2,2,2)))
%!assert (ismissing (cat(3,magic(2),[1 2;3 NaN])), logical (cat(3,[0,0;0,0],[0,0;0,1])))
%!assert (ismissing ([1 2; 3 4], [5 1; 2 0]), logical([1 1; 0 0]))
%!assert (ismissing (cat(3,'f oo','ba r')), logical(cat(3,[0 1 0 0],[0 0 1 0])))
%!assert (ismissing (cat(3,{'foo'},{''},{'bar'})), logical(cat(3,0,1,0)))

## test data type handling
%!assert (ismissing (double (NaN)), true)
%!assert (ismissing (single (NaN)), true)
%!assert (ismissing (' '), true)
%!assert (ismissing ({''}), true)
%!assert (ismissing ({' '}), false)
%!assert (ismissing (double (eye(3)), single (1)), logical(eye(3)))
%!assert (ismissing (double (eye(3)), true), logical(eye(3)))
%!assert (ismissing (double (eye(3)), int32 (1)), logical(eye(3)))
%!assert (ismissing (single (eye(3)), true), logical(eye(3)))
%!assert (ismissing (single (eye(3)), double (1)), logical(eye(3)))
%!assert (ismissing (single(eye(3)), int32 (1)), logical(eye(3)))

## test data types without missing values
%!assert (ismissing ({'123', '', 123}), [false false false])
%!assert (ismissing (logical ([1 0 1])), [false false false])
%!assert (ismissing (int32 ([1 2 3])), [false false false])
%!assert (ismissing (uint32 ([1 2 3])), [false false false])
%!assert (ismissing ({1, 2, 3}), [false false false])
%!assert (ismissing ([struct struct struct]), [false false false])
%!assert (ismissing (logical (eye(3)), true), logical(eye(3)))
%!assert (ismissing (logical (eye(3)), double (1)), logical(eye(3)))
%!assert (ismissing (logical (eye(3)), single (1)), logical(eye(3)))
%!assert (ismissing (logical (eye(3)), int32 (1)), logical(eye(3)))
%!assert (ismissing (int32 (eye(3)), int32 (1)), logical(eye(3)))
%!assert (ismissing (int32 (eye(3)), true), logical(eye(3)))
%!assert (ismissing (int32 (eye(3)), double (1)), logical(eye(3)))
%!assert (ismissing (int32 (eye(3)), single (1)), logical(eye(3)))

## test empty input handling
%!assert (ismissing ([]), logical([]))
%!assert (ismissing (''), logical([]))
%!assert (ismissing (ones (0,1)), logical(ones(0,1)))
%!assert (ismissing (ones (1,0)), logical(ones(1,0)))
%!assert (ismissing (ones (1,2,0)), logical(ones(1,2,0)))

## Test input validation
%!error ismissing ()
%!error <'indicator' and 'A' must have the same> ismissing ([1 2; 3 4], "abc")
%!error <'indicator' and 'A' must have the same> ismissing ({"", "", ""}, 1)
%!error <'indicator' and 'A' must have the same> ismissing (1, struct)
%!error <indicators not supported for data type> ismissing (struct, 1)
