/* WIDE AREA INFORMATION SERVER SOFTWARE:
   No guarantees or restrictions.  See the readme file for the full standard
   disclaimer.

   Brewster@think.com
*/

/* Copyright (c) CNIDR (see ../COPYRIGHT) */


#ifndef lint
static char *RCSid = "$Header: /usr/users/freewais/FreeWAIS-0.1/ir/ui.c,v 1.1 1993/02/16 15:05:35 freewais Exp $";
#endif

/* Change log:
 * $Log: ui.c,v $
 * Revision 1.1  1993/02/16  15:05:35  freewais
 * Initial revision
 *
 * Revision 1.16  92/03/18  09:01:09  jonathan
 * don't free DocObjs[0]->Type, it's a copy of the input!
 * 
 * Revision 1.15  92/03/06  12:53:03  jonathan
 * Plug another memory leak.  From ericb@baker.dartmouth.edu (Eric J Bivona).
 * 
 * Revision 1.14  92/02/14  15:26:53  jonathan
 * Conditionalized interpret_message and locally_answer_message so client need
 * not include search engine.
 * 
 * Revision 1.13  92/02/12  13:53:41  jonathan
 * Added "$Log" so RCS will put the log message in the header
 * 
 * 
*/

/* 
 * this is a simple ui toolkit for building other ui's on top.
 * -brewster
 * 
 * top level functions:
 *   generate_search_apdu
 *   generate_retrieval_apdu
 *   interpret_message
 *
 */

/* to do:
 *   generate multiple queries for long documents.
 *     this will crash if the file being retrieved is larger than 100k.
 *   
 */

#ifdef __DECC
#define _DECC_V4_SOURCE
#endif /* DEC C, GEC */
#include "ui.h"
#include "wutil.h"
#include "ustubs.h"
#include "futil.h"
#ifdef __DECC
#undef _DECC_V4_SOURCE
#endif /* DEC C, GEC */

#include <ctype.h>
#include <errno.h>
#include <string.h>
#ifndef MULTINET
#ifdef SOCKETSHR /* BGT */
#include "socketshr_files:types.h"
#include "socketshr_files:socketshr.h"
#if defined(__DECC) && !defined(__alpha)
#undef fread
#undef fwrite
#undef fclose
#undef fprintf
#endif /* These Socketshr routines fail with DEC C on VAX, GEC */
#else /* BGT */
#include <sys/types.h>
#ifdef __DECC
#include <unixio.h>
#endif /* DEC C, PGE */
#endif /* BGT */
#else
#ifdef __DECC
#define _DECC_V4_SOURCE
#endif /* DEC C, GEC */
#include "multinet_root:[multinet.include.sys]types.h"
#ifdef __DECC
#undef _DECC_V4_SOURCE
#endif /* DEC C, GEC */
#endif /* BSN */

/* returns a pointer in the buffer of the first free byte.
   if it overflows, then NULL is returned 
   */
char *
generate_search_apdu(buff,
		     buff_len,
		     seed_words,
		     database_name,
		     docobjs,
		     maxDocsRetrieved)
     char* buff;     /* buffer to hold the apdu */
     long *buff_len;    /* length of the buffer changed to reflect new data written */
     char *seed_words;    /* string of the seed words */
     char *database_name;
     DocObj** docobjs;
     long maxDocsRetrieved;
{
  /* local variables */

  SearchAPDU *search3;
  char  *end_ptr;
  static char *database_names[2] = {"", 0};
  any refID;
  WAISSearch *query;
  refID.size = 1;
  refID.bytes = "3";

  database_names[0] = database_name;
  query = makeWAISSearch(seed_words,
                         docobjs, /* DocObjsPtr */
                         0L,
                         1L,	/* DateFactor */
                         0L,	/* BeginDateRange */
                         0L,	/* EndDateRange */
                         maxDocsRetrieved
                         );

  search3 = makeSearchAPDU(30L, 
			   5000L, /* should be large */
			   30L,
                           1L,	/* replace indicator */
                           "",	/* result set name */
                           database_names, /* database name */   
                           QT_RelevanceFeedbackQuery, /* query_type */
                           0L,	/* element name */
                           NULL, /* reference ID */
                           query);

  end_ptr = writeSearchAPDU(search3, buff, buff_len);

  CSTFreeWAISSearch(query);
  freeSearchAPDU(search3);
  return(end_ptr);
}


/* returns a pointer into the buffer of the next free byte.
   if it overflowed, then NULL is returned
   */

char *
generate_retrieval_apdu(buff,
			buff_len,
			docID,
			chunk_type,
			start,
			end,
			type,
			database_name)
     char *buff;
     long *buff_len;    /* length of the buffer changed to reflect new data written */
     any *docID;
     long chunk_type;
     long start;
     long end;
     char *type;
     char *database_name;
{
  SearchAPDU *search;
  char  *end_ptr;

  static char *database_names[2];
  static char *element_names[3];
  any refID;

  DocObj *DocObjs[2];
  any *query;			/* changed from char* by brewster */

  if(NULL == type)
    type = s_strdup("TEXT");

  database_names[0] = database_name;
  database_names[1] = NULL;

  element_names[0] = " ";
  element_names[1] = ES_DocumentText;
  element_names[2] = NULL;

  refID.size = 1;
  refID.bytes = "3";
  
  switch(chunk_type){
  case CT_line: 
    DocObjs[0] = makeDocObjUsingLines(docID, type, start, end);
    break;
  case CT_byte:
    DocObjs[0] = makeDocObjUsingBytes(docID, type, start, end);
    break;
  }
  DocObjs[1] = NULL;

  query = makeWAISTextQuery(DocObjs);   
  search = makeSearchAPDU( 10L, 16L, 15L, 
			  1L,	/* replace indicator */
			  "FOO", /* result set name */
			  database_names, /* database name */   
			  QT_TextRetrievalQuery, /* query_type */
			  element_names, /* element name */
			  &refID, /* reference ID */
			  query);
  end_ptr = writeSearchAPDU(search, buff, buff_len);
  /* s_free(DocObjs[0]->Type); it's a copy of the input, don't free it! */
  CSTFreeDocObj(DocObjs[0]);
  CSTFreeWAISTextQuery(query);
  freeSearchAPDU(search);
  return(end_ptr);
}

/* not currently used 
   
   static boolean isnumber _AP((char* string));
   
   static boolean isnumber(string)
   char *string;
   {
     long count;
     for(count = 0; count < strlen(string); count++){
       if(!isdigit(string[count])){
	 return(false);
       }
     }
     return(true);
   }
   
   */

/* this will negotiate with server, and returs the maximum buffer size 
   the server can handle.
   
   A connection should be established first using open_socket.
   
   */

long FW_init_connection(inBuffer, outBuffer, bufferSize, connection, userInfo)
     char *inBuffer, *outBuffer;
     long bufferSize;
#ifndef VMS
     FILE *connection;
#else
     long connection;
#endif /* VMS, BSN */
     char *userInfo;
{ 
  InitAPDU* init = NULL;
  InitResponseAPDU* reply = NULL;
  long result;
  /* construct an init */
  init = makeInitAPDU(true,false,false,false,false,bufferSize,bufferSize,
		      userInfo,defaultImplementationID(),
		    defaultImplementationName(),
		    defaultImplementationVersion(),NULL,userInfo);
  /* write it to the buffer */
  result = writeInitAPDU(init,inBuffer+HEADER_LENGTH,&bufferSize) - inBuffer;

  if(result < 0){
    freeInitAPDU(init);
    return(-1);
  }
  if(0 ==
     interpret_message(inBuffer,
		       result - HEADER_LENGTH,
		       outBuffer,
		       bufferSize,
		       connection,
		       false	/* true verbose */	
		       )) {
    /* error making a connection */
    return (-1);
  }
  if (readInitResponseAPDU(&reply,outBuffer + HEADER_LENGTH) == NULL){
    freeWAISInitResponse((WAISInitResponse*)reply->UserInformationField);
    freeInitResponseAPDU(reply);
    return(-1);
  }
  if (reply->Result == false)
    {				/* the server declined service */
      freeWAISInitResponse((WAISInitResponse*)reply->UserInformationField);
      freeInitResponseAPDU(reply);
      return(-1);
    }
  else				/* we got a response back */
    { result = reply->MaximumRecordSize;
      freeWAISInitResponse((WAISInitResponse*)reply->UserInformationField);
      freeInitResponseAPDU(reply);
      return(result);
    }
}

#ifdef LOCAL_SEARCH
/* returns the length of the response, 0 if an error */
long
locally_answer_message(request_message,
		       request_length,
		       response_message,
		       response_buffer_length)
     char *request_message;
     long request_length;
     char *response_message;
     long response_buffer_length;
{
  long request_length_internal = request_length;
  long response_length;
  WAISMessage header;
  long maxBufferSize = response_buffer_length;

  readWAISPacketHeader(request_message, &header);
  {
    char length_array[11];
    strncpy(length_array, header.msg_len, 10);
    length_array[10] = '\0';
    request_length_internal = atol(length_array);
  }
  /*
     printf("request length is %ld (%ld from caller)\n", 
     request_length_internal,
     request_length);
     */
  
  response_length =
    interpret_buffer(request_message + HEADER_LENGTH, 
		     request_length_internal,
		     response_message + HEADER_LENGTH,
		     response_buffer_length,
		     &maxBufferSize,
		     (long)header.hdr_vers,
		     NULL);
  if(0 > response_length)
    return(0);
  writeWAISPacketHeader(response_message,
			response_length,
			(long)'z', /* Z39.50 */
			"DowQuest  ", /* server name */
			(long)NO_COMPRESSION, /* no compression */
			(long)NO_ENCODING,(long)header.hdr_vers);
  return(response_length);
}
#endif

/* this is a safe version of unix 'read' it does all the checking
 * and looping necessary
 * to those trying to modify the transport code to use non-UNIX streams:
 *  This is the function to modify!
 */

#ifdef VMS
extern int HTDoRead();
#endif /* VMS, BSN */
#ifdef LINUX
long read_from_stream(stream,buf,nbytes)
     FILE *stream;		/* this is the stream */
#else
     long read_from_stream(d,buf,nbytes)
     long d;
#endif /* LINUX */
     
     char *buf;
     long nbytes;
{
  long didRead;
  long toRead = nbytes;
  long totalRead = 0;		/* paranoia */

  while (toRead > 0){

#ifdef LINUX
    didRead = fread (buf, sizeof(char), toRead, stream);
#else
#ifndef VMS
    didRead = read (d, buf, toRead);
#else
    didRead = HTDoRead ((int)d, buf, (unsigned)toRead);
#endif /* VMS, Use HTDoRead for Mosaic, BSN */
#endif /* LINUX */

    if(didRead == -1)		/* error*/
      return(-1);
    if(didRead == 0)		/* eof */
      return(-2);		/* maybe this should return 0? */
    toRead -= didRead;
    buf += didRead;
    totalRead += didRead;
  }
  if(totalRead != nbytes)	/* we overread for some reason */
    return(- totalRead);	/* bad news */    
  return(totalRead);
}

/* returns the length of the response, 0 if an error */

long 
transport_message(connection,
		  request_message,
		  request_length,
		  response_message,
		  response_buffer_length)
#ifndef VMS
     FILE *connection;
#else
     long connection;
#endif /* VMS, BSN */
     char *request_message;
     long request_length;
     char *response_message;
     long response_buffer_length;
{
  WAISMessage header;
  long response_length;

  
  /* Write out message. Read back header. Figure out response length. */
  
#ifndef VMS
  if( request_length + HEADER_LENGTH
     != fwrite (request_message, 1L, request_length + HEADER_LENGTH, connection))
    return 0;

  fflush(connection);
#else
  if( request_length + HEADER_LENGTH
#ifdef MULTINET
     != socket_write ((int)connection, request_message, request_length + HEADER_LENGTH))
#else
     != write ((int)connection, request_message, request_length + HEADER_LENGTH))
#endif /* MULTINET, BSN */
    return 0;

#endif /* VMS, BSN */

  /* read for the first '0' */

  while(1){

#if defined(LINUX) || defined(VMS)
    if(0 > read_from_stream(connection, response_message, 1))
#else
      if(0 > read_from_stream(fileno(connection), response_message, 1))
#endif /* LINUX, VMS */

	return 0;
    if('0' == response_message[0])
      break;
  }

#if defined(LINUX) || defined(VMS)
  if(0 > read_from_stream(connection, response_message + 1, HEADER_LENGTH - 1))
#else
    if(0 > read_from_stream(fileno(connection), response_message + 1, HEADER_LENGTH - 1))
#endif /* LINUX, VMS */

      return 0;

  readWAISPacketHeader(response_message, &header);
  {
    char length_array[11];
    strncpy(length_array, header.msg_len, 10);
    length_array[10] = '\0';
    response_length = atol(length_array);
    /*
       if(verbose){
       printf("WAIS header: '%s' length_array: '%s'\n", 
       response_message, length_array);
       }
       */
    if(response_length > response_buffer_length){
      /* we got a message that is too long, therefore empty the message out,
	 and return 0 */
      long i;
      for(i = 0; i < response_length; i++){

#if defined(LINUX) || defined(VMS)
	read_from_stream(connection, response_message + HEADER_LENGTH, 1);
#else
	read_from_stream(fileno(connection), response_message + HEADER_LENGTH, 1);
#endif /* LINUX, VMS */

      }
      return(0);
    }
  }

#if defined(LINUX) || defined(VMS)
  if(0 > read_from_stream(connection, response_message + HEADER_LENGTH,
			  response_length))
#else
    if(0 > read_from_stream(fileno(connection), response_message + HEADER_LENGTH,
			    response_length))
#endif /* LINUX, VMS */

      return 0;
  return(response_length);
}


/* ------------------------------------------------------------*/

/*  Facility to record messages sent and recieved for testing 
    and timing purposes. */

/* from c:
   
   putenv(strdup("IR_FILE=/users/menlo-park/brewster/tmp/infile"));
   
   from csh:
   
   setenv IR_FILE /users/menlo-park/brewster/tmp/infile
   
   */

boolean environment_variables_read = false; 
char* ir_file_environment_variable = "IR_FILE";
char* ir_file = NULL;

void read_environment_variables(host, port)
     char* host;
     char* port;
{
  
  if(!environment_variables_read){
    FILE *stream;
    ir_file = (char*)getenv(ir_file_environment_variable);
    if(ir_file){
      printf("IR_file: %s\n", ir_file);
      stream = fopen(ir_file, "w");
      fprintf(stream, "%s %s\n", host, port);
      fclose(stream);
    }
    environment_variables_read = true;
  }
}

/* returns 0 if success */
long write_message_to_file(message, length, filename)
     unsigned char *message;
     long length;
     char*filename;
{
  FILE *stream = fopen(filename, "a");
  if(NULL == stream)
    return(-1);
  
  printf("Writing to file: %s %ld characters\n", filename, length);

  fprintf(stream, "---------------------------------\n");
  
  if(length != fwrite(message, sizeof(unsigned char), 
		      length, stream)){
    perror("fwrite error");
    fclose(stream);
    return(-2);
  }

  fprintf(stream, "\n");

  if(0 != fclose(stream))
    return(-3);
  return(0);
}  

/* ------------------------------------------------------------*/

/* returns the number of bytes writeen.  0 if an error */
long
interpret_message(request_message,request_length,
		  response_message,
		  response_buffer_length,
		  connection,
		  verbose)
     char *request_message;
     long request_length; /* length of the buffer */
     char *response_message;
     long response_buffer_length;
#ifndef VMS
     FILE *connection;
#else
     long connection;
#endif /* VMS, BSN */
     boolean verbose;
{
  long response_length;

  writeWAISPacketHeader(request_message,
			request_length,
			(long)'z', /* Z39.50 */
			"wais      ", /* server name */
			(long)NO_COMPRESSION, /* no compression */
			(long)NO_ENCODING,(long)HEADER_VERSION);

  if(ir_file){
    long error_code;
    if(0 != (error_code = 
	     write_message_to_file((unsigned char*)request_message, 
				   request_length + HEADER_LENGTH, 
				   ir_file)))
      printf("Error writing log file Code: %ld\n", error_code);
  }
    
#ifndef VMS
  if(connection != NULL) {
#else
  if(connection != (long)NULL) {
#endif /* VMS, GEC for MPJZ */
    if(0 == 
       (response_length =
	transport_message(connection, request_message,
			  request_length,
			  response_message,
			  response_buffer_length)))
      return(0);
  }
  else{
#ifdef LOCAL_SEARCH    
    if(0 == 
       (response_length =
	locally_answer_message(request_message, request_length, 
			       response_message,
			       response_buffer_length)))
      return(0);
#else
    waislog(WLOG_HIGH, WLOG_ERROR, "Local search not supported in this version");
    return(0);
#endif
  }
  if(verbose){
    printf ("decoded %ld bytes: \n", response_length);
    twais_dsply_rsp_apdu(response_message + HEADER_LENGTH, 
			 request_length);
  }
  if(ir_file){
    long error_code;
    if(0 != (error_code = 
	     write_message_to_file((unsigned char*)response_message, 
				   response_length + HEADER_LENGTH, 
				   ir_file)))
      printf("Error writing log file Code: %ld\n", error_code);
  }
  return(response_length);
}

/* this closes the connection to the socket.
 * the mythology is that this is cleaner than exiting
 */

long FW_close_connection(connection)
#ifndef VMS
     FILE *connection;
#else
     long connection;
#endif /* VMS, BSN */
{
  long result = 0;
  
#ifndef VMS
  if(connection != NULL){
    result = fclose(connection);
#else
#ifdef MULTINET
  if(connection != (long)NULL){
    result = socket_close((int)connection);
#else
  if(connection != (long)NULL){
    result = close((int)connection);
#endif
#endif /* VMS, BSN, GEC for MPJZ */
  }
  return(result);
}

void
display_text_record_completely(record,quote_string_quotes)
     WAISDocumentText *record;
     boolean quote_string_quotes;
{
  long count;
  /* printf(" Text\n");
     print_any("     DocumentID:  ", record->DocumentID);
     printf("     VersionNumber:  %d\n", record->VersionNumber);
     */
  for(count = 0; count < record->DocumentText->size; count++){
    long ch = (unsigned char)record->DocumentText->bytes[count];
    if(27 == ch){
      /* then we have an escape code */
      /* if the next letter is '(' or ')', then ignore two letters */
      if('(' == record->DocumentText->bytes[count + 1] ||
	 ')' == record->DocumentText->bytes[count + 1])
	count += 1;             /* it is a term marker */
      else count += 4;		/* it is a paragraph marker */
    }
    else if (ch == '\t')	/* a TAB! */
      putc(ch, stdout);
    else if (isprint(ch)){
      if(quote_string_quotes && ch == '"')
	putc('\\', stdout);
      putc(ch, stdout);
    } 
    else if (ch == '\n' || ch == '\r')
      printf ("\n");
  }
}

/* modifies the string to exclude all seeker codes. sets length to
   the new length. */
char *delete_seeker_codes(string,length)
     char *string;
     long *length;
{
  long original_count;		/* index into the original string */
  long new_count = 0;		/* index into the collapsed string */
  for(original_count = 0; original_count < *length; original_count++){
    if(27 == string[original_count]){
      /* then we have an escape code */
      /* if the next letter is '(' or ')', then ignore two letters */
      if('(' == string[original_count + 1] ||
	 ')' == string[original_count + 1])
	original_count += 1;    /* it is a term marker */
      else original_count += 4; /* it is a paragraph marker */
    }
    else string[new_count++] = string[original_count];
  }
  *length = new_count;
  return(string);
}



/* returns a pointer to a string with good stuff */
char *trim_junk(headline)
     char *headline;
{
  long length = strlen(headline) + 1; /* include the trailing null */
  long i,j;
  headline = delete_seeker_codes(headline, &length);


  /* delete leading spaces */
  for(i=0; i < strlen(headline); i++){
    if(isprint(headline[i])){
      break;
    }
  }
  headline = headline + i;
  /* delete trailing stuff */
  for (i = 0, j = 0; i < strlen(headline)+1; i++) {
    if ((headline[i] != '\r') && (headline[i] != '\n')) {
      headline[j++] = headline[i];
    }
  }
  
 
  for(i=strlen(headline) - 1 ; i > 0; i--){
    if(isprint(headline[i])){
      break;
    }
    headline[i] = '\0';
  }
  
  return(headline);
}



