/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */
/*
 *  Copyright (C) 2011-2022  Ruby-GNOME Project Team
 *  Copyright (C) 2004  Masao Mutoh
 *  Copyright (C) 2004  Kazuhiro NISHIYAMA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA  02110-1301  USA
 */

#include "rbgprivate.h"
#include "rbglib.h"

#define RG_TARGET_NAMESPACE mSpawn

static ID id_call;
static ID id_new;

static void
child_setup(gpointer data)
{
    VALUE func = POINTER2RVAL(data);
    if (!NIL_P(func)) {
        rb_funcall(func, id_call, 0);
    }
}

static VALUE
rg_s_async_with_pipes(VALUE self, VALUE working_directory, VALUE argv, VALUE envp, VALUE flags)
{
    GError *err = NULL;
    gboolean ret;
    GPid child_pid;
    VALUE func = Qnil;
    gchar **gargv;
    gchar **genvp;
    gint standard_input, standard_output, standard_error;

    if (rb_block_given_p()) {
        func = rb_block_proc();
        G_RELATIVE(self, func);
    }

    gargv = (gchar **)RVAL2STRV(argv);
    genvp = (gchar **)RVAL2STRV_ACCEPT_NIL(envp);
    ret = g_spawn_async_with_pipes(RVAL2CSTR_ACCEPT_NIL(working_directory),
                                   gargv, genvp, NUM2INT(flags),
                                   (GSpawnChildSetupFunc)child_setup, 
                                   (gpointer)func,
                                   &child_pid, 
                                   &standard_input, &standard_output,
                                   &standard_error, &err);
    g_free(gargv);
    g_free(genvp);
    if (!ret)
        RAISE_GERROR(err);

    return rb_ary_new3(4, INT2NUM((gint)child_pid), 
                       rb_funcall(rb_cIO, id_new, 1, INT2NUM(standard_input)),
                       rb_funcall(rb_cIO, id_new, 1, INT2NUM(standard_output)),
                       rb_funcall(rb_cIO, id_new, 1, INT2NUM(standard_error)));
}

static VALUE
rg_s_async(VALUE self, VALUE working_directory, VALUE argv, VALUE envp, VALUE flags)
{
    GError *err = NULL;
    gboolean ret;
    GPid child_pid;
    VALUE func = Qnil;
    gchar **gargv;
    gchar **genvp;

    if (rb_block_given_p()) {
        func = rb_block_proc();
        G_RELATIVE(self, func);
    }

    gargv = (gchar **)RVAL2STRV(argv);
    genvp = (gchar **)RVAL2STRV_ACCEPT_NIL(envp);
    ret = g_spawn_async(RVAL2CSTR_ACCEPT_NIL(working_directory),
                        gargv, genvp, NUM2INT(flags),
                        (GSpawnChildSetupFunc)child_setup, (gpointer)func,
                        &child_pid, &err);
    g_free(gargv);
    g_free(genvp);
    if (!ret)
        RAISE_GERROR(err);

    return INT2NUM((int)child_pid);
}

static VALUE
rg_s_sync(VALUE self, VALUE working_directory, VALUE argv, VALUE envp, VALUE flags)
{
    GError *err = NULL;
    gboolean ret;
    VALUE func = Qnil;
    gchar** gargv;
    gchar** genvp;
    gchar *standard_output = NULL, *standard_error = NULL;
    gint exit_status;
    VALUE std_out, std_err;

    if (rb_block_given_p()) {
        func = rb_block_proc();
        G_RELATIVE(self, func);
    }

    gargv = (gchar **)RVAL2STRV(argv);
    genvp = (gchar **)RVAL2STRV_ACCEPT_NIL(envp);
    ret = g_spawn_sync(RVAL2CSTR_ACCEPT_NIL(working_directory),
                       gargv, genvp, NUM2INT(flags),
                       (GSpawnChildSetupFunc)child_setup, (gpointer)func,
                       &standard_output, &standard_error,
                       &exit_status, &err);
    g_free(gargv);
    g_free(genvp);
    if (!ret)
        RAISE_GERROR(err);

    if (standard_output) {
        std_out = CSTR2RVAL(standard_output);
        g_free(standard_output);
    } else {
        std_out = Qnil;
        standard_output = NULL;
    }
    if (standard_error) {
        std_err = CSTR2RVAL(standard_error);
        g_free(standard_error);
        standard_error = NULL;
    } else {
        std_err = Qnil;
    }

    if (! ret)
        RAISE_GERROR(err);

    return rb_ary_new3(3, std_out, std_err, INT2FIX(exit_status));

}

static VALUE
rg_s_command_line_sync(G_GNUC_UNUSED VALUE self, VALUE str)
{
    GError *err = NULL;
    const gchar *command_line;
    gchar *standard_output = NULL, *standard_error = NULL;
    gint exit_status;
    VALUE std_out, std_err;
    gboolean ret;

    command_line = RVAL2CSTR(str);
    ret = g_spawn_command_line_sync(command_line,
                                               &standard_output,
                                               &standard_error,
                                               &exit_status,
                                               &err);
    if (standard_output) {
        std_out = CSTR2RVAL(standard_output);
        g_free(standard_output);
    } else {
        std_out = Qnil;
        standard_output = NULL;
    }
    if (standard_error) {
        std_err = CSTR2RVAL(standard_error);
        g_free(standard_error);
        standard_error = NULL;
    } else {
        std_err = Qnil;
    }

    if (! ret)
        RAISE_GERROR(err);

    return rb_ary_new3(3, std_out, std_err, INT2FIX(exit_status));
}

static VALUE
rg_s_command_line_async(G_GNUC_UNUSED VALUE self, VALUE str)
{
    GError *err = NULL;
    const gchar *command_line;
    VALUE ret;

    command_line = StringValuePtr(str);
    ret = CBOOL2RVAL(g_spawn_command_line_async(command_line, &err));
    if (err != NULL)
        RAISE_GERROR(err);

    return ret;
}

#define RVAL2GPID(value) ((GPid)NUM2INT(pid))

static VALUE
rg_s_close_pid(G_GNUC_UNUSED VALUE self, VALUE pid)
{
    g_spawn_close_pid(RVAL2GPID(pid));
    return Qnil;
}

void
Init_glib_spawn(void)
{
    VALUE RG_TARGET_NAMESPACE = rb_define_module_under(rbg_mGLib(), "Spawn");

    id_call = rb_intern("call");
    id_new = rb_intern("new");

    /* glib/gspawn.h */
    RG_DEF_SMETHOD(async_with_pipes, 4);
    RG_DEF_SMETHOD(async, 4);
    RG_DEF_SMETHOD(sync, 4);
    RG_DEF_SMETHOD(command_line_sync, 1);
    RG_DEF_SMETHOD(command_line_async, 1);
    RG_DEF_SMETHOD(close_pid, 1);

    rb_define_const(RG_TARGET_NAMESPACE,
                    "DEFAULT",
                    INT2NUM(G_SPAWN_DEFAULT));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "LEAVE_DESCRIPTORS_OPEN",
                    INT2NUM(G_SPAWN_LEAVE_DESCRIPTORS_OPEN));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "DO_NOT_REAP_CHILD",
                    INT2NUM(G_SPAWN_DO_NOT_REAP_CHILD));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "SEARCH_PATH",
                    INT2NUM(G_SPAWN_SEARCH_PATH));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "STDOUT_TO_DEV_NULL",
                    INT2NUM(G_SPAWN_STDOUT_TO_DEV_NULL));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "STDERR_TO_DEV_NULL",
                    INT2NUM(G_SPAWN_STDERR_TO_DEV_NULL));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "CHILD_INHERITS_STDIN",
                    INT2NUM(G_SPAWN_CHILD_INHERITS_STDIN));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "FILE_AND_ARGV_ZERO",
                    INT2NUM(G_SPAWN_FILE_AND_ARGV_ZERO));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "SEARCH_PATH_FROM_ENVP",
                    INT2NUM(G_SPAWN_SEARCH_PATH_FROM_ENVP));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "CLOEXEC_PIPES",
                    INT2NUM(G_SPAWN_CLOEXEC_PIPES));
#if GLIB_CHECK_VERSION(2, 74, 0)
    rb_define_const(RG_TARGET_NAMESPACE,
                    "CHILD_INHERITS_STDOUT",
                    INT2NUM(G_SPAWN_CHILD_INHERITS_STDOUT));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "CHILD_INHERITS_STDERR",
                    INT2NUM(G_SPAWN_CHILD_INHERITS_STDERR));
    rb_define_const(RG_TARGET_NAMESPACE,
                    "STDIN_FROM_DEV_NULL",
                    INT2NUM(G_SPAWN_STDIN_FROM_DEV_NULL));
#endif
}
