/*
    Copyright (c) 2006-2020 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _HAWK_PIO_H_
#define _HAWK_PIO_H_

#include <hawk-cmn.h>
#include <hawk-tio.h>

/** \file
 * This file defines a piped interface to a child process. You can execute
 * a child process, read and write to its stdin, stdout, stderr, and terminate
 * it. It provides more advanced interface than popen() and pclose().
 */

/**
 * The hawk_pio_flag_t defines enumerators to compose flags to hawk_pio_open().
 */
enum hawk_pio_flag_t
{
	/** enable text based I/O. */
	HAWK_PIO_TEXT          = (1 << 0),
	HAWK_PIO_IGNOREECERR = (1 << 1),
	HAWK_PIO_NOAUTOFLUSH   = (1 << 2),

	/** execute the command via a system shell
	 * (/bin/sh on unix/linux, cmd.exe on windows and os2) */
	HAWK_PIO_SHELL         = (1 << 3),

	/** indicate that the command to hawk_pio_open() is a multi-byte string.
	 *  it is useful if #HAWK_OOCH_IS_UCH is defined. */
	HAWK_PIO_BCSTRCMD        = (1 << 4),

	/** don't attempt to close open file descriptors unknown to pio.
	 *  it is useful only on a unix-like systems where file descriptors
	 *  not set with FD_CLOEXEC are inherited by a child process.
	 *  you're advised to set this option if all normal file descriptors
	 *  in your application are open with FD_CLOEXEC set. it can skip
	 *  checking a bunch of file descriptors and arranging to close
	 *  them to prevent inheritance. */
	HAWK_PIO_NOCLOEXEC     = (1 << 5),

	/** indidate that the command to hawk_pio_open()/hawk_pio_init() is
	 *  a pointer to a #hawk_pio_fnc_t structure. supported on unix/linux
	 *  only */
	HAWK_PIO_FNCCMD        = (1 << 6),

	/** write to stdin of a child process */
	HAWK_PIO_WRITEIN       = (1 << 8),
	/** read stdout of a child process */
	HAWK_PIO_READOUT       = (1 << 9),
	/** read stderr of a child process */
	HAWK_PIO_READERR       = (1 << 10),

	/** redirect stderr to stdout (2>&1, require #HAWK_PIO_READOUT) */
	HAWK_PIO_ERRTOOUT      = (1 << 11),
	/** redirect stdout to stderr (1>&2, require #HAWK_PIO_READERR) */
	HAWK_PIO_OUTTOERR      = (1 << 12),

	/** redirect stdin to the null device (</dev/null, <NUL) */
	HAWK_PIO_INTONUL       = (1 << 13),
	/** redirect stdin to the null device (>/dev/null, >NUL) */
	HAWK_PIO_ERRTONUL      = (1 << 14),
	/** redirect stderr to the null device (2>/dev/null, 2>NUL) */
	HAWK_PIO_OUTTONUL      = (1 << 15),

	/** drop stdin */
	HAWK_PIO_DROPIN        = (1 << 16),
	/** drop stdout */
	HAWK_PIO_DROPOUT       = (1 << 17),
	/** drop stderr */
	HAWK_PIO_DROPERR       = (1 << 18),

	/** do not reread if read has been interrupted */
	HAWK_PIO_READNORETRY   = (1 << 21),
	/** do not rewrite if write has been interrupted */
	HAWK_PIO_WRITENORETRY  = (1 << 22),
	/** return immediately from hawk_pio_wait() if a child has not exited */
	HAWK_PIO_WAITNOBLOCK   = (1 << 23),
	/** do not wait again if waitpid has been interrupted */
	HAWK_PIO_WAITNORETRY   = (1 << 24),

	/** put stdin to non-blocking mode (only on supported platforms) */
	HAWK_PIO_INNOBLOCK     = (1 << 25),
	/** put stdout to non-blocking mode (only on supported platforms)*/
	HAWK_PIO_OUTNOBLOCK    = (1 << 26),
	/** put stderr to non-blocking mode (only on supported platforms) */
	HAWK_PIO_ERRNOBLOCK    = (1 << 27)
};

/**
 * The hawk_pio_hid_t type defines pipe IDs established to a child process.
 */
enum hawk_pio_hid_t
{
	HAWK_PIO_IN  = 0, /**< stdin of a child process */
	HAWK_PIO_OUT = 1, /**< stdout of a child process */
	HAWK_PIO_ERR = 2  /**< stderr of a child process */
};
typedef enum hawk_pio_hid_t hawk_pio_hid_t;


typedef int (*hawk_pio_fncptr_t) (void* ctx);

/**
 * The hawk_pio_fnc_t type defines a structure to point to the function
 * executed in a child process when #HAWK_PIO_FNCCMD is specified.
 */
typedef struct hawk_pio_fnc_t hawk_pio_fnc_t;
struct hawk_pio_fnc_t
{
	hawk_pio_fncptr_t ptr;
	void* ctx;
};

#if defined(_WIN32)
	/* <winnt.h> => typedef PVOID HANDLE; */
	typedef void* hawk_pio_hnd_t; /**< defines a pipe handle type */
	typedef void* hawk_pio_pid_t; /**< defines a process handle type */
#	define  HAWK_PIO_HND_NIL ((hawk_pio_hnd_t)HAWK_NULL)
#	define  HAWK_PIO_PID_NIL ((hawk_pio_pid_t)HAWK_NULL)
#elif defined(__OS2__)
	/* <os2def.h> => typedef LHANDLE HFILE;
	                 typedef LHANDLE PID;
	                 typedef unsigned long LHANDLE; */
	typedef unsigned long hawk_pio_hnd_t; /**< defines a pipe handle type */
	typedef unsigned long hawk_pio_pid_t; /**< defined a process handle type */
#	define  HAWK_PIO_HND_NIL ((hawk_pio_hnd_t)-1)
#	define  HAWK_PIO_PID_NIL ((hawk_pio_pid_t)-1)
#elif defined(__DOS__)
	typedef int hawk_pio_hnd_t; /**< defines a pipe handle type */
	typedef int hawk_pio_pid_t; /**< defines a process handle type */
#	define  HAWK_PIO_HND_NIL ((hawk_pio_hnd_t)-1)
#	define  HAWK_PIO_PID_NIL ((hawk_pio_pid_t)-1)
#else
	typedef int hawk_pio_hnd_t; /**< defines a pipe handle type */
	typedef int hawk_pio_pid_t; /**< defines a process handle type */
#	define  HAWK_PIO_HND_NIL ((hawk_pio_hnd_t)-1)
#	define  HAWK_PIO_PID_NIL ((hawk_pio_pid_t)-1)
#endif

typedef struct hawk_pio_t hawk_pio_t;
typedef struct hawk_pio_pin_t hawk_pio_pin_t;

struct hawk_pio_pin_t
{
	hawk_pio_hnd_t handle;
	hawk_tio_t*    tio;
	hawk_pio_t*    self;
};


/**
 * The hawk_pio_t type defines a structure to store status for piped I/O
 * to a child process. The hawk_pio_xxx() funtions are written around this
 * type. Do not change the value of each field directly.
 */
struct hawk_pio_t
{
	hawk_gem_t*       gem;
	int               flags;  /**< options */
	hawk_pio_pid_t    child;   /**< handle to a child process */
	hawk_pio_pin_t    pin[3];
};

/** access the \a child field of the #hawk_pio_t structure */
#define HAWK_PIO_CHILD(pio)     ((pio)->child)
/** get the native handle from the #hawk_pio_t structure */
#define HAWK_PIO_HANDLE(pio,hid) ((pio)->pin[hid].handle)

#if defined(__cplusplus)
extern "C" {
#endif

/**
 * The hawk_pio_open() function executes a command \a cmd and establishes
 * pipes to it. #HAWK_PIO_SHELL causes the function to execute \a cmd via
 * the default shell of an underlying system: /bin/sh on *nix, cmd.exe on win32.
 * On *nix systems, a full path to the command is needed if it is not specified.
 * If \a env is #HAWK_NULL, the environment of \a cmd inherits that of the
 * calling process. If you want to pass an empty environment, you can pass
 * an empty \a env object with no items inserted. If #HAWK_PIO_BCSTRCMD is
 * specified in \a flags, \a cmd is treated as a multi-byte string whose
 * character type is #hawk_bch_t.
 * \return #hawk_pio_t object on success, #HAWK_NULL on failure
 */
HAWK_EXPORT hawk_pio_t* hawk_pio_open (
	hawk_gem_t*        gem,    /**< gem */
	hawk_oow_t         ext,    /**< extension size */
	const hawk_ooch_t* cmd,    /**< command to execute */
	int                flags   /**< 0 or a number OR'ed of the #hawk_pio_flag_t enumerators*/
);

/**
 * The hawk_pio_close() function closes pipes to a child process and waits for
 * the child process to exit.
 */
HAWK_EXPORT void hawk_pio_close (
	hawk_pio_t* pio /**< pio object */
);

/**
 * The hawk_pio_init() functions performs the same task as the hawk_pio_open()
 * except that you need to allocate a #hawk_pio_t structure and pass it to the
 * function.
 * \return 0 on success, -1 on failure
 */
HAWK_EXPORT int hawk_pio_init (
	hawk_pio_t*        pio,    /**< pio object */
	hawk_gem_t*        gem,    /**< gem */
	const hawk_ooch_t* cmd,    /**< command to execute */
	int                flags   /**< 0 or a number OR'ed of the #hawk_pio_flag_t enumerators*/
);

/**
 * The hawk_pio_fini() function performs the same task as hawk_pio_close()
 * except that it does not destroy a #hawk_pio_t structure pointed to by \a pio.
 */
HAWK_EXPORT void hawk_pio_fini (
	hawk_pio_t* pio /**< pio object */
);

#if defined(HAWK_HAVE_INLINE)
static HAWK_INLINE void* hawk_pio_getxtn (hawk_pio_t* pio) { return (void*)(pio + 1); }
#else
#define hawk_pio_getxtn(pio) ((void*)((hawk_pio_t*)(pio) + 1))
#endif

/**
 * The hawk_pio_getcmgr() function returns the current character manager.
 * It returns #HAWK_NULL is \a pio is not opened with #HAWK_PIO_TEXT.
 */
HAWK_EXPORT hawk_cmgr_t* hawk_pio_getcmgr (
	hawk_pio_t*    pio,
	hawk_pio_hid_t hid
);

/**
 * The hawk_pio_setcmgr() function changes the character manager to \a cmgr.
 * The character manager is used only if \a pio is opened with #HAWK_PIO_TEXT.
 */
HAWK_EXPORT void hawk_pio_setcmgr (
	hawk_pio_t*    pio,
	hawk_pio_hid_t hid,
	hawk_cmgr_t*   cmgr
);

/**
 * The hawk_pio_gethnd() function gets a pipe handle.
 * \return pipe handle
 */
HAWK_EXPORT hawk_pio_hnd_t hawk_pio_gethnd (
	const hawk_pio_t* pio, /**< pio object */
	hawk_pio_hid_t    hid  /**< handle ID */
);

/**
 * The hawk_pio_getchild() function gets a process handle.
 * \return process handle
 */
HAWK_EXPORT hawk_pio_pid_t hawk_pio_getchild (
	const hawk_pio_t* pio /**< pio object */
);

/**
 * The hawk_pio_read() fucntion reads at most \a size bytes/characters
 * and stores them to the buffer pointed to by \a buf.
 * \return -1 on failure, 0 on EOF, data length read on success
 */
HAWK_EXPORT hawk_ooi_t hawk_pio_read (
	hawk_pio_t*    pio,  /**< pio object */
	hawk_pio_hid_t hid,  /**< handle ID */
	void*          buf,  /**< buffer to fill */
	hawk_oow_t     size  /**< buffer size */
);

HAWK_EXPORT hawk_ooi_t hawk_pio_readbytes (
	hawk_pio_t*    pio,  /**< pio object */
	hawk_pio_hid_t hid,  /**< handle ID */
	void*          buf,  /**< buffer to fill */
	hawk_oow_t     size  /**< buffer size */
);


/**
 * The hawk_pio_write() function writes up \a size bytes/characters
 * from the buffer pointed to by \a data. If #HAWK_PIO_TEXT is used
 * and the \a size parameter is (hawk_oow_t)-1, the function treats
 * the \a data parameter as a pointer to a null-terminated string.
 * (hawk_oow_t)-1 into \a size is not treated specially if #HAWK_PIO_TEXT
 * is not set.
 *
 * \return -1 on failure, data length written on success
 */
HAWK_EXPORT hawk_ooi_t hawk_pio_write (
	hawk_pio_t*    pio,   /**< pio object */
	hawk_pio_hid_t hid,   /**< handle ID */
	const void*    data,  /**< data to write */
	hawk_oow_t     size   /**< data size */
);

HAWK_EXPORT hawk_ooi_t hawk_pio_writebytes (
	hawk_pio_t*    pio,   /**< pio object */
	hawk_pio_hid_t hid,   /**< handle ID */
	const void*    data,  /**< data to write */
	hawk_oow_t     size   /**< data size */
);

/**
 * The hawk_pio_flush() flushes buffered data if #HAWK_PIO_TEXT has been
 * specified to hawk_pio_open() and hawk_pio_init().
 */
HAWK_EXPORT hawk_ooi_t hawk_pio_flush (
	hawk_pio_t*    pio, /**< pio object */
	hawk_pio_hid_t hid  /**< handle ID */
);

/**
 * The hawk_pio_drain() drops unflushed input and output data in the
 * buffer.
 */
HAWK_EXPORT void hawk_pio_drain (
	hawk_pio_t*    pio, /**< pio object */
	hawk_pio_hid_t hid  /**< handle ID */
);

/**
 * The hawk_pio_end() function closes a pipe to a child process
 */
HAWK_EXPORT void hawk_pio_end (
	hawk_pio_t*    pio, /**< pio object */
	hawk_pio_hid_t hid  /**< handle ID */
);

/**
 * The hawk_pio_wait() function waits for a child process to terminate.
 * #HAWK_PIO_WAIT_NORETRY causes the function to return an error and set the
 * \a pio->errnum field to #HAWK_PIO_EINTR if the underlying system call has
 * been interrupted. If #HAWK_PIO_WAIT_NOBLOCK is used, the return value of 256
 * indicates that the child process has not terminated. Otherwise, 256 is never
 * returned.
 *
 * \return
 *  -1 on error, 256 if the child is alive and #HAWK_PIO_WAIT_NOBLOCK is used,
 *  a number between 0 and 255 inclusive if the child process ends normally,
 *  256 + signal number if the child process is terminated by a signal.
 */
HAWK_EXPORT int hawk_pio_wait (
	hawk_pio_t* pio /**< pio object */
);

/**
 * The hawk_pio_kill() function terminates a child process by force.
 * You should know the danger of calling this function as the function can
 * kill a process that is not your child process if it has terminated but
 * there is a new process with the same process handle.
 * \return 0 on success, -1 on failure
 */
HAWK_EXPORT int hawk_pio_kill (
	hawk_pio_t* pio /**< pio object */
);

#if defined(__cplusplus)
}
#endif

#endif
