/* {{{ Copyright */

/* Background support.
   Copyright (C) 1996 The Free Software Foundation
   
   Written by: 1996 Miguel de Icaza

   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* }}} */

#include <config.h>
#include <stdarg.h>
#include <sys/types.h>
#ifdef HAVE_SYS_WAIT_H
#    include <sys/wait.h>
#endif
#include <errno.h>
#ifdef HAVE_UNISTD_H
#    include <unistd.h>
#endif
#include <sys/stat.h>
#include <sys/param.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include "tty.h"
#include "global.h"
#include "dlg.h"
#include "widget.h"
#include "wtools.h"

/*
 * We currenlty only support one way of comunicating the background
 * and foreground process by using the socketpair system call
 */
#ifdef USE_NETCODE
#    include <sys/socket.h>
#endif
#include "dialog.h"
#include "fileopctx.h"
#include "key.h"	/* For add_select_channel(), delete_select_channel() */
#include "eregex.h"
#include "file.h"
#include "filegui.h"

/* If true, this is a background process */
int we_are_background = 0;

#ifdef WITH_BACKGROUND
/* If set background tasks wait to be attached */
int background_wait = 0;

#ifndef HAVE_SOCKETPAIR
int socketpair(int, int, int, int fd[2]);
#endif

/* File descriptor for talking to our parent */
static int parent_fd;

#define MAXCALLARGS 4		/* Number of arguments supported */
#define mymsg "Desde el hijo\n\r"

struct TaskList *task_list = NULL;

static int background_attention (int fd, void *closure);
    
static void
register_task_running (FileOpContext *ctx, pid_t pid, int fd, char *info)
{
    TaskList *new;

    new = g_new (TaskList, 1);
    new->pid   = pid;
    new->info  = info;
    new->state = Task_Running;
    new->next  = task_list;
    new->fd    = fd;
    task_list  = new;

    add_select_channel (fd, background_attention, ctx);
}

void
unregister_task_running (pid_t pid, int fd)
{
    TaskList *p = task_list;
    TaskList *prev = 0;

    while (p){
	if (p->pid == pid){
	    if (prev)
		prev->next = p->next;
	    else
		task_list = p->next;
	    g_free (p->info);
	    g_free (p);
	    break;
	}
	prev = p;
	p = p->next;
    }
    delete_select_channel (fd);
}

/*
 * Try to make the Midnight Commander a background job
 *
 * Returns:
 *  1 for parent
 *  0 for child
 * -1 on failure
 */
int
do_background (FileOpContext *ctx, char *info)
{
    int comm [2];		/* control connection stream */
    pid_t pid;

    if (socketpair (AF_UNIX, SOCK_STREAM, 0, comm) == -1)
	return -1;

    if ((pid = fork ()) == -1)
	return -1;

    if (pid == 0){
	int nullfd;

	parent_fd = comm [1];
	we_are_background = 1;

	/* Make stdin/stdout/stderr point somewhere */
	close (0);
	close (1);
	close (2);

	if ((nullfd = open ("/dev/null", O_RDONLY)) != -1){
	    while (dup2 (nullfd, 0) == -1 && errno == EINTR)
		    ;
	    while (dup2 (nullfd, 1) == -1 && errno == EINTR)
		    ;
	    while (dup2 (nullfd, 2) == -1 && errno == EINTR)
		    ;
	}

	/* To make it obvious if it fails, there is a bug report on this */
	write (2, mymsg, sizeof (mymsg));
	write (1, mymsg, sizeof (mymsg));

	/* Just for debugging the background back end */
	if (background_wait){
	    volatile int i = 1;
	    
	    while (i)
		;
	}
	return 0;
    } else {
	close (comm [1]);
	ctx->pid = pid;
	register_task_running (ctx, pid, comm [0], info);
	return 1;
    }
}

static char *
background_title (char *str)
{
    char *result = g_strconcat (_("Background process:"), str, NULL);

    return result;
}

/* {{{ Routines that do the real job */
static void
real_message_1s (enum OperationMode mode, int *flags, char *title, char *str1)
{
    char *full_title;
    
    if (mode == Background)
	full_title = background_title (title);
    else
        full_title = title;
    
    message (*flags, title, str1);

    if (title != full_title)
	g_free (full_title);
}

static void
real_message_2s (enum OperationMode mode, int *flags, char *title, char *str1, char *str2)
{
    char *full_title;
    
    if (mode == Background)
	full_title = background_title (title);
    else
        full_title = title;
    
    message (*flags, title, str1, str2);
    
    if (title != full_title)
	g_free (full_title);
}

static void
real_message_3s (enum OperationMode mode, int *flags, char *title, char *str1, char *str2, const char *str3)
{
    char *full_title;
    
    if (mode == Background)
	full_title = background_title (title);
    else
        full_title = title;
    
    message (*flags, title, str1, str2, str3);
    
    if (title != full_title)
	g_free (full_title);
}
/* }}} */

/* {{{ Parent handlers */

/* Parent/child protocol
 *
 * the child (the background) process send the following:
 * void *routine -- routine to be invoked in the parent
 * int  nargc    -- number of arguments
 * int  type     -- Return argument type.
 *
 * If the routine is zero, then it is a way to tell the parent
 * that the process is dying.
 *
 * nargc arguments in the following format:
 * int size of the coming block
 * size bytes with the block
 *
 * Now, the parent loads all those and then invokes
 * the routine with pointers to the information passed
 * (we just support pointers).
 *
 * If the return type is integer:
 *
 *     the parent then writes an int to the child with
 *     the return value from the routine and the values
 *     of any global variable that is modified in the parent
 *     currently: do_append and recursive_result.
 *
 * If the return type is a string:
 *
 *     the parent writes the resulting string lenght
 *     if the result string was NULL or the empty string,
 *     then the lenght is zero.
 *     The parent then writes the string lenght and frees
 *     the result string.
 */
/*
 * Receive requests from background process and invoke the
 * specified routine 
 */

static int
background_attention (int fd, void *closure)
{
    FileOpContext *ctx;
    int have_ctx;
    void *routine;
    int  argc, i, result, status;
    char *data [MAXCALLARGS];
    char *resstr;
    int bytes;
    enum ReturnType type;
    char *background_process_error = _(" Background process error ");

    ctx = closure;

    bytes = read (fd, &routine, sizeof (routine));
    if (bytes < (sizeof (routine))){
	if (errno == ECHILD)
	    message (1, background_process_error, _(" Child died unexpectedly "));
	else
	    message (1, background_process_error, _(" Unknown error in child "));
	unregister_task_running (ctx->pid, fd);
	waitpid (ctx->pid, &status, 0);
	return 0;
    }

    /* If the routine is zero, then the child is telling us that he is dying */
    if ((int) routine == MSG_CHILD_EXITING){
	unregister_task_running (ctx->pid, fd);
	waitpid (ctx->pid, &status, 0);
	return 0;
    }

    read (fd, &argc, sizeof (argc));
    if (argc > MAXCALLARGS){
	message (1, _(" Background protocol error "),
		 _(" Background process sent us a request for more arguments \n"
		 " than we can handle. \n"));
    }
    read (fd, &type, sizeof (type));
    read (fd, &have_ctx, sizeof (have_ctx));
    if (have_ctx)
	    read (fd, ctx, sizeof (FileOpContext));

    for (i = 0; i < argc; i++){
	int size;

	read (fd, &size, sizeof (size));
	data [i] = g_malloc (size+1);
	read (fd, data [i], size);

	data [i][size] = 0;	/* NULL terminate the blocks (they could be strings) */
    }

    /* Handle the call */
    if (type == Return_Integer){
	if (have_ctx)
	    switch (argc){
	    case 1:
		result = (*(int (*)(int, char *))routine)(Background, data [0]);
		break;
	    case 2:
		result = (*(int (*)(int, char *, char *))routine)
		    (Background, data [0], data [1]);
		break;
	    case 3:
		result = (*(int (*)(int, char *, char *, char *))routine)
		    (Background, data [0], data [1], data [2]);
		break;
	    case 4:
		result = (*(int (*)(int, char *, char *, char *, char *))routine)
		    (Background, data [0], data [1], data [2], data [3]);
		break;
	    }
	else
	    switch (argc){
	    case 1:
		result = (*(int (*)(FileOpContext *, int, char *))routine)
		    (ctx, Background, data [0]);
		break;
	    case 2:
		result = (*(int (*)(FileOpContext *, int, char *, char *))routine)
		    (ctx, Background, data [0], data [1]);
		break;
	    case 3:
		result = (*(int (*)(FileOpContext *, int, char *, char *, char *))routine)
		    (ctx, Background, data [0], data [1], data [2]);
		break;
	    case 4:
		result = (*(int (*)(FileOpContext *, int, char *, char *, char *, char *))routine)
		    (ctx, Background, data [0], data [1], data [2], data [3]);
		break;
	    }

	/* Send the result code and the value for shared variables */
	write (fd, &result, sizeof (int));
	if (have_ctx)
	    write (fd, ctx, sizeof (FileOpContext));
    } else if (type == Return_String) {
	int len;

	/* FIXME: string routines should also use the Foreground/Background
	 * parameter.  Currently, this is not used here
	 */
	switch (argc){
	case 1:
	    resstr = (*(char * (*)(char *))routine)(data [0]);
	    break;
	case 2:
	    resstr = (*(char * (*)(char *, char *))routine)
		(data [0], data [1]);
	    break;
	case 3:
	    resstr = (*(char * (*)(char *, char *, char *))routine)
		(data [0], data [1], data [2]);
	    break;
	case 4:
	    resstr = (*(char * (*)(char *, char *, char *, char *))routine)
		(data [0], data [1], data [2], data [3]);
	    break;
	}
	if (resstr){
	    len = strlen (resstr);
	    write (fd, &len, sizeof (len));
	    if (len){
		write (fd, resstr, len);
		g_free (resstr);
	    }
	} else {
	    len = 0;
	    write (fd, &len, sizeof (len));
	}
    }
    for (i = 0; i < argc; i++)
	g_free (data [i]);

    do_refresh ();
    mc_refresh ();
#ifndef HAVE_X
    doupdate ();
#endif
    return 0;
}


/* }}} */

/* {{{ client RPC routines */

/* Sends the header for a call to a routine in the parent process.  If the file
 * operation context is not NULL, then it requests that the first parameter of
 * the call be a file operation context.
 */
static void
parent_call_header (void *routine, int argc, enum ReturnType type, FileOpContext *ctx)
{
    int have_ctx;

    have_ctx = (ctx != NULL);

    write (parent_fd, &routine, sizeof (routine));
    write (parent_fd, &argc, sizeof (int));
    write (parent_fd, &type, sizeof (type));
    write (parent_fd, &have_ctx, sizeof (have_ctx));

    if (have_ctx)
	write (parent_fd, ctx, sizeof (FileOpContext));
}

int
parent_call (void *routine, FileOpContext *ctx, int argc, ...)
{
    va_list ap;
    int i;

    va_start (ap, argc);
    parent_call_header (routine, argc, Return_Integer, ctx);
    for (i = 0; i < argc; i++) {
	int  len;
	void *value;

	len   = va_arg (ap, int);
	value = va_arg (ap, void *);
	write (parent_fd, &len, sizeof (int));
	write (parent_fd, value, len);
    }
    read (parent_fd, &i, sizeof (int));
    if (ctx)
	read (parent_fd, ctx, sizeof (FileOpContext));

    return i;
}

static char *
parent_call_string (void *routine, int argc, ...)
{
    va_list ap;
    char *str;
    int i;
    
    va_start (ap, argc);
    parent_call_header (routine, argc, Return_String, NULL);
    for (i = 0; i < argc; i++){
	int  len;
	void *value;

	len   = va_arg (ap, int);
	value = va_arg (ap, void *);
	write (parent_fd, &len, sizeof (int));
	write (parent_fd, value, len);
    }
    read (parent_fd, &i, sizeof (int));
    if (!i)
	return NULL;
    str = g_malloc (i + 1);
    read (parent_fd, str, i);
    str [i] = 0;
    return str;
}

void
tell_parent (int msg)
{
    write (parent_fd, &msg, sizeof (int));
}

void
message_1s (int flags, char *title, char *str1)
{
    if (we_are_background)
	parent_call ((void *)real_message_1s, NULL, 3, sizeof (flags), &flags,
		     strlen (title), title, strlen (str1), str1);
    else
	real_message_1s (Foreground, &flags, title, str1);
}

void
message_2s (int flags, char *title, char *str1, char *str2)
{
    if (we_are_background)
	parent_call ((void *)real_message_2s, NULL, 4, sizeof (flags), &flags,
		     strlen (title), title, strlen (str1), str1,
	             strlen (str2), str2);
    else
	real_message_2s (Foreground, &flags, title, str1, str2);
}

void
message_3s (int flags, char *title, char *str1, char *str2, const char *str3)
{
    if (we_are_background)
	parent_call ((void *)real_message_3s, NULL, 3, sizeof (flags), &flags,
		     strlen (title), title, strlen (str1), str1,
	             strlen (str2), str2, strlen (str3), str3);
    else
	real_message_3s (Foreground, &flags, title, str1, str2, str3);
}

char *
input_dialog_help (char *header, char *text, char *help, char *def_text)
{
    extern char *real_input_dialog_help (char *header, char *text, char *help, char *def_text);
    if (we_are_background)
	return parent_call_string ((void *)real_input_dialog_help, 4,
				   strlen (header),   header,
				   strlen (text),     text,
				   strlen (help),     help,
				   strlen (def_text), def_text);
    else
	return real_input_dialog_help (header, text, help, def_text);
}

#else /* Else => No background code support */

/* {{{ Stubs if background code is not supported */
void
message_1s (int flags, char *title, char *str1)
{
    message (flags, title, str1);
}

void
message_2s (int flags, char *title, char *str1, char *str2)
{
    message (flags, title, str1, str2);
}

void
message_3s (int flags, char *title, char *str1, char *str2, const char *str3)
{
    message (flags, title, str1, str2, str3);
}

char *
input_dialog_help (char *header, char *text, char *help, char *def_text)
{
    return real_input_dialog_help (header, text, help, def_text);
}
/* }}} */

#endif

/* {{{ Functions shared between background and foreground */

void
message_1s1d (int flags, char *title, char *str, int d)
{
    char *p = g_strdup_printf (str, d);
    message_1s (flags, title, p);
    g_free (p);
}

/* }}} */
