/*
 * query.c : Programmatic Prospero interface to Archie
 *
 * Copyright (c) 1991 by the University of Washington
 *
 * For copying and distribution information, please see the file
 * <copyright.h>.
 *
 * Originally part of the Prospero Archie client by Clifford 
 * Neuman (bcn@isi.edu).  Modifications, addition of programmatic interface,
 * and new sorting code by George Ferguson (ferguson@cs.rochester.edu) 
 * and Brendan Kehoe (brendan@cs.widener.edu).
 *
 * v2.0   - 04/23/93 (gf)  - for xarchie 2.0 - and now we part company...
 * v1.3.2 - bpk - for Archie client 1.3.2
 * v1.2.0 - 09/17/91 (bpk) - added BULL & USG stuff, thanks to Jim Sillas
 * v1.1.3 - 08/30/91 (bpk) - cast index()
 * v1.1.2 - 08/20/91 (bcn) - make it do it properly (new invdatecmplink)
 * v1.1.1 - 08/20/91 (bpk) - made sorting go inverted as we purport it does
 */
#include <copyright.h>
#include <stdio.h>
#include <pfs.h>
#include <perrno.h>
#include <archie.h>

#include "config.h"
#ifdef TM_IN_SYS_TIME
# include <sys/time.h>
#else
# include <time.h>
#endif
#include "stringdefs.h"
#include "xtypes.h"
#include "db.h"
#include "appres.h"
#include "browser.h"
#include "alert.h"
#include "status.h"
#include "debug.h"
extern DbEntry *db;

/* These are in dirsend.c */
extern int client_dirsrv_timeout,client_dirsrv_retry,rdgram_priority;

/* Functions defined here: */
void queryItemAndParse(),queryHostAndParse(),queryLocationAndParse();
VLINK stringQuery();
int parseArchieQueryResults(), parseStringQueryResults();
int handleProsperoErrors();

static void doQueryAndParse();
static void parseHostAndFilename(), parseAttributes();

/* Data defined here */
int pfs_debug;

/*	-	-	-	-	-	-	-	-	*/
/*
 * Main function to call to process a query from the user.
 */
void
queryItemAndParse(query)
char *query;
{
    char qstring[MAX_VPATH];

    DEBUG1("queryItemAndParse: \"%s\"\n",query);
    sprintf(qstring,"ARCHIE/MATCH(%d,%d,%c)/%s",
	    appResources.maxHits,appResources.offset,appResources.searchType,
	    query);
    doQueryAndParse(qstring);
    DEBUG0("queryItemAndParse: done\n");
}

/*
 * Main function to open a host specified by the user.
 */
void
queryHostAndParse(hostname)
char *hostname;
{
    char qstring[MAX_VPATH];

    DEBUG1("queryHostAndParse: \"%s\"\n",hostname);
    sprintf(qstring,"ARCHIE/HOST/%s",hostname);
    doQueryAndParse(qstring);
    DEBUG0("queryHostAndParse: done\n");
}

/*
 * Main function to open a host specified by the user.
 */
void
queryLocationAndParse(hostname,location)
char *hostname,*location;
{
    char qstring[MAX_VPATH];

    DEBUG2("queryLocationAndParse: \"%s:%s\"\n",hostname,location);
    sprintf(qstring,"ARCHIE/HOST/%s%s",hostname,location);
    doQueryAndParse(qstring);
    DEBUG0("queryLocationAndParse: done\n");
}

/*
 * Used by both query functions to send the string to Archie and interpret
 * the results into the browser.
 */
static void
doQueryAndParse(str)
char *str;
{
    VLINK links;
    int num;

    /* Send the query to Archie */
    DEBUG1("doQueryAndParse: \"%s\"\n",str);
    DEBUG0("calling stringQuery...\n");
    links = stringQuery(appResources.archieHost,str);
    DEBUG0("calling handleProsperoErrors...\n");
    (void)handleProsperoErrors();
    /* If we aborted or had an error, then don't clear the db */
    if (links == NULL ) {
	/* Wasn't an error... */
	if (perrno == PSUCCESS)
	    status0("No matches -- Ready.");
	return;
    }
    /* Reset browser to leftmost position */
    resetBrowser();
    /* Empty (and free) previous contents of database */
    clearEntries(db);
    /* Process the results into the database */
    status0("Parsing...");
    DEBUG0("calling parseArchieQueryResults\n");
    switch(appResources.sortType) {
	case GfName:
	    num = parseArchieQueryResults(db,links,cmpEntryNames);
	    break;
	case GfDate:
	    num = parseArchieQueryResults(db,links,cmpEntryDates);
	    break;
	case GfWeight:
	    num = parseArchieQueryResults(db,links,cmpEntryWeights);
	    break;
    }
    /* Display results in browser */
    DEBUG0("calling displayEntries\n");
    displayEntries(db,0);
    status1("Found %d matches -- Ready",(char *)num);
    DEBUG0("doQueryAndParse: done\n");
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * Returns an unsorted, untranslated list of vlinks for string from host.
 */
VLINK
stringQuery(host,string)
char *host,*string;
{
    VLINK links;		/* Matches returned by server */
    VDIR_ST dir_st;		/* Filled in by get_vdir      */
    PVDIR dir = &dir_st;
    VLINK p,nextp,r;
    int	tmp;
    
    /* initialize Prospero globals from appResources */
    pfs_debug = appResources.debugLevel;
    rdgram_priority = appResources.niceLevel;
    client_dirsrv_timeout = appResources.timeout;
    client_dirsrv_retry = appResources.retries;

    /* Initialize Prospero structures */
    perrno = PSUCCESS; *p_err_string = '\0';
    pwarn = PNOWARN; *p_warn_string = '\0';
    vdir_init(dir);
    
    /* Retrieve the list of matches, return error if there was one */
#if defined(MSDOS)
    if ((tmp=get_vdir(host,string,"",dir,
		      (long)GVD_ATTRIB|GVD_NOSORT,NULL,NULL)) != 0) {
#else
    if ((tmp=get_vdir(host,string,"",dir,
		      GVD_ATTRIB|GVD_NOSORT,NULL,NULL)) != 0) {
# endif
	perrno = tmp;
	return(NULL);
    }
    
    /* Save the links, and clear in dir in case it's used again   */
    links = dir->links; dir->links = NULL;
    
    /* As returned, list is sorted by suffix, and conflicting     */
    /* suffixes appear on a list of "replicas".  We want to       */
    /* create a one-dimensional list sorted by host then filename */
    /* and maybe by some other parameter                          */
    
    /* First flatten the doubly-linked list */
    for (p = links; p != NULL; p = nextp) {
	nextp = p->next;
	if (p->replicas != NULL) {
	    p->next = p->replicas;
	    p->next->previous = p;
	    for (r = p->replicas; r->next != NULL; r = r->next)
		/*EMPTY*/ ;
	    r->next = nextp;
	    nextp->previous = r;
	    p->replicas = NULL;
	}
    }
    perrno = PSUCCESS;
    return(links);
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * Here take the list of untranslated unsorted links and put them into the
 * database, translating and sorting as needed. The entries are added to
 * make a host-location-file hierarchy as appropriate for the top of the
 * database query for a query. Returns number of entries returned from query.
 * This routine is also used by the routine that reloads a database.
 */
int
parseArchieQueryResults(parent,links,cmp_proc)
DbEntry *parent;
VLINK links;
int (*cmp_proc)();
{
    VLINK vl;
    DbEntry *firstHost,*firstLoc;
    DbEntry *thisHost,*thisLoc,*thisFile;
    char hostname[MAX_VPATH],location[MAX_VPATH],filename[MAX_VPATH];
    int type;
#ifdef MSDOS
    unsigned long size;
#else
    int size;
#endif
    char *modes,*gt_date,*archie_date;
    int num;

    DEBUG0("parseArchieQueryResults: parsing links...\n");
    num = 0;
    firstHost = firstLoc = NULL;
    for (vl=links; vl != NULL; vl = vl->next) {
	parseHostAndFilename(vl,hostname,location,filename);
	parseAttributes(vl,&type,&size,&modes,&archie_date,&gt_date);
	if (firstHost == NULL) {
	    firstHost = thisHost = addEntry(parent,NULL);
	    firstLoc = thisLoc = addEntry(firstHost,NULL);
	    thisFile = addEntry(firstLoc,NULL);
	} else {
	    if ((thisHost=findEntryFromString(parent,hostname)) == NULL)
		thisHost = addEntry(parent,NULL);
	    if ((thisLoc=findEntryFromString(thisHost,location)) == NULL)
		thisLoc = addEntry(thisHost,NULL);
	    thisFile = addEntry(thisLoc,NULL);
	}
	setEntryData(thisHost,hostname,DB_HOST,0,"","","",NULL);
	setEntryData(thisLoc,location,DB_LOCATION,0,"","","",NULL);
	setEntryData(thisFile,filename,type,size,modes,archie_date,gt_date,vl);
	num += 1;
    }
    DEBUG0("parseArchieQueryResults: sorting entries...\n");
    sortEntriesRecursively(parent,cmp_proc);
    DEBUG1("parseArchieQueryResults: returning %d matches\n",num);
    return(num);
}

/*
 * Like parseArchieQueryresults(), but all the entries for the links are
 * added as immediate children of parent, rather than a three-level tree.
 * This is used to expand the browser below some item.
 */
int
parseStringQueryResults(parent,links,cmp_proc)
DbEntry *parent;
VLINK links;
int (*cmp_proc)();
{
    VLINK vl;
    DbEntry *dbp;
    char hostname[MAX_VPATH],location[MAX_VPATH],filename[MAX_VPATH];
    int type,size;
    char *modes,*gt_date,*archie_date;
    int num;

    DEBUG0("parseStringQueryResults: parsing links...\n");
    num = 0;
    for (vl=links; vl != NULL; vl = vl->next) {
	parseHostAndFilename(vl,hostname,location,filename);
	parseAttributes(vl,&type,&size,&modes,&archie_date,&gt_date);
	dbp = addEntry(parent,NULL);
	setEntryData(dbp,filename,type,size,modes,archie_date,gt_date,vl);
	num += 1;
    }
    DEBUG0("parseStringQueryResults: sortign entries...\n");
    sortEntriesRecursively(parent,cmp_proc);
    DEBUG1("parseStringQueryResults: returning %d matches\n",num);
    return(num);
}
    
/*
 * Fills in hostname, location, and filename with the appropriately-translated
 * and adjusted information from the link vl.
 */
static void
parseHostAndFilename(vl,hostname,location,filename)
VLINK vl;
char *hostname,*location,*filename;
{
    char *slash;

    DEBUG3(" input:host=\"%s\"\n       filename=\"%s\"\n       name=\"%s\"\n",
	   vl->host,vl->filename,vl->name);
    /* If the link is for an Archie pseudo-directory, adjust names. */
    if (strcmp(vl->type,"DIRECTORY") == 0 &&
	strncmp(vl->filename,"ARCHIE/HOST",11) == 0) {
	strcpy(hostname,vl->filename+12);
	slash = index(hostname,'/');
	if (slash != NULL) {
	    strcpy(filename,slash);
	    *slash = '\0';
	} else
	    strcpy(filename,"/");
    } else {
	/* else just use the names as is */
	strcpy(hostname,vl->host);
	strcpy(filename,vl->filename);
    }
    /* The "location" is the leading part of the pathname */
    strcpy(location,filename);
    slash = rindex(location,'/');
    /* If filename ends with slash, try going back one more slash */
    if (slash && *(slash+1) == '\0')
	slash = (char *)rindex(slash,'/');
    if (slash) {
	strcpy(filename,slash+1);
	*slash = '\0';
    } else
	strcpy(location,"/");
    /* If filename was /foo, then we need to leave the slash there */
    if (*location == '\0')
	strcpy(location,"/");
    DEBUG3(" output:host=\"%s\"\n        location=\"%s\"\n        filename=\"%s\"\n",
	   hostname,location,filename);
}

/*
 * Fills in *sizep, *modesp, and archie_date with the information in the
 * attribute list of the link vl.
 */
static void
parseAttributes(vl,typep,sizep,modesp,archie_datep,gt_datep)
VLINK vl;
int *typep;
#ifdef MSDOS
unsigned long *sizep;
#else
int *sizep;
#endif
char **modesp,**archie_datep,**gt_datep;
{
    static char date[64];
    PATTRIB ap;
    int  gt_year,gt_mon,gt_day,gt_hour,gt_min;
    struct tm *presenttime;
    long now;

    (void)time(&now);
    presenttime = localtime(&now);
    if (strcmp(vl->type,"DIRECTORY") == 0) {
	*typep = DB_DIRECTORY;
    } else {
	*typep = DB_FILE;
    }
    *sizep = 0;
    *modesp = "";
    *archie_datep = "";
    *gt_datep = "";
    gt_year = gt_mon = gt_day = gt_hour = gt_min = 0;
    for (ap = vl->lattrib; ap; ap = ap->next) {
	if (strcmp(ap->aname,"SIZE") == 0) {
#ifdef MSDOS
	    sscanf(ap->value.ascii,"%lu",sizep);
#else
	    sscanf(ap->value.ascii,"%d",sizep);
#endif
	} else if(strcmp(ap->aname,"UNIX-MODES") == 0) {
	    *modesp = ap->value.ascii;
	} else if(strcmp(ap->aname,"LAST-MODIFIED") == 0) {
	    *gt_datep = ap->value.ascii;
	    sscanf(*gt_datep,"%4d%2d%2d%2d%2d",&gt_year,
		   &gt_mon, &gt_day, &gt_hour, &gt_min);
	    if ((12 * (presenttime->tm_year + 1900 - gt_year) +
		 presenttime->tm_mon - gt_mon) > 6)
		sprintf(date,"%s %2d %4d",month_sname(gt_mon),
			gt_day, gt_year);
	    else
		sprintf(date,"%s %2d %02d:%02d",month_sname(gt_mon),
			gt_day, gt_hour, gt_min);
	    *archie_datep = date;
	}
    }
}

/*
 * Pops up alerts depending on perrno and pwarn.
 * Used in several places after calling Archie.
 */
int
handleProsperoErrors()
{
    int err = 0;

    /* Error? */
    if (perrno != PSUCCESS) {
	if (p_err_text[perrno]) {
	  if (*p_err_string)
		alert2("Prospero error: %.100s - %.100s",p_err_text[perrno],
			p_err_string);
	  else
		alert1("Prospero error: %.200s",p_err_text[perrno]);
	} else
	    alert1("Prospero error: Undefined error %d (prospero)",(char*)perrno);
	err = 1;
    }
    /* Warning? */
    if (pwarn != PNOWARN) {
        if (*p_warn_string)
            alert2("Prospero warning: %.100s - %.100s",
                   p_warn_text[pwarn], p_warn_string);
        else
            alert1("Prospero warning: %.200s",p_warn_text[pwarn]);
        status0("Ready");
    }
    return(err);
}
