/**
 * `Process` provides shortcuts for [class@Gio.Subprocess] with sane defaults.
 */
[Version (deprecated = true)]
public class AstalIO.Process : Object {
    public string[] argv { construct; get; }

    private void read_stream(DataInputStream stream, bool err) {
        stream.read_line_utf8_async.begin(Priority.DEFAULT, null, (_, res) => {
            try {
                var output = stream.read_line_utf8_async.end(res);
                if (output != null) {
                    if (err) {
                        stdout(output.strip());
                    } else {
                        stderr(output.strip());
                    }
                    read_stream(stream, err);
                }
            } catch (Error err) {
                critical(err.message);
            }
        });
    }

    private DataInputStream out_stream;
    private DataInputStream err_stream;
    private DataOutputStream in_stream;
    private Subprocess process;

    /**
     * When the underlying subprocess writes to its stdout.
     *
     * @param out Line written to stdout
     */
    public signal void stdout(string out);

    /**
     * When the underlying subprocess writes to its stderr.
     *
     * @param err Line written to stderr
     */
    public signal void stderr(string err);

    /**
     * When the underlying subprocess exits or is terminated.
     *
     * @param code Exit code or signal number if terminated
     */
    public signal void exit(int code, bool terminated);

    /**
     * Force quit the subprocess.
     */
    public void kill() {
        process.force_exit();
    }

    /**
     * Send a signal to the subprocess.
     *
     * @param signal_num Signal number to be sent
     */
    public void signal(int signal_num) {
        process.send_signal(signal_num);
    }

    /**
     * Write a line to the subprocess' stdin synchronously.
     *
     * @param in String to be written to stdin
     */
    public void write(string in) throws Error {
        in_stream.put_string(in);
    }

    /**
     * Write a line to the subprocess' stdin asynchronously.
     *
     * @param in String to be written to stdin
     */
    public async void write_async(string in) {
        try {
            yield in_stream.write_all_async(in.data, in.data.length, null, null);
        } catch (Error err) {
            printerr("%s\n", err.message);
        }
    }

    /** See [func@AstalIO.Process.subprocessv] */
    public Process(string[] cmd) throws Error {
        Object(argv: cmd);
        process = new Subprocess.newv(
            cmd,
            SubprocessFlags.STDIN_PIPE |
            SubprocessFlags.STDERR_PIPE |
            SubprocessFlags.STDOUT_PIPE
        );

        out_stream = new DataInputStream(process.get_stdout_pipe());
        err_stream = new DataInputStream(process.get_stderr_pipe());
        in_stream = new DataOutputStream(process.get_stdin_pipe());

        read_stream(out_stream, true);
        read_stream(err_stream, false);

        process.wait_async.begin(null, (_, res) => {
            try {
                process.wait_async.end(res);
            } catch (Error err) {
                // ignore
            }

            if (process.get_if_exited()) {
                exit(process.get_exit_status(), false);
            }

            if (process.get_if_signaled()) {
                exit(process.get_term_sig(), true);
            }
        });
    }

    /**
     * Start a new subprocess with the given command.
     *
     * The first element of the vector is executed with the remaining elements as the argument list.
     */
    public static Process subprocessv(string[] cmd) throws Error {
        return new Process(cmd);
    }

    /**
     * Start a new subprocess with the given command
     * which is parsed using [func@GLib.shell_parse_argv].
     */
    public static Process subprocess(string cmd) throws Error {
        string[] argv;
        Shell.parse_argv(cmd, out argv);
        return Process.subprocessv(argv);
    }

    /**
     * Execute a command synchronously.
     * The first element of the vector is executed with the remaining elements as the argument list.
     *
     * @return stdout of the subprocess
     */
    public static string execv(string[] cmd) throws Error {
        var process = new Subprocess.newv(
            cmd,
            SubprocessFlags.STDERR_PIPE |
            SubprocessFlags.STDOUT_PIPE
        );

        string err_str, out_str;
        process.communicate_utf8(null, null, out out_str, out err_str);
        var success = process.get_successful();
        if (success) {
            return out_str.strip();
        } else {
            throw new IOError.FAILED(err_str.strip());
        }
    }

    /**
     * Execute a command synchronously.
     * The command is parsed using [func@GLib.shell_parse_argv].
     *
     * @return stdout of the subprocess
     */
    public static string exec(string cmd) throws Error {
        string[] argv;
        Shell.parse_argv(cmd, out argv);
        return Process.execv(argv);
    }

    /**
     * Execute a command asynchronously.
     * The first element of the vector is executed with the remaining elements as the argument list.
     *
     * @return stdout of the subprocess
     */
    public static async string exec_asyncv(string[] cmd) throws Error {
        var process = new Subprocess.newv(
            cmd,
            SubprocessFlags.STDERR_PIPE |
            SubprocessFlags.STDOUT_PIPE
        );

        string err_str, out_str;
        yield process.communicate_utf8_async(null, null, out out_str, out err_str);
        var success = process.get_successful();
        if (success) {
            return out_str.strip();
        } else {
            throw new IOError.FAILED(err_str.strip());
        }
    }

    /**
     * Execute a command asynchronously.
     * The command is parsed using [func@GLib.shell_parse_argv].
     *
     * @return stdout of the subprocess
     */
    public static async string exec_async(string cmd) throws Error {
        string[] argv;
        Shell.parse_argv(cmd, out argv);
        return yield exec_asyncv(argv);
    }
}
