/*
    GNU Octave level-set package.
    Copyright (C) 2014  Daniel Kraft <d@domob.eu>

    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/>.
*/

/* Calculate the elements-related things of the information returned
   by ls_find_geometry.  */

#include "Utils.hpp"

#include <octave/oct.h>
#include <octave/ov-struct.h>

#include <cassert>
#include <vector>
#include <stdexcept>

/** Vector of indices used for the *indx fields.  */
typedef std::vector<unsigned> indexArr;

/* C++ implementation.  */
DEFUN_DLD (__levelset_geomElements, args, nargout,
  "  ELEMENTS = geomElements (PHI)\n\n"
  "Construct ELEM structure as per ls_find_geometry for the given level-set\n"
  "function PHI.  PHI must not contain values which are exactly zero.\n")
{
  try
    {
      if (args.length () != 1 || nargout > 1)
        throw std::runtime_error ("invalid argument counts");

      /* Get argument.  */
      const Matrix phi = args(0).matrix_value ();

      /* Extract the dimensions.  */
      const dim_vector dims = phi.dims ();
      const unsigned L = dims(0);
      const unsigned M = dims(1);

      /* Go over all elements to build the nodelist.  */
      const unsigned nElem = (L - 1) * (M - 1);
      Matrix nodelist(nElem, 4);
      for (unsigned row = 0; row < L - 1; ++row)
        for (unsigned col = 0; col < M - 1; ++col)
          {
            const unsigned elInd = col * (L - 1) + row;

            /* The element nodes are stored in order SE, SW, NW, NE.  */
#define STORE_NODELIST(num, dr, dc) \
  nodelist(elInd, num) = (col + (dc)) * L + (row + (dr)) + 1
            STORE_NODELIST(0, 1, 1);
            STORE_NODELIST(1, 1, 0);
            STORE_NODELIST(2, 0, 0);
            STORE_NODELIST(3, 0, 1);
#undef STORE_NODELIST
          }

      /* Find outer/inner/bdry elements.  */
      indexArr outindx, inindx, bdryindx;
      ColumnVector type(nElem);
      for (unsigned i = 0; i < (L - 1) * (M - 1); ++i)
        {
          bool hasOut = false;
          bool hasIn = false;
          for (unsigned j = 0; j < 4; ++j)
            {
              const unsigned nodeInd = nodelist(i, j) - 1;
              if (phi(nodeInd) > 0.0)
                hasOut = true;
              else
                {
                  assert (phi(nodeInd) < 0.0);
                  hasIn = true;
                }
            }

          if (hasOut && hasIn)
            {
              type(i) = 0.0;
              bdryindx.push_back (i + 1);
            }
          else if (hasOut)
            {
              type(i) = 1.0;
              outindx.push_back (i + 1);
            }
          else
            {
              assert (hasIn);
              type(i) = -1.0;
              inindx.push_back (i + 1);
            }
        }

      /* Build result structure.  */
      octave_scalar_map indx;
      indx.assign ("inner", convertToColumnVector (inindx));
      indx.assign ("outer", convertToColumnVector (outindx));
      indx.assign ("bdry", convertToColumnVector (bdryindx));
      octave_scalar_map st;
      st.assign ("n", nElem);
      st.assign ("nodelist", nodelist);
      st.assign ("type", type);
      st.assign ("index", indx);

      /* Return.  */
      octave_value_list res;
      res(0) = st;

      return res;
    }
  catch (const std::runtime_error& exc)
    {
      error (exc.what ());
      return octave_value_list ();
    }
}
