/*
 * ftp-actions.c : X/FTP interface routines for xarchie
 *
 * George Ferguson, ferguson@cs.rochester.edu, 23 Apr 1993.
 * 13 May 1993: Check for NULL return from ftpNewContext().
 * 27 Jul 1993: Don't use boolean AND of condition flags for XtAppAddInput().
 */

#include <stdio.h>
#include <fcntl.h>			/* O_RDONLY, etc. */
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Dialog.h>
#include "config.h"
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include "db.h"
#include "appres.h"
#include "xarchie.h"
#include "selection.h"
#include "browser.h"
#include "ftp.h"
#include "view-file.h"
#include "status.h"
#include "popups.h"
#include "xutil.h"
#include "alert.h"
#include "syserr.h"
#include "debug.h"

/*
 * Functions defined here
 */
void initFtpActions();
void ftpGetSelectedItems(),ftpOpenSelectedItems();
void RegisterFtpFd(),UnregisterFtpFd();
int ftpPrompt();

static void ftpSelectedItems(),countFileToGet(),countFileToOpen(),addFile();
static void ftpGetFinished(),ftpOpenOneFinished();
static void initFtpTraceWidgets();
static void ftpTraceDoneAction();
static void ftpTraceReset(),ftpTraceFunc();
static void inputCallback();
static void ftpPromptCallback();

/*
 * Data defined here
 */
static char **ftpFilenames;
static int numFtpFiles;
static FtpContext *ftpCurrentContext;

static Widget ftpTraceShell,ftpTraceText;
static Boolean isPoppedUp;

static XtActionsRec actionTable[] = {
    { "ftp-trace-done",	ftpTraceDoneAction },
};

/*	-	-	-	-	-	-	-	-	*/
/* Interface functions */

void
initFtpActions()
{
    XtAppAddActions(appContext,actionTable,XtNumber(actionTable));
}

void
ftpGetSelectedItems()		/* Called from getAction() */
{
    ftpSelectedItems(appResources.ftpLocalDir,appResources.ftpType,
		     appResources.ftpPrompt,appResources.ftpStrip,
		     countFileToGet,NULL,ftpGetFinished);
}

void
ftpOpenSelectedItems()		/* Called from openBrowser() */
{
    ftpSelectedItems(tmpDirectory,"ascii",False,True,
		     countFileToOpen,ftpOpenOneFinished,ftpGetFinished);
}

void
ftpAbortTransfer()
{
    DEBUG1("ftpAbortTransfer: ftpCurrentContext=0x%x\n",ftpCurrentContext);
    ftpAbort(ftpCurrentContext);
    DEBUG0("ftpAbortTransfer: done\n");
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * This function starts ftp retrieval of the items selected in the
 * browser into the local directory.
 */
static void
ftpSelectedItems(local_dir,typestr,prompt,strip,
		 countProc,doneOneProc,doneAllProc)
char *local_dir,*typestr;
Boolean prompt,strip;
void (*countProc)();
FtpCallbackProc doneOneProc,doneAllProc;
{
    char *host,*cwd;
    int type;
#ifdef DEBUG
    int i;
#endif

    DEBUG0("ftpSelectedItems: setting files to transfer\n");
    numFtpFiles = 0;
    forEachSelectedItem(countProc);
    if (numFtpFiles == 0) {
	return;
    }
    ftpFilenames = (char **)XtCalloc(numFtpFiles,sizeof(char *));
    numFtpFiles = 0;
    forEachSelectedItem(addFile);
#ifdef DEBUG
    for (i=0; i < numFtpFiles; i++)
	fprintf(stderr,"ftpSelectedItems: ftpFilenames[%d] = \"%s\"\n",
		i,ftpFilenames[i]);
#endif
    /* Is there a host? */
    if ((host=getWidgetString(hostText)) == NULL || *host == '\0') {
	alert0("No host specified for transfer!");
	return;
    }
    DEBUG1("ftpSelectedItems: host = \"%s\"\n",host);
    /* Optional remote directory */
    cwd = getWidgetString(locationText); /* Can be "" */
    DEBUG1("ftpSelectedItems: remote dir = \"%s\"\n",cwd);
    if (cwd && *cwd == '\0')
	cwd = NULL;
    /* Convert type to ARPA code */
    if (typestr == NULL || *typestr == 'a' || *typestr == 'A')
	type = TYPE_A;
    else if (*typestr == 'b' || *typestr == 'B')
	type = TYPE_I;
    else if (*typestr == 'e' || *typestr == 'E')
	type = TYPE_E;
    else
	type = atoi(typestr);
    /* Put up the tracing window if needed */
    if (appResources.ftpTrace) {
	if (ftpTraceShell == NULL)
	    initFtpTraceWidgets();
	ftpTraceReset();
	isPoppedUp = True;
	XtPopup(ftpTraceShell,XtGrabNone);
    }
    /* Get the FTP context */
    ftpCurrentContext =
	ftpNewContext(host,"anonymous",appResources.ftpMailAddress,
		      cwd,local_dir,type,strip,(numFtpFiles>1 && prompt),
		      FTP_GET,ftpFilenames,numFtpFiles,
		      (appResources.ftpTrace ? ftpTraceFunc : NULL),
		      doneOneProc,doneAllProc);
    XtFree((char *)ftpFilenames);
    /* Hostname lookup might have failed */
    if (ftpCurrentContext == NULL)
	return;
    /* Here we go... */
    setBrowserState(BROWSER_FTP);
    ftpStart(ftpCurrentContext);
    DEBUG0("ftpSelectedItems: done\n");
}

/*	-	-	-	-	-	-	-	-	*/
/* Functions called by ftpGetSelectedItems */

/*ARGSUSED*/
static void
countFileToGet(dbp,list_index)		/* complains about non-files */
DbEntry *dbp;
int list_index;
{
    if (dbp->type == DB_FILE) {
	numFtpFiles += 1;
    } else {
	alert1("Can't retrieve non-file \"%s\".",dbp->name);
    }
}

/*ARGSUSED*/
static void
countFileToOpen(dbp,list_index)		/* doesn't complain about non-files */
DbEntry *dbp;
int list_index;
{
    if (dbp->type == DB_FILE) {
	numFtpFiles += 1;
    }
}

/*ARGSUSED*/
static void
addFile(dbp,list_index)			/* adds filename to array */
DbEntry *dbp;
int list_index;
{
    if (dbp->type == DB_FILE) {
	ftpFilenames[numFtpFiles++] = XtNewString(dbp->name);
    }
}

/*	-	-	-	-	-	-	-	-	*/
/* FTP callbacks */

/*
 * This function is called back when the ftp transfer is complete (for
 * whatever reason).
 */
/*ARGUSED*/
static void
ftpGetFinished(ftpc)
FtpContext *ftpc;
{
    DEBUG1("ftpGetFinished(0x%x)...\n",ftpc);
    setBrowserState(BROWSER_READY);
    ftpFreeContext(ftpc);
    status0("Ready");
    DEBUG0("ftpGetFinished: done\n");
}

/*
 * This function is called back when a single file has been successfully
 * retrieved for Open.
 */
/*ARGSUSED*/
static void
ftpOpenOneFinished(ftpc)
FtpContext *ftpc;
{
    char filename[MAXPATHLEN];

    DEBUG1("ftpOpenOneFinished(0x%x)...\n",ftpc);
    if (ftpc->local_dir && *(ftpc->local_dir))
	sprintf(filename,"%s/%s",ftpc->local_dir,ftpc->files[ftpc->this_file]);
    else
	strcpy(filename,ftpc->files[ftpc->this_file]);
    viewFile(filename);
    DEBUG0("ftpOpenOneFinished: done\n");
}

/*	-	-	-	-	-	-	-	-	*/
/* Routines for tracing the ftp connection in a window */

static void
initFtpTraceWidgets()
{
    Widget form;

    ftpTraceShell = XtCreatePopupShell("ftpTraceShell",
				       topLevelShellWidgetClass,
				       toplevel,NULL,0);
    form = XtCreateManagedWidget("ftpTraceForm",formWidgetClass,
				 ftpTraceShell,NULL,0);
    (void)XtCreateManagedWidget("ftpTraceDismissButton",commandWidgetClass,
				form,NULL,0);
    ftpTraceText = XtCreateManagedWidget("ftpTraceText",asciiTextWidgetClass,
					 form,NULL,0);
    XtRealizeWidget(ftpTraceShell);
    (void)XSetWMProtocols(XtDisplay(ftpTraceShell),XtWindow(ftpTraceShell),
			  &WM_DELETE_WINDOW,1);
}

void
setFtpTraceShellState(state)
int state;
{
    if (!isPoppedUp)
	return;
    switch (state) {
	case NormalState:
	    XtMapWidget(ftpTraceShell);
	    break;
	case IconicState:
	    XtUnmapWidget(ftpTraceShell);
	    break;
    }
}

/*ARGSUSED*/
static void
ftpTraceDoneAction(w,event,params,num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    isPoppedUp = False;
    XtPopdown(ftpTraceShell);
}

static void
ftpTraceReset()
{
    Arg args[1];

    XtSetArg(args[0],XtNstring,"");
    XtSetValues(ftpTraceText,args,1);
}

/*
 * This function is called back from within the ftp routines to monitor
 * the exchange of messages.
 */
/*ARGSUSED*/
static void
ftpTraceFunc(ftpc,who,text)
FtpContext *ftpc;
int who;		/* 0 => recvd, non-0 => sent */
char *text;		/* text of this message */
{
    if (who)
	appendWidgetText(ftpTraceText,"ftp> ");
    appendWidgetText(ftpTraceText,text);
    if (*(text+strlen(text)-1) != '\n')
	appendWidgetText(ftpTraceText,"\n");
}

/*	-	-	-	-	-	-	-	-	*/
/* File descriptor registration routines: */

/*
 * We need as many of these as there are file descriptors. It's
 * easier to guess big than to figure out where such a magic number lives.
 */
#define NUMFDS 64

struct _ftpRegisteredFds_struct {
    FtpCallbackProc proc;
    FtpContext *context;
    XtInputId readid,writeid;
} ftpRegisteredFds[NUMFDS];

/*
 * This function is exported and used by the FTP routines to register
 * a file descriptor for notification. We register it by recording the
 * callback information (proc and ftpc) and telling X to register the
 * fd as an external input source. When X calls back to inputCallback(),
 * we use the stored information to call back to the FTP routine.
 * Got that?
 */
void
RegisterFtpFd(ftpc,fd,flags,proc)
FtpContext *ftpc;
int fd,flags;
FtpCallbackProc proc;
{
    DEBUG3("RegisterFtpFd: ftpc=0x%x, fd=%d, flags=%d\n",ftpc,fd,flags);
    if (fd < 0 || fd >= NUMFDS) {
	fprintf(stderr,"YOW! Attempt to register fd %d!\n",fd);
#ifdef DEBUG
	abort();
#endif
	return;
    }
    ftpRegisteredFds[fd].proc = proc;
    ftpRegisteredFds[fd].context = ftpc;
    /*
     * In R4, we can't give a boolean combination of flags to XtAppAddInput().
     * Even if it doesn't complain, XtRemoveInput() won't remove the fd from
     * both select() masks, so we'll get the "select failed" message.
     */
    switch (flags) {
      case O_RDONLY:
	ftpRegisteredFds[fd].readid =
	    XtAppAddInput(appContext,fd,(XtPointer)XtInputReadMask,
			  inputCallback,NULL);
	ftpRegisteredFds[fd].writeid = (XtInputId)NULL;
	break;
      case O_WRONLY:
	ftpRegisteredFds[fd].readid = (XtInputId)NULL;
	ftpRegisteredFds[fd].writeid =
	    XtAppAddInput(appContext,fd,(XtPointer)XtInputWriteMask,
			  inputCallback,NULL);
	break;
      case O_RDWR:
	ftpRegisteredFds[fd].readid =
	    XtAppAddInput(appContext,fd,(XtPointer)XtInputReadMask,
			  inputCallback,NULL);
	ftpRegisteredFds[fd].writeid =
	    XtAppAddInput(appContext,fd,(XtPointer)XtInputWriteMask,
			  inputCallback,NULL);
	break;
    }
    DEBUG2("RegisterFtpFd: done: readid=0x%x, writeid=0x%x\n",
	   ftpRegisteredFds[fd].readid,ftpRegisteredFds[fd].writeid);
}

/*
 * Called back when X says source is ready. We call the function registered
 * by the FTP routines, passing the context.
 */
/*ARGSUSED*/
static void
inputCallback(client_data,source,id)
XtPointer client_data;
int *source;
XtInputId *id;
{
    if (ftpRegisteredFds[*source].proc != NULL) {
	(*(ftpRegisteredFds[*source].proc))(ftpRegisteredFds[*source].context);
    } else {
	fprintf(stderr,"YOW! Callback for fd=%d not registered!\n",*source);
#ifdef DEBUG
	abort();
#endif
    }
}

/*
 * This function is exported and used by the FTP routines to undo
 * a previous registration.
 */
/*ARGSUSED*/
void
UnregisterFtpFd(ftpc,fd)
FtpContext *ftpc;
int fd;
{
    DEBUG2("UnregisterFtpFd: ftpc=0x%x, fd=%d\n",ftpc,fd);
    if (fd < 0 || fd >= NUMFDS) {
	fprintf(stderr,"YOW! Attempt to unregister fd %d!\n",fd);
#ifdef DEBUG
	abort();
#endif
	return;
    }
    DEBUG2("UnregisterFtpFd: readid=0x%x, writeid=0x%x\n",
	   ftpRegisteredFds[fd].readid,ftpRegisteredFds[fd].writeid);
    if (ftpRegisteredFds[fd].readid != (XtInputId)NULL)
	XtRemoveInput(ftpRegisteredFds[fd].readid);
    ftpRegisteredFds[fd].readid = (XtInputId)NULL;
    if (ftpRegisteredFds[fd].writeid != (XtInputId)NULL)
	XtRemoveInput(ftpRegisteredFds[fd].writeid);
    ftpRegisteredFds[fd].writeid = (XtInputId)NULL;
    ftpRegisteredFds[fd].proc = NULL;
    ftpRegisteredFds[fd].context = NULL;
    DEBUG0("UnregisterFtpFd: done\n");
}

/*	-	-	-	-	-	-	-	-	*/
/* Routines for prompting during FTP transfers: */

static Widget ftpPromptShell;
static int ftpPromptResult;

int
ftpPrompt(ftpc)
FtpContext *ftpc;
{
    char str[256];

    if (ftpPromptShell == NULL)
	ftpPromptShell = createPopup("ftpPrompt",4,ftpPromptCallback);
    sprintf(str,"%s %s?",
	    (ftpc->filecmd==FTP_GET?"GET":"PUT"),ftpc->files[ftpc->this_file]);
    setPopupLabel(ftpPromptShell,"ftpPrompt",str);
    popupMainLoop(ftpPromptShell);
    return(ftpPromptResult);
}
 
/*ARGSUSED*/
static void
ftpPromptCallback(w,client_data,call_data)
Widget w;
XtPointer client_data;		/* button number */
XtPointer call_data;
{
    switch ((int)client_data) {
      case 0:			/* Yes */
	ftpPromptResult = 1;
	break;
      case 1:			/* No */
	ftpPromptResult = 0;
	break;
      case 2:			/* All */
	ftpCurrentContext->prompt = 0;
	ftpPromptResult = 1;
	break;
      case 3:			/* Abort */
	ftpCurrentContext->this_file = ftpCurrentContext->num_files;
	ftpPromptResult = 0;
	break;
    }
    popupDone();
}
