/*

Copyright (C) 2002 Hayato Fujiwara <h_fujiwara@users.sourceforge.net>
Copyright (C) 2010-2016 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 2 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/>.

*/

// PKG_ADD: autoload ("reval", "parallel_interface.oct");
// PKG_DEL: autoload ("reval", "parallel_interface.oct", "remove");

#include <octave/oct.h>
#include <octave/dirfns.h>
#include <octave/oct-map.h>
#include <octave/oct-env.h>

#include "parallel-gnutls.h"

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>

DEFUN_DLD (reval, args, ,
           "-*- texinfo -*-\n\
@deftypefn {Loadable Function} {} reval (@var{commands}, @var{connections})\n\
Evaluate @var{commands} at all remote hosts specified by @var{connections}.\n\
\n\
This function can only be successfully called at the client\n\
machine. @var{commands} must be a string containing Octave commands\n\
suitable for execution with Octaves @code{eval()} function. See\n\
@code{pconnect} for a description of the @var{connections}\n\
variable. @var{connections} can contain all connections of the\n\
network, a subset of them, or a single connection. The local machine\n\
(client), if contained in @var{connections}, is\n\
ignored. @var{commands} is executed in the same way at each specified\n\
machine.\n\
\n\
@seealso{pconnect, pserver, psend, precv, sclose, parallel_generate_srp_data, select_sockets}\n\
@end deftypefn")
{
  std::string fname ("reval");

  octave_value retval;

  if (args.length () != 2 ||
      args(1).type_id () != octave_parallel_connections::static_type_id ())
    {
      print_usage ();

      return retval;
    }

  octave_value val = args(0);

  charMatrix cm;

  CHECK_ERROR (cm = val.char_matrix_value (), retval,
               "%s: could not convert first argument to char matrix",
               fname.c_str ());
             
  int rows = val.rows ();
  int cols = val.columns ();

  const octave_base_value &rep = args(1).get_rep ();
  const octave_parallel_connections &cconns =
    dynamic_cast<const octave_parallel_connections&> (rep);
  octave_parallel_connections_rep *conns = cconns.get_rep ();
  int nconns = conns->get_connections ().numel ();

  if (conns->get_whole_network ()->is_closed ())
    {
      error ("%s: network is closed", fname.c_str ());

      return retval;
    }

  if (conns->is_server ())
    {
      error ("%s: 'reval' can't be called at the server side", fname.c_str ());

      return retval;
    }

  inthandler_dont_restart_syscalls __inthandler_guard__;

  // check each connection before a command is sent over any
  if (nconns < conns->get_all_connections ().numel ()) // it's a subnet
    for (int i = 0; i < nconns; i++)
      if (conns->get_connections ()(i)->is_pseudo_connection ())
        {
          error ("%s: using a subnet and client was given as one of the servers (index %i)",
                 fname.c_str (), i + 1);

          return retval;
        }
  bool command_errors = false, stream_errors = false;
  int err;
  for (int i = 0; i < nconns; i++)
    {
      if (conns->get_connections ()(i)->is_pseudo_connection ())
        continue;

      if ((err = conns->get_connections ()(i)->poll_for_errors ()))
        {
          if (err > 0)
            {
              c_verror ("%s: a previous command at server with index %i caused an error",
                        fname.c_str (), i + 1);
              command_errors = true;
            }
          else // err < 0
            {
              c_verror ("%s: communication error with server with index %i",
                        fname.c_str (), i + 1);
              stream_errors = true;
            }
        }
    }
  if (stream_errors)
    conns->close ();
  if (stream_errors || command_errors)
    {
      error ("error in reval");

      return retval;
    }

  std::string command (cols + 1, '\n'); // all but the last '\n' will
                                        // be overwritten
  for (int i = 0; i < nconns; i++)
    {
      if (conns->get_connections ()(i)->is_pseudo_connection ())
        continue;

      for (int j = 0; j < rows; j++)
        {
          command.replace (0, cols,
                           cm.extract (j, 0, j, cols - 1).data (), cols);

          if (conns->get_connections ()(i)->get_cmd_stream ()->
              network_send_string (command.c_str ()))
            {
              conns->close ();

              error ("%s: error sending command to server with index %i",
                     fname.c_str (), i + 1);

              return retval;
            }
        }
    }

  return retval;
}

/*
;;; Local Variables: ***
;;; mode: C++ ***
;;; End: ***
*/
