/*

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

*/

#ifndef __OCT_PARALLEL_CONTROL__

#define __OCT_PARALLEL_CONTROL__

#include <octave/oct.h>

#include "config.h"

#include <octave/defaults.h>
#include <octave/oct-syscalls.h>

#include <unistd.h>

namespace octave_parallel
{

  class
  instance
  {
  public:


    instance (int fd, std::string& octb);

    ~instance (void);

    bool good (void)
    {
      return store_errno == 0;
    }

    int send_octave_value (octave_value& ov)
    {
      if (minimal_write_data (*os, ov))
        {
          store_errno = -1;

          return -1;
        }

      return 0;
    }

    int recv_octave_value (octave_value& ov)
    {
      if (minimal_read_data (*is, ov))
        {
          store_errno = -1;

          return -1;
        }

      return 0;
    }

    int initialize_new_function_handle (octave_value& fcn,
                                        std::string& dir,
                                        std::string& path,
                                        octave_value& eh,
                                        octave_value& nargout);

    int get_in_read_posix_fd (void)
    {
      return pps.get_in_read_posix_fd ();
    }

  private:

    int store_errno;

    pipes pps;

#if defined _WIN32 && ! defined __CYGWIN__
    void *process_handle;
#else
    pid_t pid;
#endif

    octave_parallel_pipe_streambuf *inbuf, *outbuf;

    std::istream *is;

    std::ostream *os;
  };

  class
  control
  {
  public:

    /*

    control (void)
      : nproc (0), octave_fd (-1), unrecoverable_error (0)
    {

      // This is only test code.
      std::string empty_string;
      if (set_executable (empty_string))
        _p_error ("couldn't set executable");
      else
        instc = new instance (octave_fd, octave_binary);
    }

    ~control (void)
    {

      // This is only test code
      delete instc;

    }

    // This is only for testing.
    instance *instc;

    */

    control (const std::string& path);

    ~control (void)
    {
      close_all ();

      if (gnulib_pollfds)
        {
          free (gnulib_pollfds);
        }
    }

    int set_nproc (octave_idx_type np);

    int set_nproc_used (octave_idx_type npu);

    octave_idx_type get_nproc (void)
    {
      return nproc;
    }

    octave_idx_type get_nproc_used (void)
    {
      return nproc_used;
    }

    int set_executable (const std::string& path);

    int close_all (void)
    {
      return set_nproc (0);
    }

    int initialize_job (const octave_value& fcn,
                        const std::string& dir,
                        const std::string& path,
                        const octave_value& eh,
                        const octave_value& nargout);

    // returns index
    octave_value get_next_result (octave_value& res);

    int send_next_args (octave_value& idx, octave_value& args);

    bool good (void)
    {
      return ! unrecoverable_error;
    }

  private:

    int poll_input (int timeout);

    octave_idx_type nproc; // all running processes

    octave_idx_type nproc_used; // used for current job

    octave_idx_type nproc_max;

    std::string octave_binary;

    int octave_fd;

    int unrecoverable_error;

    std::vector<instance*> instances;

    // 1-based indices into argument set or zero
    std::vector<octave_idx_type> states;

    std::vector<int> posix_fds;

    void *gnulib_pollfds;

    std::vector<int> poll_res; // used to store result of poll()

    octave_value fcn;

    octave_value eh;

    std::string dir;

    std::string search_path;

    octave_value nargout;
  };

  class
  scheduler_watch
  {
  public:
    scheduler_watch (std::string &path_arg) : path (path_arg)
    {
      sched = new control (path);
    }

    void reset (void)
    {
      if (sched)
        delete sched;

      sched = new control (path);
    }

    void reset_path (std::string &path_arg)
    {
      path = path_arg;

      reset ();
    }

    control &get_sched (void)
    {
      return *sched;
    }

    control &get_good_sched (void)
    {
      if (! sched->good ())
        reset ();

      if (! sched->good ())
        error ("can't reset scheduler");

      return *sched;
    }

    ~scheduler_watch (void)
    {
      if (sched)
        delete sched;
    }

  private:

    std::string path;

    control *sched;
  };

} // namespace octave_parallel

// For fexecve, we need to copy some const char* to non-const. Since
// fork() and exec() are not seperate commands at all operating
// systems, this has to be done before fork, in the parent process, so
// measures must be taken for deallocation.
class
copy_to_non_const
{
public:
  copy_to_non_const (const char *cstr)
    : str (NULL)
  {
    str = new char [strlen (cstr) + 1];

    strcpy (str, cstr);
  }

  ~copy_to_non_const (void)
  {
    delete [] str;
  }

  char *get_str (void)
  {
    return str;
  }

private:

  char *str;
};


#endif // __OCT_PARALLEL_CONTROL__
