static char rcsid[] = "waisquery.c,v 1.17 1995/09/06 20:51:54 duane Exp";
/*
 *  waisquery.c - Code for "in-line" Harvest Broker queries using a
 *              WAIS, Inc. WAISserver and the z39.50v2 protocol.
 * 
 *  Darren Hardy, hardy@cs.colorado.edu, May 1995
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

/* -------------------------------------------------------------------------- */
/* Based on demo-client.c from WAIS, Inc's client-toolkit. */
/* -------------------------------------------------------------------------- */
/*****************************************************************************
*  (c) Copyright 1994 Wide Area Information Servers, Inc                     *
*      All rights reserved.                                                  *
*                                                                            *
*  This code can be freely copied and used for any purpose as long as        *
*  this notice and copyright notice is included without modification         *
*  at the top of the file.                                                   *
*                                                                            *
*****************************************************************************/
/*---------------------------------------------------------------------------
  ---------------------------------------------------------------------------
 Module:   demo-main.c

 Purpose:  The client-main module provides an example program of how to use
           the client protocol toolkit library.  It makes use of the
           Application Programmers Interface (API) described by the following
           header files:
                         nConnMgr.h
                         appConnMgr.h
                         appConnCli.h
---------------------------------------------------------------------------*/
#include <appConnCli.h>

#define BUFFER_SIZE (256 * 1024)	/* need a big buffer */
#define CLIENT_DESC \
	"Harvest Broker (inline Z39.50-1988 query) v1.3"

/* USE_CONN_PER_QUERY - forces one connection per query to server */
#ifndef USE_CONN_PER_QUERY
#define USE_CONN_PER_QUERY
#endif

/* 
 *  Operations to perform a search:
 *      do_wais_inline_search   - wrapper for doing a search
 *      teardown_search         - 1 time teardown
 *      setup_search            - 1 time initialization (local)
 *      perform_search          - do a search (local)
 */
appConn *conn = NULL;
nConn *net = NULL;
char *theHost = NULL;
int thePort = -1;
char *theDBname = NULL;
char theBuffer[BUFFER_SIZE];


/*---------------------------------------------------------------------------
  ---------------------------------------------------------------------------
 Function:   init
 Purpose:    Initializes the application connection, given by the appConn,
             conn.
 Called by:  main
 Parameters: conn   -- input; a handle to an appConn. It holds
                       information about the state of the connection;
                       may be modified by this function.
             client -- input; a null-terminated string whose value is
                       the client performing the request.
             diags  -- output; a handle to a list of diagnostics,
                       where each item in the list is of type 
                       appConnDiag (see appConnMgr.c).  If the
                       operation was unsuccessful, a list of diags
                       are returned in this parameter.
 Returns:    boolean, true(1) if successful, false(0) otherwise.
---------------------------------------------------------------------------*/

static boolean
 init(appConn * conn,
      char *client,
      list * diags)
{
	long pduType = appConnUnknown;
	appConnStatus as;

	appConnInitReqWrite(conn, client, "", "", NULL, diags);

	if (*diags != NULL)
		return (false);

	while (((as = appConnPduInfo(conn, &pduType, NULL, diags))
		== appConnStatusNotFinished) && (*diags == NULL));

	if (*diags != NULL)
		return (false);

	if (pduType != appConnInitResp)
		return (false);

	while (((as = appConnInitRespRead(conn, NULL, diags))
		== appConnStatusNotFinished) && (*diags == NULL));

	if (*diags != NULL)
		return (false);

	return (true);
}



/*---------------------------------------------------------------------------
  ---------------------------------------------------------------------------
 Function:   search
 Purpose:    Performs a search of a query on a database using the application
             connection, conn.
 Called by:  Demo functions: testSearchAndRetrieveByRecId
                             testSearchWithRFAndRetrieveByPosition
 Parameters: conn         -- input; a handle to an appConn. It holds
                             information about the state of the connection;
                             may be modified by this function.
             maxHits      -- input; a long integer whose value is the
                             maximum number of results the server is 
                             requested to generate. maxHits results are
                             to be stored on the server for the duration
                             of this search.
             qry          -- input; a handle to an appConnQry data type
                             containing the user's query and is created
                             using the appQryAddTerm() function.
             qryTermsUsed -- output; a handle to a list of null-terminated
                             character strings.  Each character string
                             contains the query terms used by the server
                             in the search; these terms may or may not
                             have been specified in the original query.
                             The terms are dependent on the behavior of the
                             server and the contents of the database.  
                             Stop words may have been eliminated, right-
                             truncated words may have been expanded, and
                             synonyms may have been included.
             hits         -- output; a handle to a list.  Each item
                             in the list is of type appConnHeadline, and
                             represents one element of the result set.  
                             The number of hits returned may be less than
                             or equal to the number of hits requested.
             diags        -- output; a handle to a list of diagnostics,
                             where each item in the list is of type 
                             appConnDiag (see appConnMgr.c).  If the
                             operation was unsuccessful, a list of diags
                             are returned in this parameter.
 Returns:    boolean, true(1) if successful, false(0) otherwise.
---------------------------------------------------------------------------*/
static boolean
 search(appConn * conn,
	long maxHits,
	appConnQry * qry,
	list * qryTermsUsed,
	list * hits,
	list * diags)
{
	long pduType = appConnUnknown;
	appConnStatus as;
	long totalNumHits, currentNumHits, requestedNumHits;

	/* Send the search request to the remote system. */
	appConnSearchReqWrite(conn, maxHits, maxHits, rankByServerDefault,
			      theDBname, qry, NULL, diags);

	if (*diags != NULL)
		return (false);

	/* Receive the response. */
	while (((as = appConnPduInfo(conn, &pduType, NULL, diags))
		== appConnStatusNotFinished) && (*diags == NULL));

	if (*diags != NULL)
		return (false);

	/* Make sure the response has the correct packet type. */
	if (pduType != appConnSearchResp)
		return (false);

	/* Read the response and get the headliness. */
	while (((as = appConnSearchRespRead(conn, &totalNumHits, qryTermsUsed,
					    hits, NULL, diags))
		== appConnStatusNotFinished) && (*diags == NULL));

	if (*diags != NULL)
		return (false);

	/* Remember how many headlines we currently have,
	   and how many we want to receive. */
	currentNumHits = length(*hits);
	maxHits = (maxHits < 0) ? MAXLONG : maxHits;
	requestedNumHits = MIN(maxHits, totalNumHits);

	/* If not enough headlines were returned, keep asking for more headlines. */
	while (currentNumHits < requestedNumHits) {
		/* Send a request for the next set of headlines. */
		as = appConnNextHeadlinesReqWrite(conn,
						  1 + currentNumHits,
				       requestedNumHits - currentNumHits,
						  NULL, NULL);

		if (as == appConnStatusUndefinedFunction)
			break;
		if (*diags != NULL)
			return (false);

		/* Receive the response. */
		while (((as = appConnPduInfo(conn, &pduType, NULL, diags))
			== appConnStatusNotFinished) && (*diags == NULL));

		if (*diags != NULL)
			return (false);

		/* Make sure the response has the correct packet type. */
		if (pduType != appConnNextHeadlinesResp)
			return (false);

		/* Read the response and get the headliness. */
		while (((as = appConnNextHeadlinesRespRead(conn, hits, NULL, diags))
			== appConnStatusNotFinished) && (*diags == NULL));

		if (*diags != NULL)
			return (false);

		/* Update the current headline count. */
		currentNumHits = length(*hits);
	}

	return (true);
}


/*---------------------------------------------------------------------------
  ---------------------------------------------------------------------------
 Function:   displaySearchResults
 Purpose:    Displays search results (hits) to stdout.
 Called by:  Test functions: testSearchAndRetrieveByRecId
                             testSearchWithRFAndRetrieveByPosition
 Parameters: hits -- input; a list of hits where each hit is of datatype
                     appConnHeadline.
 Returns:    void
---------------------------------------------------------------------------*/
static void displaySearchResults(FILE * fp, list hits)
{
	appConnHeadline *hit;
	appConnElement *element;
	appConnVariant *variant;
	long i, j, k;

	for (i = 0; i < length(hits); i++) {
		long count = 0, len = 0;
#ifdef DEBUG
		printf("Displaying Hit %d\n", i);
#endif
		hit = nth(hits, i);

		for (j = 0; j < length(hit->elements); j++) {
			element = nth(hit->elements, j);

			/* If only one variant exists. */
			if (length(element->variants) == 1) {
				variant = first(element->variants);
				len = variant->size;
#ifdef DEBUG
		printf("length is is only one variant: %d\n", len);
#endif
			} else {
#ifdef DEBUG
		printf("length is in many variants:\n");
#endif
				for (k = 0; k < length(element->variants); k++) {
					variant = nth(element->variants, k);
					len += variant->size;
#ifdef DEBUG
		printf("adding variant: %d = %d\n", variant->size, len);
#endif
				}
			}
		}
#ifdef DEBUG
		printf("RESULT: %3ld   score %4ld len %ld %s\n",
			hit->rank, hit->score, len < 0 ? -len : len,
			hit->headline);
#endif
		fprintf(fp, "%3ld   score %4ld len %ld %s\n",
			hit->rank, hit->score, len < 0 ? -len : len,
			hit->headline);
	}
	fflush(fp);
}



/*---------------------------------------------------------------------------
  ---------------------------------------------------------------------------
 Function:   displayDiagsAndCleanup
 Purpose:    Display the diagnostics to stdout, and frees qry, hits, data, 
             and diags.
 Called by:  Test functions: testSearchAndRetrieveByRecId
                             testSearchWithRFAndRetrieveByPosition
 Parameters: qry   -- input; a handle to an appConnQry data type created
                      by the appQryAddTerm() function.
             hits  -- input; a handle to a list.  Each item in the list is
                      of type appConnHeadline.
             data  -- input; a character string containing the retrieved data
                      to be freed.
             diags -- output; a handle to a list of diagnostics, where each
                      item in the list is of type appConnDiag (see 
                      appConnMgr.c).  If the operation was unsuccessful, 
                      a list of diags are returned in this parameter.
 Returns:    void
---------------------------------------------------------------------------*/
static void displayDiagsAndCleanup(appConnQry * qry,
				   list hits,
				   char *data,
				   list diags)
{
	appConnDiag *diag;
	long i;

	for (i = 0L, diag = nth(diags, 0L); i < length(diags); i++, diag = nth(diags, i))
		printf("Diagnostic code %ld, %s\n", diag->code, diag->info);

#ifdef DEBUG
	printf("Cleaning up the query...\n");
#endif

	/* Clean up. */
	appConnQryFree(qry);

	/* Free the hits from the search. */
	for (i = 0L; i < length(hits); i++) {
		appConnHeadlineFree(nth(hits, i));
#ifdef DEBUG
		printf("Cleaning up query hit %d...\n", i);
#endif
	}
	freeList(hits);

	safeFree(data);

	/* Free the diags. */
	for (i = 0L; i < length(diags); i++) {
		appConnDiagFree(nth(diags, i));
#ifdef DEBUG
		printf("Cleaning up diag %d...\n", i);
#endif
	}
	freeList(diags);

}

/*----------------------------------------------------------------------*/
/*---------------------- Harvest Functions -----------------------------*/
/*----------------------------------------------------------------------*/

/* 
 *  teardown_search() - Kills the connection to the WAIS server
 */
void teardown_search()
{
#ifdef DEBUG
	printf("teardown_search: cleaning up state...\n");
#endif
	/* clean up state */
	if (theDBname)
		free(theDBname);
	theDBname = NULL;
	if (theHost)
		free(theHost);
	theHost = NULL;
	thePort = -1;

	if (conn == NULL)
		return;		/* nothing to do */

#ifdef DEBUG
	printf("teardown_search: cleaning up connection...\n");
#endif
	/* Clean up TCP nConn connection and Application appConn connection. */
	nConnFree(net);
	appConnFree(conn);
	nConnTypesFree();
	appConnTypesFree();

	conn = NULL;		/* reset state */
}

/*
 *  perform_search - send query to server, and print results
 */
static void perform_search(FILE * fp, char *qryTxt, int maxhits)
{
	appConnQry *qry = NULL;
	list hits = NULL;
	list diags = NULL;
	long i;

	/* Create query, search, and display results. */
	qry = appConnQryMakeLeafFreeText(qryTxt);
	if (search(conn, (long) maxhits, qry, NULL, &hits, &diags)) {
#ifdef DEBUG
		printf("Got %d hits (max %d)\n", length(hits), maxhits);
#endif
		displaySearchResults(fp, hits);
	} else {
		printf("Search (%s) failed.\n", qryTxt);
	}
	displayDiagsAndCleanup(qry, hits, NULL, diags);
}

/*
 *  setup_search - establish a connection to the wais server.
 */
static int setup_search(char *host, long port)
{
	char *nConnTypeName = "TCP";
	char *appConnTypeName = "Z39501988";	/* .v2 breaks! */
	list diags = NULL;

	/* force a reset if needed */
	if (conn != NULL)
		teardown_search();

	/* set state */
	thePort = port;
	if (theHost)
		free(theHost);
	theHost = strdup(host);

#ifdef DEBUG
	printf("Register the client application-layer protocols.\n");
#endif
	/* Register the client application-layer protocols. */
	appConnClientInit();

#ifdef DEBUG
	printf("Open a TCP connection.\n");
#endif
	/* Open a TCP connection. */
	if (nConnOpen(&net, nConnTypeName, 2L, theHost, thePort) !=
	    nConnStatusOK) {
		printf("Unable to open a %s network connection to %s:%ld.\n",
		       nConnTypeName, theHost, thePort);
		nConnTypesFree();
		appConnTypesFree();
		return 1;
	}
#ifdef DEBUG
	printf("Using the TCP conn, open a application protocol conn.\n");
#endif
	/* Using the TCP connection, open a application protocol connection. */
	if (appConnOpen(&conn, appConnTypeName, net, theBuffer,
			BUFFER_SIZE, 1L, 0L) != appConnStatusOK) {
		printf("Unable to open a %s application connection.\n",
		       appConnTypeName);
		nConnTypesFree();
		appConnTypesFree();
		nConnFree(net);
		return 1;
	}
#ifdef DEBUG
	printf("Initialize and display any diagnostics that result.\n");
#endif
	/* Initialize and display any diagnostics that result. */
	if (!init(conn, CLIENT_DESC, &diags)) {
		printf("Cannot initialize %s connection using protocol %s.\n",
		       nConnTypeName, appConnTypeName);
		displayDiagsAndCleanup(NULL, NULL, NULL, diags);
		nConnTypesFree();
		appConnTypesFree();
		nConnFree(net);
		appConnFree(conn);
		return 1;
	}
#ifdef DEBUG
	printf("setup_search done\n");
#endif
	return 0;
}

/*
 *  do_search - Submits 'query' to the WAIS server running on host:port, 
 *              and writes the result set in fp.
 *
 *  Returns non-zero on error; otherwise, returns 0.
 */
int do_wais_inline_search(FILE * fp,
			  char *host,
			  long port,
			  char *query,
			  char *dbname,
			  int maxhits)
{
#ifdef DEBUG
	printf("CALLING do_wais_inline_search: %p, %s, %d, %s, %s, %d\n",
		fp, host, port, query, dbname, maxhits);
#endif
	/* 
	 *  Init the search, if this is our first time through, or
	 *  the query is on a different database that the last one 
	 */
	if ((conn == NULL) ||
	    (strcmp(host, theHost) != 0) ||
	    (port != thePort) ||
	    (strcmp(dbname, theDBname) != 0)) {
		printf("Switching to WAIS server (%s:%d) for %s...\n",
		       host, port, dbname);
		if (setup_search(host, port)) {
			printf("ERROR: Cannot connect to WAIS server %s:%d.\n",
			       host, port);
			teardown_search();	/* force reset */
			return 1;
		}
		theDBname = strdup(dbname);
	}
#ifdef DEBUG
	else
		printf("Doing inline search using previous connection: %s\n", query);
#endif
	/* do the search, and send results to fp */
	perform_search(fp, query, maxhits);

#ifdef USE_CONN_PER_QUERY
	/* this will reset the connection for each query */
	teardown_search();
#endif

	return 0;
}

#ifdef USE_MAIN
/* simple test program.  submit queries to stdin. */
int main(argc, argv)
int argc;
char *argv[];
{
	char buf[BUFSIZ];
	if (argc != 5) {
		printf("Usage: %s host port dbname maxhits\nqueries to stdin",
		       argv[0]);
		exit(1);
	}
	while (fgets(buf, BUFSIZ, stdin)) {
		do_wais_inline_search(stdout,
				      argv[1],
				      atoi(argv[2]),
				      buf,
				      argv[3],
				      atoi(argv[4]));
	}
	teardown_search();
	exit(0);
}
#endif
