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

   Brewster@think.com
*/

/* 
 * 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.
 *   
 */

#include "ui.h"
#include "wutil.h"
#include "ustubs.h"

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

#define HOSTNAME_BUFFER_SIZE 120

/* 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);
  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 init_connection(inBuffer, outBuffer, bufferSize, connection, userInfo)
char *inBuffer, *outBuffer;
long bufferSize;
FILE *connection;
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);
    }
}

/* 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);
}

/* 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!
 */
long read_from_stream(d,buf,nbytes)
long d;				/* this is the stream */
char *buf;
long nbytes;
{
  long didRead;
  long toRead = nbytes;
  long totalRead = 0;		/* paranoia */

  while (toRead > 0){
    didRead = read (d, buf, toRead);
    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)
FILE *connection;
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. */
  
  if( request_length + HEADER_LENGTH
     != fwrite (request_message, 1L, request_length + HEADER_LENGTH, connection))
    return 0;

  fflush(connection);

  /* read for the first '0' */

  while(1){
    if(0 > read_from_stream(connection->_file, response_message, 1))
      return 0;
    if('0' == response_message[0])
      break;
  }

  if(0 > read_from_stream(connection->_file, 
				  response_message + 1, 
				  HEADER_LENGTH - 1))
    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++){
	read_from_stream(connection->_file, 
			 response_message + HEADER_LENGTH,
			 1);
/*	fread(response_message + HEADER_LENGTH, 1, 1, connection); */
      }
      return(0);
    }
  }
  if(0 > read_from_stream(connection->_file, 
				  response_message + HEADER_LENGTH,
				  response_length))
/*  if(0 > fread(response_message + HEADER_LENGTH,
	       1, response_length, connection)) */
    return 0;
  return(response_length);
}

/* 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;
FILE *connection;
boolean verbose;
{
  long response_length;

  /* ?
  if(verbose){
    printf ("sending");
    if(hostname_internal && strlen(hostname_internal) > 0)
      printf(" to host %s", hostname_internal);
    if(service_name && strlen(service_name) > 0)
      printf(" for service %s", service_name);
    printf("\n");
    twais_dsply_rsp_apdu(request_message + HEADER_LENGTH, 
			 request_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(connection != NULL) {
    if(0 == 
       (response_length =
	transport_message(connection, request_message,
			  request_length,
			  response_message,
			  response_buffer_length)))
      return(0);
  }
  else{
    if(0 == 
       (response_length =
	locally_answer_message(request_message, request_length, 
			       response_message,
			       response_buffer_length)))
      return(0);
  }
  if(verbose){
    printf ("decoded %ld bytes: \n", response_length);
    twais_dsply_rsp_apdu(response_message + HEADER_LENGTH, 
			 request_length);
  }
  return(response_length);
}

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

long close_connection(connection)
FILE *connection;
{
  long result = 0;
  
  if(connection != NULL){
    result = fclose(connection);
  }
  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;
  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=strlen(headline) - 1 ; i > 0; i--){
    if(isprint(headline[i])){
      break;
    }
    headline[i] = '\0';
  }
  return(headline);
}
