##  Copyright (C) 2014  Daniel Kraft <d@domob.eu>
##  GNU Octave level-set package.
##
##  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  {Function File} {@var{d} =} ls_nb_from_geom (@var{geom}, @var{phi})
## @deftypefnx {Function File} {[@var{d}, @var{g}] =} ls_nb_from_geom (@var{geom}, @var{phi}, @var{g0})
## 
## Initialise the narrow-band values utilising information about the
## geometry.  @var{geom} should be the geometry according to
## @code{ls_find_geometry}, and it must contain absolute coordinates
## as per @code{ls_absolute_geom} in addition.  Thus it is unnecessary
## to pass the grid spacing, as this information is already
## contained in the absolute coordinates.
## This is an alternative routine to the standard
## @code{ls_init_narrowband} for 2D situations.
##
## This function sets the distance at @emph{each} node of a boundary
## element to the shortest distance to any boundary edge
## according to the geometry information in @var{geom}.  In contrast
## to @code{ls_init_narrowband}, this also initialises values on the
## ``far diagonally away'' point.  This makes the result more accurate.
##
## In the second form, @var{g0} is expected to contain the values of some
## function defined on each boundary edge (ordered in the same way as
## @code{@var{geom}.bedges}).  These function values will be
## extended onto narrow-band points as well, and returned in @var{g}.
##
## @seealso{ls_init_narrowband, ls_find_geometry, ls_absolute_geom,
## ls_solve_stationary, fastmarching}
## @end deftypefn

function [d, g] = ls_nb_from_geom (geom, phi, g0 = NA)
  if (nargin () < 2 || nargin () > 3)
    print_usage ();
  endif

  hasG = (nargin () == 3);
  if (hasG)

    % Turn row vector into column.
    if (size (g0, 1) == 1)
      g0 = g0';
    endif

    if (size (g0, 2) != 1)
      error ("G0 should be a vector");
    endif
    if (length (g0) != geom.bedges.n)
      error ("G0 has wrong size");
    endif
  else
    if (nargout () > 1)
      print_usage ();
    endif
  endif

  if (!isfield (geom.ispts, "coord") || !isfield (geom, "nodes"))
    error ("GEOM misses absolute coordinates, use ls_absolute_geom first");
  endif

  [d, gSum, gCnt] = __levelset_nbFromGeom (geom.bedges.ispts, phi, hasG, g0, ...
                                           geom.elem.nodelist, ...
                                           geom.elem.index.bdry, ...
                                           geom.ispts.onedge(:, :, 1), ...
                                           geom.nodes.coord, geom.ispts.coord);

  d = ls_copy_sign (d, phi);
  if (hasG)
    g = gSum ./ gCnt;
  endif
endfunction

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Tests.

% Test for error conditions.
%!error <Invalid call to>
%!  ls_nb_from_geom (1)
%!error <Invalid call to>
%!  ls_nb_from_geom (1, 2, 3, 4)
%!error <Invalid call to>
%!  [a, b] = ls_nb_from_geom (1, 2);
%!error <G0 should be a vector>
%!  ls_nb_from_geom (1, 2, rand (2, 2));

% Test for missing absolute coordinates.
%!error <GEOM misses absolute coordinates, use ls_absolute_geom first>
%!  x = linspace (-10, 10, 10);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!  phi = ls_genbasic (XX, YY, "sphere", [0, 0], 5);
%!  phi = ls_normalise (phi, h);
%!  geom = ls_find_geometry (phi, h);
%!  ls_nb_from_geom (geom, phi);

% Test for size check on g0.
%!error <G0 has wrong size>
%!  x = linspace (-10, 10, 10);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!  phi = ls_genbasic (XX, YY, "sphere", [0, 0], 5);
%!  phi = ls_normalise (phi, h);
%!  geom = ls_find_geometry (phi, h);
%!  ls_nb_from_geom (geom, phi, [1, 2, 3]);

% Compare (with some tolerance) to ls_init_narrowband.
%!test
%!  n = 50;
%!  x = linspace (-10, 10, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi = ls_setdiff (ls_genbasic (XX, YY, "sphere", [0, 0], 8), ...
%!                    ls_genbasic (XX, YY, "sphere", [1, 1], 5));
%!  phi = ls_normalise (phi, h);
%!  geom = ls_find_geometry (phi, h);
%!  geom = ls_absolute_geom (geom, XX, YY);
%!
%!  dOld = ls_init_narrowband (phi, h);
%!  dNew = ls_nb_from_geom (geom, phi);
%!
%!  where = (!isna (dOld));
%!  assert (all (!isna (dNew(where))));
%!  assert (dNew(where), dOld(where), h / 4);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Demos.

% TODO: Demo comparing different initialisations.

% Try a basic extension of function values.
%!demo
%!  n = 50;
%!  x = linspace (-10, 10, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi = ls_genbasic (XX, YY, "sphere", [0, 0], 3);
%!  phi = ls_normalise (phi, h);
%!  geom = ls_find_geometry (phi, h);
%!  geom = ls_absolute_geom (geom, XX, YY);
%!
%!  g0 = NA (1, geom.bedges.n);
%!  for i = 1 : geom.bedges.n
%!    a = geom.ispts.coord(geom.bedges.ispts(i, 1), :);
%!    b = geom.ispts.coord(geom.bedges.ispts(i, 2), :);
%!    m = (a + b) / 2;
%!    angle = abs (atan2 (m(2), m(1)));
%!    g0(i) = min (angle, 2 * pi - angle);
%!  endfor
%!
%!  [d, g] = ls_nb_from_geom (geom, phi, g0);
%!  [d, g] = fastmarching (d, g, h * ones (size (phi)));
%!
%!  figure ();
%!  hold ("on");
%!  imagesc (x, x, g);
%!  set (gca (), "ydir", "normal");
%!  contour (XX, YY, phi, [0, 0], "k");
%!  hold ("off");
%!  colorbar ();
