// \file child.hpp
//
// Copyright (C) 2014 MicroNeil Research Corporation.
//
// This program is part of the MicroNeil Research Open Library Project. For
// more information go to http://www.microneil.com/OpenLibrary/index.html
//
// 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, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA
//==============================================================================

/*
  \brief The child module provides classes to spawn and communicate
  with child processes.
*/

#ifndef CHILD_HPP
#define CHILD_HPP

#ifdef WIN32
#include <windows.h> 
#endif

#include <cstdint>
#include <streambuf>
#include <istream>
#include <ostream>
#include <string>
#include <vector>

namespace CodeDweller {

  /**
     \namespace CodeDweller

     The CodeDweller namespace contains components providing high-level
     functionality for applications.

  */

  /** Class that abstracts the creation of child processes.

      This class provides functionality to create a child process,
      communicate with the child process via streams and signals, and
      obtain the exit code of the child process.

  */

  class Child {

  private:

    /// Streambuf class for reading the standard output of the child
    /// process.
    class ReadStreambuf : public std::streambuf {

    public:

      /// Reader streambuf constructor.
      //
      // \param[in] bufferSize is the size in bytes of the input
      // buffer.
      //
      explicit ReadStreambuf(std::size_t bufferSize = 4096);

#ifdef WIN32
      /// Set the handle to read the standard output of the child
      /// process.
      //
      // \param[in] inHandle is the input handle for the standard
      // output of the child process.
      //
      void setInputHandle(HANDLE inHandle);
#else
      /// Set the file descriptor to read the standard output of the
      /// child process.
      //
      // \param[in] inFd is the input file descriptor for the standard
      // output of the child process.
      //
      void setInputFileDescriptor(int inFd);
#endif

    private:

      /// Override streambuf::underflow().
      int_type underflow();

      /// Copy constructor not implemented.
      ReadStreambuf(const ReadStreambuf &);

      /// Copy constructor not implemented.
      ReadStreambuf &operator=(const ReadStreambuf &);

      /// Input handle.
#ifdef WIN32
      HANDLE inputHandle;
#else
      int inputFileDescriptor;
#endif

      /// Read buffer.
      std::vector<char> buffer;

    };

    /// Streambuf class for writing to the standard input of the child
    /// process.
    //
    // Note: If an error occurs when writing the output from the
    // parent process, the output buffer is cleared.
    //
    class WriteStreambuf : public std::streambuf {

    public:

      /// Writer streambuf constructor.
      //
      // \param[in] bufferSize is the size in bytes of the input
      // buffer.
      //
      explicit WriteStreambuf(std::size_t bufferSize = 4096);

#ifdef WIN32
      /// Set the handle to write the standard input of the child
      /// process.
      //
      // \param[in] outHandle is the output handle for the standard
      // input of the child process.
      //
      void setOutputHandle(HANDLE outHandle);
#else
      /// Set the file descriptor to write the standard input of the
      /// child process.
      //
      // \param[in] outFd is the output file descriptor for the
      // standard input of the child process.
      //
      void setOutputFileDescriptor(int outFd);
#endif

    private:

      /// Flush the output buffer.
      void flushBuffer();

      /// Override streambuf::overflow().
      int_type overflow(int_type ch);

      /// Override streambuf::sync().
      int sync();

      /// Copy constructor not implemented.
      WriteStreambuf(const WriteStreambuf &);

      /// Copy constructor not implemented.
      WriteStreambuf &operator=(const WriteStreambuf &);

      /// Output handle.
#ifdef WIN32
      HANDLE outputHandle;
#else
      int outputFileDescriptor;
#endif

      /// Write buffer.
      std::vector<char> buffer;

    };

    /// Stream buffer for reading to the stdout of the child process;
    ReadStreambuf readStreambuf;

    /// Stream buffer for writing to the stdin of the child process;
    WriteStreambuf writeStreambuf;

  public:

    /** Constructor for spawning with command-line parameters.

	The constructor configures the object, but doesn't spawn the
	child process.

	\param[in] args contains the child executable file name and
	command-line parameters. args[0] contains the full path of the
	executable, and args[1] thru args[n] are the command-line
	parameters.

	\param[in] bufSize is the buffer size of the reader and writer
	streams used to communicate with the child process.

    */
    Child(std::vector<std::string> args, size_t bufSize = 4096);

    /** Constructor for spawning without command-line parameters.

	The constructor configures the object, but doesn't spawn the
	child process.

	\param[in] childpath contains the child executable file name.

	\param[in] bufSize is the buffer size of the reader and writer
	streams used to communicate with the child process.

    */
    Child(std::string childpath, size_t bufSize = 4096);

    /** Destructor terminates the child process. */
    ~Child();

    /// Input stream to read data from the child's standard output.
    std::istream reader;

    /// Output stream to write data to the child's standard input.
    std::ostream writer;

    /** Spawn the child process.

	\throws runtime_error if an error occurs.

    */
    void run();

    /** Terminite the child process.

	\throws runtime_error if an error occurs.

	\throws logic_error if the child process is not running.

    */
    void terminate();

    /** Check whether the child process has exited.

	\returns True if the child process has exited, false
	otherwise.

	\throws runtime_error if an error occurs.

	\throws logic_error if the child process is not running.

    */
    bool isDone();

    /** Get the exit value of the child process.

	\returns The exit value of the child process if the child
	process has exited.

	\throws runtime_error if an error occurs.

	\throws logic_error if the child process has not exited.

	\throws logic_error if the child process is not running.

    */
    int32_t result();

  private:

    /// Exit code to use when terminating the child process.
    static const uint32_t terminateExitCode = 0;

    /// True if the child process was successfully started.
    bool childStarted;

    /// True if the child process has exited.
    bool childExited;

    /// Initialize data members.
    void init();

    /// Child executable path and command-line parameters.
    std::vector<std::string> cmdArgs;

    /// Child executable path and command-line parameters.
    std::string cmdline;

#ifdef WIN32
    /// Child's process handle.
    HANDLE childProcess;

    /// Child's thread handle.
    HANDLE childThread;
#else
    /// Child process ID.
    pid_t childPid;
#endif

    /// Exit value of the process.
    int32_t exitCode;

    /// True if the exit code has been obtained.
    bool exitCodeObtainedFlag;

    /// Return text for the most recent error.
    //
    // \returns Human-readable description of the most recent error.
    //
    static std::string getErrorText();

  };

}

#endif // CHILD_HPP