/*

Copyright (C) 2019, 2020 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/>.

*/

#include <unistd.h>
#include <iostream>

#include "parallel-gnutls.h"

#if defined _WIN32 && ! defined __CYGWIN__

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#endif

DEFUN_DLD (__rfeval_slave__, args, ,
           "-*- texinfo -*-\n\
@deftypefn {Loadable Function} {} __rfeval_slave__ ()\n\
Undocumented internal function.\n\
@end deftypefn")
{
  // This function doesn't return, except for errors with the old
  // error handling of Octave up to version 4.0.
  octave_value retval;

  std::string fname ("__rfeval_slave__");

  // Don't run if called interactively.
  bool interact;
#ifdef HAVE_OCTAVE_INTERPRETER_H
  interact = OCTAVE__INTERPRETER::the_interpreter () -> interactive ();
#else
  interact = interactive;
#endif
  if (interact)
    {
      error ("%s must be called non-interactively", fname.c_str ());
      return retval;
    }

  // arguments: in pipe read end, out pipe write end
  if (args.length () != 2)
    {
      error ("invalid call to %s", fname.c_str ());
      return retval;
    }

  // file descriptor for input pipe, reading end (used)
  uint64_t pin;
  CHECK_ERROR (pin = args(0).uint64_value (),
               retval,
               "%s: could not convert argument 1 to uint64_t",
               fname.c_str ());
  
  // file descriptor for output pipe, writing end (used)
  uint64_t pout;
  CHECK_ERROR (pout = args(1).uint64_value (),
               retval,
               "%s: could not convert argument 2 to uint64_t",
               fname.c_str ());

  octave_parallel::pipes::pipe_wrapper pw_in (pin);

  octave_parallel_pipe_streambuf inbuf (pw_in, true);

  if (! inbuf.good ())
    {
      error ("%s: could not costruct streambuf from input pipe",
             fname.c_str ());
      return retval;
    }

  octave_parallel::pipes::pipe_wrapper pw_out (pout);

  octave_parallel_pipe_streambuf outbuf (pw_out, false);

  if (! outbuf.good ())
    {
      error ("%s: could not construct streambuf from output pipe",
             fname.c_str ());
      return retval;
    }

  std::istream is (& inbuf);

  std::ostream os (& outbuf);

  octave_value in_val, fcn, eh;

  octave_value flag_err (octave_int32 (-1));
  octave_value flag_handler_err (octave_int32 (-2));
  octave_value arg_idx;

  bool have_eh = false;

  octave_idx_type nargout;

  while (true)
    {
      if (minimal_read_data (is, in_val))
        {
          error ("%s: could not read variable",
                 fname.c_str ());
          return retval;
        }

      if (in_val.is_function_handle ())
        {
          // A new function for argument evaluation has been
          // read.
          fcn = in_val;

          // Read the next value, it is the current directory, chdir()
          // to it and write an integer indicating if chdir() has been
          // successful.
          if (minimal_read_data (is, in_val))
            {
              error ("%s: could not read directory", fname.c_str ());
              return retval;
            }
          // OCTAVE__SYS__ENV::chdir returns 'true' for success
          int cd_ok = OCTAVE__SYS__ENV::chdir (in_val.string_value ());
          octave_value ov_cd_ok = octave_value (octave_int32 (cd_ok));
          if (minimal_write_data (os, ov_cd_ok))
            {
              error ("%s: could not write result of chdir", fname.c_str ());
              return retval;
            }

          if (cd_ok)
            {
              // Read the next value --- it may be an error handler
              // function or nargout. If it is a function (error
              // handler), read a further value, which will be
              // nargout.
              if (minimal_read_data (is, in_val))
                {
                  error ("%s: could not read second variable after user function",
                         fname.c_str ());
                  return retval;
                }
              if (in_val.OV_ISINTEGER ())
                {
                  nargout = in_val.int_value ();
                  have_eh = false;
                }
              else
                {
                  // value is the error handler 
                  eh = in_val;
                  have_eh = true;
                  if (minimal_read_data (is, in_val))
                    {
                      error ("%s: could not read third variable after user function",
                             fname.c_str ());
                      return retval;
                    }
                  nargout = in_val.int_value ();
                }
            }
        }
      else if (in_val.OV_ISINTEGER ())
        {
          // Termination signal.
          _exit (0);
        }
      else
        {
          // An argument set has been read. If we have an error
          // handler, the next value will be the index into the
          // arguments.
          if (have_eh && minimal_read_data (is, arg_idx))
            {
              error ("%s: could not read argument index", fname.c_str ());
              return retval;
            }
          octave_value_list out_val;
          Cell c_in_val = in_val.cell_value ();
          octave_value_list ovl_in_val = octave_value_list (c_in_val);
          bool execution_error = false, handler_error = false;
          try
            {
              out_val = OCTAVE__FEVAL (MAYBE_FUNCTION_VALUE(fcn),
                                       ovl_in_val, nargout);
#ifdef HAVE_OCTAVE_ERROR_STATE
              if (error_state)
                {
                  execution_error = true;
                  error_state = false;
                }
#endif
            }
          catch (const OCTAVE__EXECUTION_EXCEPTION&)
            {
              OCTAVE__INTERPRETER__RECOVER_FROM_EXCEPTION;
              execution_error = true;
            }
          if (execution_error)
            {
              if (have_eh)
                {
                  octave_scalar_map info;
                  info.assign ("identifier",
                               OCTAVE__INTERPRETER__GET_ERROR_SYSTEM__LAST_ERROR_ID ());
                  info.assign ("message",
                               OCTAVE__INTERPRETER__GET_ERROR_SYSTEM__LAST_ERROR_MESSAGE ());
                  info.assign ("index", arg_idx.double_value ());
                  ovl_in_val.prepend (info);
                  try
                    {
                      out_val = OCTAVE__FEVAL (MAYBE_FUNCTION_VALUE(eh),
                                               ovl_in_val, nargout);
#ifdef HAVE_OCTAVE_ERROR_STATE
                      if (error_state)
                        {
                          handler_error = true;
                          error_state = false;
                        }
#endif
                    }
                  catch (const OCTAVE__EXECUTION_EXCEPTION&)
                    {
                      OCTAVE__INTERPRETER__RECOVER_FROM_EXCEPTION;
                      handler_error = true;
                    }
                  if (handler_error)
                    {
                      if (minimal_write_data (os, flag_handler_err))
                        {
                          error ("%s: could not write handler error flag",
                                 fname.c_str ());
                          return retval;
                        }
                    }
                  else
                    {
                      octave_value c_out_val = out_val.cell_value ();
                      if (minimal_write_data (os, c_out_val))
                        {
                          error ("%s, could not write result of error handler",
                                 fname.c_str ());
                          return retval;
                        }
                    }
                }
              else
                {
                  if (minimal_write_data (os, flag_err))
                    {
                      error ("%s: could not write execution error flag",
                             fname.c_str ());
                      return retval;
                    }
                }
            }
          else
            {
              octave_value c_out_val = out_val.cell_value ();
              if (minimal_write_data (os, c_out_val))
                {
                  error ("%s: could not write result", fname.c_str ());
                  return retval;
                }
            }
        }
    }

  return retval; // should not be reached
}
