/*

Copyright (C) 2010-2019 Olaf Till <i7tiol@t-online.de>

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

*/

// Code of the files of Octave-3.2.4 src/data.cc (do_cat()), src/ov.cc
// (do_cat_op ()), src/OPERATORS/op-struct.cc, src/ops.h, and
// oct-map.cc (Octave_map::concat ()) has been studied and initially
// re-used, but in the end it has been made differently here.

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

#include "error-helpers.h"

static octave_map
structcat_op_fcn (const octave_map& m1, const octave_map& m2,
                      const dim_vector& dv,
                      const Array<octave_idx_type>& ra_idx,
                      const octave_value& fillv)
{
  octave_map retval (dv);

  Cell c2 (m2.dims (), fillv);

  for (octave_map::const_iterator pa = m1.begin (); pa != m1.end (); pa++)
    {
      Cell c (dv);

      c.insert (m1.contents(pa), 0, 0);

      octave_map::const_iterator pb = m2.seek (m1.key(pa));

      if (pb == m2.end ())
        c.insert (c2, ra_idx);
      else
        c.insert (m2.contents(pb), ra_idx);

      retval.assign (m1.key(pa), c);
    }

  for (octave_map::const_iterator pa = m2.begin (); pa != m2.end (); pa++)
    {
      octave_map::const_iterator pb = m1.seek (m2.key(pa));

      if (pb == m1.end ())
        {
          Cell c (dv, fillv);

          retval.assign (m2.key(pa),
                         c.insert (m2.contents(pa), ra_idx));
        }
    }

  return retval;
}


DEFUN_DLD (structcat, args, ,
  "-*- texinfo -*-\n\
@deftypefn {Loadable Function} {} structcat (@var{dim}, @var{struct1}, @dots{}, @var{structn})\n\
@deftypefnx {Loadable Function} {} structcat (@var{dim}, @var{default}, @var{struct1}, @dots{}, @var{structn})\n\
Return the concatenation of N-d structures @var{struct1}, @dots{}, @var{structn} along dimension @var{dim}. Differently to @code{cat}, fields need not match --- missing fields get an empty matrix value. Without structure arguments, an empty structure array is returned. If a scalar argument @var{default} is given, missing fields get its value instead of an empty matrix value.\n\
@end deftypefn")
{
  std::string fname ("structcat");

  octave_map retval;

  octave_value_list err_retval;

  octave_idx_type n_args = args.length ();

  if (n_args == 0)
    {
      print_usage ();

      return err_retval;
    }
  
  octave_idx_type dim = 0;

  CHECK_ERROR (dim = args(0).int_value () - 1, err_retval,
               "%s: first argument must be an integer",
               fname.c_str ());

  if (dim < 0)
    {
      error ("%s: first argument must be a positive integer",
             fname.c_str ());

      return err_retval;
    }

  octave_idx_type m1_id;
  octave_value fillv;

  if (n_args > 1 && args(1).is_scalar_type ())
    {
      m1_id = 2;

      fillv = args(1);
    }
  else
    {
      m1_id = 1;

      fillv = Matrix ();
    }

  dim_vector dv;

  octave_idx_type idx_len = dv.length ();

  if (dim >= idx_len) idx_len = dim + 1;

  Array<octave_idx_type> ra_idx (dim_vector (idx_len, 1), 0);

  for (octave_idx_type i = m1_id; i < n_args; i++)
    {
      if (! args(i).OV_ISSTRUCT ())
        {
          error ("%s: some argument not a structure", fname.c_str ());

          return err_retval;
        }

      dim_vector dvi = args(i).dims (), old_dv = dv;

      if (! dv.concat (dvi, dim))
        {
          error ("%s: dimension mismatch", fname.c_str ());

          return err_retval;
        }

      if (! dvi.all_zero ())
        {
          retval = structcat_op_fcn (retval, args(i).map_value (),
                                     dv, ra_idx, fillv);

          ra_idx(dim) += (dim < dvi.length () ? dvi(dim) : 1);
        }
    }

  return octave_value (retval);
}

/*
%!assert (structcat (1, struct ("a", 1, "b", 2), struct ("a", 3, "c", 4)), cat (1, struct ("a", 1, "b", 2, "c", []), struct ("a", 3, "b", [], "c", 4)))
*/
