/*
 * Copyright (C) 1992 by Gustaf Neumann, Stefan Nusser
 *
 *      Wirtschaftsuniversitaet Wien,
 *      Abteilung fuer Wirtschaftsinformatik
 *      Augasse 2-6,
 *      A-1090 Vienna, Austria
 *      neumann@wu-wien.ac.at, nusser@wu-wien.ac.at
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both that
 * copyright notice and this permission notice appear in all supporting
 * documentation.  This software is provided "as is" without expressed or
 * implied warranty.
 *
 * Date: Mon, Apr 13 1992
 * Author: Stefan Nusser, Gustaf Neumann
 * Version: 0.96
 */

/*
 *   WAFE.C 
 *
 *   This file contains the main function as well as some
 *   utility procedures for set up communication in frontend mode
 */


#define MAIN

#include <sys/types.h>
#include <ctype.h>

#include "wafe.h"
#include <X11/Xos.h>

#ifndef STREAMS
#include <sys/socket.h>
#else
#include <sys/stream.h>
#include <stropts.h>
#include <fcntl.h>
#endif

#ifdef RDD
#include <rdd.h>
#endif

#include "version.h"


/*****************************************************************************
 *    FUNCTION: iSocketPair
 *            Use socketpair on BSD or ioctl on Sys 5 or pipes
 *                                                                          
 *    Arguments: array of file descriptors
 * 
 *               parentRd childWr childRd parentWr
 *
 *    Returns:  0 if success, otherwise -1                         
 *                                                                          
 *    Used by:  main
 *****************************************************************************/
#ifdef PIPES
int
iSocketPair(fd, onlyOne)
int      fd[4],onlyOne;
    {
    if (pipe(&fd[0])<0) return(-1);
    if (onlyOne) return(0);
    if (pipe(&fd[2])<0) return(-1);
    return(0);
    }
#else
#ifdef STREAMS
#define SPX_DEVICE "/dev/spx"
int
iSocketPair(fd, onlyOne)
int      fd[4], onlyOne;
    {
    struct strfdinsert  ins;
    queue_t                  *pointer;

    if ((fd[0] = open(SPX_DEVICE, O_RDWR)) < 0)
	return(-1);

    if ((fd[1] = open(SPX_DEVICE, O_RDWR)) < 0)
	{
	close(fd[0]);
	return(-1);
	}

    ins.ctlbuf.buf = (char *) &pointer;
    ins.ctlbuf.maxlen = sizeof(queue_t *);		
    ins.ctlbuf.len = sizeof(queue_t *);		

    ins.databuf.buf = (char *) 0;
    ins.databuf.maxlen = 0;
    ins.databuf.len = -1;

    ins.fildes = fd[1];
    ins.flags = 0;
    ins.offset = 0;

    if (ioctl(fd[0], I_FDINSERT, (char *) &ins) < 0)
	{
	close(fd[0]);
	close(fd[1]);
	return(-1);
	}

    fd[2]=fd[1];
    fd[3]=fd[0];
    return(0);
    }

#else

int
iSocketPair(fd, onlyOne)
int      fd[4], onlyOne;
    {
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1)
	return(-1);
    else
	{
	fd[2]=fd[1];
	fd[3]=fd[0];
	return(0);
	} 
    }
#endif
#endif


char *
checkArg(argc,args, argv, c)
int argc;
int *args;
char ** argv;
char c;
    {
    if (argv[*args][3]) 
        return &argv[*args][3];

    (*args)++;
    if (argc == *args)
        {
	fprintf(stderr, "Wafe: Option --%c needs an additional argument\n", c);
	exit(-1);
        }

    return argv[*args];
    }


/*****************************************************************************
 *    FUNCTION:  MAIN                                
 *               Command Line parsing, starts Xt Application, registers     
 *               addditional tcl-commands  and according                   
 *               to mode establishes the socket connection or reads in a   
 *               file or reads from stdin.                                 
 *                                                                         
 *    Arguments: argc and argv                                             
 *    Returns:   nothing                                                   
 *                                                                         
 *****************************************************************************/

void
main(argc, argv, env)
int    argc;
char **argv;
char **env;
    {
    int        i;
    Boolean    opt_p = False;
    Boolean    forkChild  = True;
    Boolean    promptMode = True;
    Boolean    inputMode  = False;
    Boolean    directMode = False;
    Boolean    explicitClassName = False;
    int        args = 0; 
    int        cargc = 0;
    char     **cargv;    
    char     **cargvCount;
    char      *charPtr;
    char      *execString = NULL;
    char      *applicationName = NULL;
    int        sp[4];  
    int        ep[4];  
    int        cp[4];

    DBUG_ENTER ("main");
    DBUG_PROCESS (argv[0]);

#ifdef DEBUG_MALLOC
    mal_debug(3);
    mal_trace(1);
#endif

    cargvCount = cargv = (char **)XtMalloc((argc+1) * sizeof(char *));
    
    /* In frontend mode, the class will be derived from the application's 
     * name, in file mode from the filename and in direct  mode it is 
     * defined to be Wafe
     */
    wafeAppClass = XtNewString(argv[0]);

    /*
     * Argument parsing loop - build new argument list without wafe's 
     * own args
     */ 

    while (args < argc)
	{
	if ((argv[args][0] == '-') 
	    && (argv[args][1] == '-') 
	    && (strlen(argv[args]) > 2))
	    {
	    /***  Debug - Mode  ***/
	    if (argv[args][2] == 'D')
                {                             
                DBUG_PUSH (&(argv[args][3]));  
                args++; 
	        }                             
	    else
                             /***  Prompt - Mode  ***/ 
	    if (argv[args][2] == 'n')
	        {                              
                promptMode = False;               
                args++;
                }
	    else
                             /***  Version  ***/ 
            if (argv[args][2] == 'v')
	        {                              
		fprintf(stderr, "\nThis is Wafe, version %s\n\n", VERSION);
                fprintf(stderr, "Copyright (C) 1992 by Gustaf Neumann, Stefan Nusser\n");
                fprintf(stderr, "University of Economics and Business Administration\n"); 
                fprintf(stderr, "Vienna, Austria\n\n");
                exit(0);
                }
	    else
                             /***  Input - Mode  ***/
	    if (argv[args][2] == 'i')
	        {                            
                inputMode  = True;            
                args++;
                }
	    else
                             /***  Execute - Mode  ***/
	    if (argv[args][2] == 'e')
	        {
		execString = checkArg(argc,&args, argv, argv[args][2]);
		args++;
                }
	    else
                             /***  Direct - Mode  ***/
            if (argv[args][2] == 'd')
	        {                           
                directMode = True;
		forkChild = False;
                args++; 
	        }
	    else 
                             /*** Change Prompt Character  ***/
            if (argv[args][2] == 'c')          
	        {
		extern char promptChar;
                promptChar = argv[args][3];
                args++;
	        }
	    else
	    if (argv[args][2] == 'p')
	        {
		opt_p = True;
		charPtr = checkArg(argc,&args, argv, argv[args][2]);

		if (!explicitClassName) 
		    {
		    if (wafeAppClass) XtFree(wafeAppClass);
		    wafeAppClass = XtNewString(charPtr);
		    }
		if (applicationName) XtFree(applicationName);
                applicationName = XtNewString(charPtr);

                args ++;
	        }
            else
	    if (argv[args][2] == 'T')
	        {
		charPtr = checkArg(argc,&args, argv, argv[args][2]);
		if (!(sscanf(charPtr,"%d/%d",&wafeExtraCom,&wafeExtraClient)))
		    {
		    fprintf(stderr, 
			    "Wafe: Option --t needs fileno(wafeSock)/fileno(clientSock)\n");
		    exit(-1);
		    }
		args ++;
                }
            else
	    if (argv[args][2] == 'C')
	        {
		charPtr = checkArg(argc,&args, argv, argv[args][2]);
		if (wafeAppClass) XtFree(wafeAppClass);
                wafeAppClass = XtNewString(charPtr);
		explicitClassName = True;
		args ++;
                }
            else
                             /***  File - Mode  ***/
            if (argv[args][2] == 'f')     /* Enter file mode */
                {
                if (argv[args][3] == 'i') /* For convenience: --fi == --f --i*/
		    inputMode = True;

		wafeScriptName = checkArg(argc,&args, argv, argv[args][2]);
		forkChild = False;
		promptMode = False;

		if (!explicitClassName)
		    {
		    if (wafeAppClass) XtFree(wafeAppClass);
		    wafeAppClass = XtNewString(wafeScriptName);
		    }

                args ++;
                }
	    else
		{
                fprintf(stderr, "Wafe(main): Unknown Argument, ignoring!\n");
                args++; 
                }
	    }
	else
	    {
	    *cargvCount = XtNewString(argv[args]);
	    cargc++;
	    args++;
	    cargvCount++;
	    }
	}

    *cargvCount = NULL;

    /* Check that everybody has got his args */

    DBUG_PRINT("start", ("Arguments at startup (argc = %d):", argc));
    for (i = 0; argv[i] != NULL; i++)
	DBUG_PRINT("start",("argv[%d] = %s", i, argv[i]));

    DBUG_PRINT("start", ("Xt's + app's args (cargc = %d):", cargc));
    for (i = 0; cargv[i] != NULL; i++)
	DBUG_PRINT("start",("cargv[%d] = %s", i, cargv[i]));

     /*
      * If the program's name contains a path specification cut it off...
      */
    if ((charPtr = strrchr(wafeAppClass, '/')))
	wafeAppClass = ++charPtr;

    /*
     * We will need this information when we execute the application process...
     */
    if (!applicationName) 
	applicationName = XtNewString(wafeAppClass); 
    
    /*
     * If the name contains a dot, which is not allowed in Xt's class names, 
     * substitute it with a dash (keep this in mind, it will possibly affect 
     * the name of the AD file!
     */
    while((charPtr = strrchr(wafeAppClass, '.')))
	*charPtr = '-';
         
    /* Just because argv[0] will appear in the titlebar of the 
     * top level window - if we were in file mode the title would
     * always be wafe (which is probably not desired).
     */
    cargv[0] = XtNewString(wafeAppClass);

    /* 
     *  Now create class: Capitalized X, second letter capitalized, 
     *  rest remains unchanged. Under this name, the app-defaults file
     *  will be searched! 
     */
    if ((*wafeAppClass == 'x') || (*wafeAppClass == 'X'))
	{
	*wafeAppClass = 'X';
	*(wafeAppClass+1) = toupper(*(wafeAppClass+1));
	}
    else
	*wafeAppClass = toupper(*wafeAppClass);

    /* Must initialize here, so that Xt can strip off its arguments 
     * and application gets reduced argument list (frontend mode)
     */

    wafeTopLevel = XtVaAppInitialize(&wafeAppContext, wafeAppClass, NULL, 0, 
				 &cargc, cargv, NULL, NULL); 

    MOTIF_EDITRES_HANDLER(wafeTopLevel);

    DBUG_PRINT("start", ("App's args (cargc = %d):", cargc)); 

    for(i = 0; cargv[i] != NULL; i++)
	DBUG_PRINT("start",("cargv[%d] = %s", i, cargv[i])); 

    if (forkChild && !opt_p)
	forkChild = (*applicationName == 'x') || 
	            (*applicationName == 'X');

    /* if "Nothing" was specified, default to interactive operation */
    if (!(forkChild || opt_p || fileMode || 
	  inputMode || directMode || execString))
	{
	directMode = True;
	promptMode = False;
	fprintf(stderr, 
		"\nThis is Wafe %s! (%s Version, Interactive Mode)\n\n", 
		VERSION,
#ifdef ATHENA
		"Athena"
#else
		"OSF/Motif"
#endif
		);
	}


    if (forkChild)     /* running as a frontend */
	{     

	 /*
          *  Main socketpair, will be connected to the client's stdin
          *  and stdout channels.
          */

	if (iSocketPair(sp,0) == -1)
	    {
	    fprintf(stderr, "Wafe(main): Couldn't create socket, aborting\n");
	    exit(-1);
	    }
/*
 printf("sp %d %d %d %d\n",sp[0],sp[1],sp[2],sp[3]);
 */

	/*
	 * This pair is used to facilitate an additional communication channel.
	 */

	if (iSocketPair(cp,1) == -1)
	    {
	      fprintf(stderr,"Wafe(main): Couldn't create socket, aborting\n");
	      exit(-1);
	    }
/*
 printf("cp %d %d %d %d\n",cp[0],cp[1],cp[2],cp[3]);
 */

	  wafeExtraClient = cp[1];  /* Communication endpoint for client  */
	  wafeExtraCom    = cp[0];  /* Communication endpoint for wafe    */

/*          close(cp[2]);
          close(cp[3]);
*/

	 /*
          *  Used to catch client's error messages - wafe will never write on
          *  this channel!
          */

	if (iSocketPair(ep,1) == -1)
	    {
	    fprintf(stderr, 
		    "Wafe(main): Couldn't create socket for app's stderr\n");
	    exit(-1);
	    }
/*
   printf("ep %d %d %d %d\n",ep[0],ep[1],ep[2],ep[3]);
 */
	wafeClientPid = fork();
    
	if (!wafeClientPid)     /* Child-process  */
	    {

	    dup2(sp[1], fileno(stdout));
	    dup2(sp[2], fileno(stdin));
	    dup2(ep[1], fileno(stderr));
/*
	    fprintf(stderr,
		    "closing %d %d %d %d in CHILD\n", 
		    ep[0], ep[1], sp[1], sp[0]);

	    fprintf(stderr,"after dup %d = %d (stdin)\n", 
		    sp[1], fileno(stdin));
	    fprintf(stderr,"after dup %d = %d (stdout)\n", 
		    sp[2], fileno(stdout));
	    fprintf(stderr,"after dup %d = %d (stderr)\n", 
		    ep[1], fileno(stderr));
*/
	    close(ep[0]);              /* Those 2 are for ..          */
	    close(ep[1]);              /* getting stderr-messages.    */

	    close(sp[1]);              /* For regular communication ..*/
	    close(sp[0]);              /* via stdin and stdout!       */

            setpgrp();

	    cargv[0] = opt_p ? applicationName : applicationName+1;
	    execvp(cargv[0], cargv);   /* execute the client-program  */  
   
	    fprintf(stderr, "Wafe(main): Cannot execute <%s>, aborting\n",
		    cargv[0]);  
	    exit(-1);
	    }

	if (wafeClientPid == -1)
	    {
	    fprintf(stderr, "Wafe(main): Couldn't fork!\n");
	    DBUG_VOID_RETURN;
	    }

/*
          close(sp[1]);
          close(ep[1]);
*/

          /* Those are the four main communication-channels:
           *
           * 1) wafeErrorClient   is the client's diagnostic output,
           * 2) wafeToClient      leads to client's stdin and
           * 3) wafeFromClient    is client's stdout 
           * 4) wafeExtraCom      additional communication channel
           */

	wafeErrorClient = ep[0];
	wafeToClient    = sp[3];
	wafeFromClient  = sp[0];
/*
	fprintf(stderr,
		"errorClient=%d, toClient=%d, fromClient=%d, comWafe=%d\n", 
		wafeErrorClient, wafeToClient, wafeFromClient,wafeExtraCom);
 */
	}

     /* Now check for invalid combination of modes */
    if ((directMode) && (fileMode))
	{
	fprintf(stderr, 
	   "Wafe: Invalid combination direct-mode and file-mode, aborting!\n");
	exit(-1);
	}


    /* Now set up interrup handler, initalize wafe packages, set
     * global tcl variables
     */

    wafeInit(cargc, cargv, inputMode, promptMode);

    if (execString)
	(void)wafeEval(wafeInterpreter, execString, "cmdline");

    if (fileMode)
	if (Tcl_EvalFile(wafeInterpreter, wafeScriptName) == TCL_ERROR)
	    {
	    fprintf(stderr, "Wafe(file evaluation): %s\n", 
		    wafeInterpreter->result);
	    exit(-1);
	    }

/*    signal(SIGCLD, signalHandler);*/

#ifdef RDD
    rddAppMainLoop(wafeAppContext);
#else
    XtAppMainLoop(wafeAppContext);
#endif

    DBUG_VOID_RETURN;
    }

