// file      : butl/process -*- C++ -*-
// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#ifndef BUTL_PROCESS
#define BUTL_PROCESS

#ifndef _WIN32
#  include <sys/types.h> // pid_t
#endif

#include <cassert>
#include <cstdint> // uint32_t
#include <system_error>

namespace butl
{
  struct process_error: std::system_error
  {
    process_error (int e, bool child)
        : system_error (e, std::system_category ()), child_ (child) {}

    bool
    child () const {return child_;}

  private:
    bool child_;
  };

  class process
  {
  public:
    // Start another process using the specified command line. The default
    // values to the in, out and err arguments indicate that the child
    // process should inherit the parent process stdin, stdout, and stderr,
    // respectively. If -1 is passed instead, then the corresponding child
    // process descriptor is connected (via a pipe) to out_fd for stdin,
    // in_ofd for stdout, and in_efd for stderr (see data members below).
    //
    // Instead of passing -1 or the default value, you can also pass your
    // own descriptors. Note, however, that in this case they are not
    // closed by the parent. So you should do this yourself, if required.
    // For example, to redirect the child process stdout to stderr, you
    // can do:
    //
    // process p (..., 0, 2);
    //
    // Throw process_error if anything goes wrong. Note that some of the
    // exceptions (e.g., if exec() failed) can be thrown in the child
    // version of us.
    //
    process (char const* const args[], int in = 0, int out = 1, int err = 2);

    // The "piping" constructor, for example:
    //
    // process lhs (..., 0, -1); // Redirect stdout to a pipe.
    // process rhs (..., lhs);   // Redirect stdin to lhs's pipe.
    //
    // rhs.wait (); // Wait for last first.
    // lhs.wait ();
    //
    process (char const* const args[], process& in, int out = 1, int err = 2);

    // Versions of the above constructors that allow us to change the
    // current working directory of the child process. NULL and empty
    // cwd arguments are ignored.
    //
    process (const char* cwd, char const* const[], int = 0, int = 1, int = 2);
    process (const char* cwd, char const* const[], process&, int = 1, int = 2);

    // Wait for the process to terminate. Return true if the process
    // terminated normally and with the zero exit status. Throw
    // process_error if anything goes wrong. This function can be
    // called multiple times with subsequent calls simply returning
    // the status.
    //
    bool
    wait ();

    // Return true if the process has already terminated in which case
    // the argument is set to the result of wait().
    //
    bool
    try_wait (bool&);

    ~process () {if (handle != 0) wait ();}

    // Moveable-only type.
    //
    process (process&&);
    process& operator= (process&&);

    process (const process&) = delete;
    process& operator= (const process&) = delete;

    // Create an empty or "already terminated" process. That is, handle is 0
    // and exit status is 0.
    //
    process ();

  public:
#ifndef _WIN32
    using handle_type = pid_t;
    using id_type = pid_t;
    using status_type = int;
#else
    using handle_type = void*;      // Win32 HANDLE
    using id_type = std::uint32_t;  // Win32 DWORD
#endif

    static id_type
    current_id ();

  public:
    handle_type handle;
    status_type status;

    int out_fd; // Write to this fd to send to the new process' stdin.
    int in_ofd; // Read from this fd to receive from the new process' stdout.
    int in_efd; // Read from this fd to receive from the new process' stderr.
  };
}

#include <butl/process.ixx>

#endif // BUTL_PROCESS