## Copyright (C) 2020 Adam H. Aitkenhead
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## This file is part of Octave.
##
## Octave 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.
##
## Octave 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 Octave; see the file COPYING.  If not, see
## <https://www.gnu.org/licenses/>.

## -*- texinfo -*- 
## @deftypefn {Function File} {[@var{f1}] =} freqspace (@var{n})
## @deftypefnx {Function File} {[@var{f1, f2, f3, ...}] =} freqspace (@var{n})
## @deftypefnx {Function File} {[@var{f1, f2, f3, ...}] =} freqspace ([@var{m, n, o, ...}])
## @deftypefnx {Function File} {[@var{f1}] =} freqspace (@var{n, 'whole'})
## @deftypefnx {Function File} {[@var{f1, f2}] =} freqspace (@var{[m, n], 'meshgrid'})
##
## @code{freqspace} returns a vector @var{f1} of @var{n} evenly spaced values.
##
## If @var{n} is even, the returned vector @var{f1} contains the values: [-1 : 2/n : (n-2)/n].
##
## If @var{n} is odd, the returned vector @var{f1} contains the values: [(1-n)/n : 2/n : (n-1)/n].
##
## If multiple outputs @var{f1,f2,f3...} are requested for a single input @var{n}, then the returned vectors
## @var{f1,f2,f3,...} are all identical.
##
## If multiple outputs @var{f1,f2,f3...} are requested for multiple inputs [@var{m,n,o...}], then each
## returned vector @var{f1,f2,f3...} corresponds to one input @var{m,n,o...}.
##
## If the option 'whole' is applied, the returned vector @var{f1} contains the values: [0 : 2/n : 2*(n-1)/n].
##
## If the option 'meshgrid' is applied, @code{freqspace} applies @code{meshgrid} to the outputs,
## returning @var{f1,f2,f3,...} as arrays rather than vectors.  The first two dimensions of the returned
## arrays @var{f1,f2,f3,...} are ordered according to @code{meshgrid}, and not as they would be ordered
## by @code{ndgrid}.
##
## Note that [@var{m,n,o...}] must have integer values, but may be of class single or double.
##
## @seealso{meshgrid}
## @end deftypefn

## Author: Adam H. Aitkenhead <adamaitkenhead@hotmail.com>

function varargout = freqspace (varargin)

  # Check the number of input arguments
  if (sum (cellfun (@isnumeric, varargin))!=1)
    error ('freqspace: Unsupported number of input arguments')
  endif

  # Check the number of output arguments
  ncount = numel (varargin{1});
  if (ncount>1 && nargout!=ncount)
    error ('freqspace: Number of output arguments does not match the given inputs')
  endif

  # Check that the numeric inputs have integer values
  if (any (mod (varargin{1}, 1)))
    error ('freqspace: Numeric inputs must have integer values')
  endif

  # Check that all string input arguments are either 'whole' or 'meshgrid'
  if (nargin>1) && (!all (ismember (varargin(2:end), {'whole', 'meshgrid'})))
    error ('freqspace: Supported string inputs are ''whole'' and ''meshgrid''')
  endif

  # Generate the outputs F1, F2...
  for i = 1:ncount
    n = varargin{1}(i);
    if (mod (n, 2) == 0)   # n is even
      varargout{i} = [-1 : 2/n : (n-2)/n];
    else                   # n is odd
      varargout{i} = [(1-n)/n : 2/n : (n-1)/n];
    endif
  endfor

  # Duplicate the output arguments if nargout>1 and ncount==1
  if (nargout>ncount)
    for i = 2:nargout
      varargout{i} = varargout{1};
    endfor
  endif

  # Option: 'whole'
  if (nargin>1) && ...
     (all (cellfun (@ischar, varargin(2:end)))) && ....
     (any (cellfun ('strcmp', varargin(2:end), {'whole'} )))
    minout = cellfun (@min, varargout);
    for i = 1:nargout
      varargout{i} = varargout{i} - minout(i);
    endfor
  endif

  # Option: 'meshgrid'
  if (nargin>1) && ...
     (all (cellfun (@ischar, varargin(2:end)))) && ...
     (any (cellfun ('strcmp', varargin(2:end), {'meshgrid'} )))
    [varargout{:}] = ndgrid (varargout{:});
    for i = 1:nargout
      varargout{i} = permute (varargout{i}, ...
                             [2,1,3:max(3, ndims (varargout{i}))]);
    endfor
  endif

endfunction


%!test
%! x = 2;
%! [XX, YY] = freqspace (x);
%! assert (numel (XX), x);
%! assert (numel (YY), x);

%!test
%! x = 2;
%! y = 3;
%! [XX, YY] = freqspace ([x, y]);
%! assert (numel (XX), x);
%! assert (numel (YY), y);

%!test
%! x = 2;
%! y = 3;
%! [XX, YY] = freqspace ([x, y], 'meshgrid');
%! assert (size (XX), [3,2]);
%! assert (size (YY), [3,2]);

%!test
%! x = 2;
%! y = 3;
%! [XX, YY] = freqspace ([x, y], 'whole');
%! assert (XX(1), 0);
%! assert (YY(1), 0);

%!test
%! x = 2;
%! y = 3;
%! [XX, YY] = freqspace ([x, y], 'whole', 'meshgrid');
%! assert (size (XX), [3,2]);
%! assert (size (YY), [3,2]);
%! assert (XX(1), 0);
%! assert (YY(1), 0);


## Test input validation

%!error <freqspace: Unsupported number of input arguments> freqspace (2,3);
%!error <freqspace: Number of output arguments does not match the given inputs> [XX] = freqspace ([2, 3]);
%!error <freqspace: Number of output arguments does not match the given inputs> [XX, YY] = freqspace ([2, 3, 4]);
%!error <freqspace: Number of output arguments does not match the given inputs> [XX, YY, ZZ] = freqspace ([2, 3]);
%!error <freqspace: Numeric inputs must have integer values> [XX] = freqspace ([2.5])
%!error <freqspace: Supported string inputs are 'whole' and 'meshgrid'> [XX, YY] = freqspace ([2, 3], 'test')

