/* WAISgopher daemon...

   Paul Lindner, March 1992
   <lindner@boombox.micro.umn.edu>
*/

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

#include <ctype.h>
#include <string.h>
#include <ui.h>
#include <sockets.h>
#include <docid.h>

/*#include <netinet/in.h>
#include <netdb.h>
*/
#define MAIN
#include "wais.h"

#define MAX_MESSAGE_LEN 100000
#define CHARS_PER_PAGE 10000 /* number of chars retrieved in each request */
#define MAX_FILE_NAME_LEN 1000

#define WAISSEARCH_DATE "Fri Sep 13 1991"

char* log_file_name = NULL;
FILE* logfile = NULL;


void
PrintStatus(str)
char * str;
{
  fprintf(stderr, "%s", str);
}


void ZapTabs(in)
  char *in;
{
     /** replace tabs with a space... **/
     while (*in != '\0') {
	  if (*in == '\t')
	       *in = ' ';
	  in ++;
     }
}

/*** Modified from ../ir/ui.c to add \r\n at the ends of the line... ***/
void
Mydisplay_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 ("\r\n");
  }
}



/* modified from Jonny G's version in ui/question.c */
void showDiags(d)
  diagnosticRecord **d;
{
     long i;
     
     for (i = 0; d[i] != NULL; i++) {
	  if (d[i]->ADDINFO != NULL) {
	       printf("Code: %s, %s\n", d[i]->DIAG, d[i] ->ADDINFO);
	  }
     }
}



void
ZapCRLF(inputline)
char *inputline;
{
     char *cp;
     
     cp = strchr(inputline, '\r');    /* Zap CR-LF */
     if (cp != NULL)
          *cp = '\0';
     else {
          cp = strchr(inputline, '\n');
          if (cp != NULL)
               *cp = '\0';
     }
}


int
acceptable(foo)
  char foo;
{
     if (foo == '\t' || foo == '\r' || foo == '\n' || foo == '\0')
	  return(0);
     else if (!isprint(foo))
	  return(0);
     else
	  return(1);
}



static char * hex = "0123456789ABCDEF";

/*      Decode one hex character
*/

char 
from_hex(c)
  char c;
{
     return                (c>='0')&&(c<='9') ? c-'0'
	  : (c>='A')&&(c<='F') ? c-'A'+10
	       : (c>='a')&&(c<='f') ? c-'a'+10
		    :                      0;
}


/*
 * DocId_to_Gopher transforms a docid into a character string suitable for
 * transmission
 */

char *DocId_to_Gopher(docid, docsize)
  any *docid;
{
     static char GopherString[512];
     char *q = GopherString;
     char *p;
     int l, i;
          
     /** First lets stick on the size of the document first **/

     sprintf(GopherString, "%d", docsize);
     q += strlen(GopherString);
     *q++ = ':';

     for (p=docid->bytes; (p < docid->bytes + docid->size) && q<&GopherString[512];) {
	  if (*p >= 10) {
	       ; /* Bad thing happened, can't understand docid, punt */
	       return(NULL);
	  }
	  
	  *q++ = (*p++) + '0';   /* Record Type */
	  *q++ = '=';            /* Seperate */
	  l = *p++;              /* Length */
	  for (i=0; i<l; i++, p++) {
	       if (acceptable(*p)==0) {
		    *q++ = '%';
		    *q++ = hex[(*p) >> 4];
		    *q++ = hex[(*p) & 15];
	       }
	       else *q++ = *p;
	  }
	  *q++ = ';';
     }
     *q++ = 0;
     return(GopherString);
}

/*
 * Gstring is a name produced by DocID_to_Gopher
 */

any *Gopher_to_DocId(Gstring, DocSize)
  char *Gstring;
  int *DocSize;
{
     static any docid;
     char *outptr;
     char *inptr; 
     char *sor;
     char *eqptr;  
     char *semiptr;
     int size; 

     /* Strip off the Document size first.... */
     
     inptr = strchr(Gstring, ':');
     if (inptr == NULL) 
	  return;

     *DocSize = atoi(Gstring);
     
     Gstring = inptr +1;

     for (size=0, inptr=Gstring; *inptr; inptr++) {
	  size ++;
	  if (*inptr == ';') 
	       size--;
	  else if (*inptr ==  '%') 
	       size -=2;
	  
     }

     docid.size = size;

     docid.bytes = (char *) malloc(docid.size);
     outptr = docid.bytes;

     for (inptr = Gstring; *inptr;) {
	  *outptr++ = *inptr++ - '0';  /* Record Type */
	  eqptr = strchr(inptr, '=');
	  if (!eqptr)
	       return(0);
	  semiptr = strchr(inptr, ';');
	  if (!semiptr)
	       return(0);
	  sor = outptr;
	  outptr++;

	  for (inptr = eqptr+1; *inptr!=';' ; ) {
	       if (*inptr == '%') {
		    char c;
		    unsigned int b;
		    
		    inptr++;
		    c = *inptr++;
		    b = from_hex(c);
		    c = *inptr++;
		    if (!c) break;
		    *outptr++ = (b<<4) + from_hex(c);
	       } else {
		    *outptr++ = *inptr++;
	       }
	  }
	  
	  *sor = (outptr-sor-1);
	  inptr++;
     }
     
     return(&docid);

}
		    


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

/* modified from tracy shen's version in wutil.c
 * displays either a text record of a set of headlines.
 */
void
display_search_response(response, hostname, port, dbname, GateHost, GatePort, SourceName)
  SearchResponseAPDU *response;
  char *hostname, *port, *dbname, *GateHost, *GatePort, *SourceName;
{
     WAISSearchResponse  *info;
     long continue_viewing;
     long i, k;
     struct sockaddr_in serv_addr;
     int length = sizeof(serv_addr);
     int GopherPort;
     
     /** Try to figure out the port we're running on. **/
     
     if (getsockname(0, &serv_addr,&length) == 0)
	  GopherPort = ntohs(serv_addr.sin_port);
     
     if ( response->DatabaseDiagnosticRecords != 0 ) {
	  info = (WAISSearchResponse *)response->DatabaseDiagnosticRecords;
	  i =0; 
		  
	  if (info->Diagnostics != NULL)
	       showDiags(info->Diagnostics);
	  
	  if ( info->DocHeaders != 0 ) {
	       k =0;
	       while (info->DocHeaders[k] != 0 ) {
		    i++;
		    ZapCRLF(info->DocHeaders[k]->Headline);
		    ZapTabs(info->DocHeaders[k]->Headline);
		    printf("0%s\t-%s:%s\t%s\t%d\r\n", 
			   info->DocHeaders[k]->Headline,
			   SourceName,
			   DocId_to_Gopher(info->DocHeaders[k]->DocumentID, info->DocHeaders[k]->DocumentLength),
			   GateHost,
			   GopherPort
		      );

		    k++;
	       }
	       printf(".\r\n");
	  }

	  if ( info->Text != 0 ) {
	       k =0;
	       while ( (continue_viewing == 1) && (info->Text[k] != 0) ) {
		    i++;
		    printf("\n    Text record %2d, ", i);
		    Mydisplay_text_record_completely( info->Text[k++], false);
	       }
	  }
     }				/* display user info */
}



#define MAX_KEYWORDS_LENGTH 1000
#define MAX_SERVER_LENGTH 1000
#define MAX_DATABASE_LENGTH 1000
#define MAX_SERVICE_LENGTH 1000
#define MAXDOCS 40

/* any*
     copy_any(thing)
any *thing;
{
     int i;
     any* result;
     
     result = NULL;
     
     if(thing != NULL) {
	  if((result = (any*)s_malloc(sizeof(any))) != NULL) {
	       result->bytes = NULL;
	       result->size = thing->size;
	       if((result->bytes = s_malloc(thing->size)) != NULL) {
		    for(i = 0; i < thing->size; i++)
			 result->bytes[i] = thing->bytes[i];
	       }
	  }
     }
     return result;
}

*/
/******************************************************************/

void main(argc, argv)
  int argc;
  char *argv[];
{
     char* request_message = NULL; /* arbitrary message limit */
     char* response_message = NULL; /* arbitrary message limit */
     long request_buffer_length;	/* how of the request is left */
     SearchResponseAPDU  *query_response;
     SearchResponseAPDU  *retrieval_response;
     WAISSearchResponse  *query_info, *retrieval_info;
     char server_name[MAX_SERVER_LENGTH + 1];	
     char service[MAX_SERVICE_LENGTH + 1];
     char database[MAX_DATABASE_LENGTH + 1];
     char *next_argument, *command_name;
     long count;
     FILE *connection;
     int Searching = 1;  /** Default is to search **/
     char *keywords;
     char *DocIdString;
     char *Inputline;
     char *Dotsrcfile;
     char *SourceName;
     char *WaisGateHost=NULL, *WaisGatePort=NULL;
     
     next_argument = next_arg(&argc, &argv);
     command_name = next_argument;
     
     server_name[0] = '\0';  /* null it out */
     database[0] = '\0';  /* null it out */
     service[0] = '\0';  /* null it out */

     while(NULL != (next_argument = next_arg(&argc, &argv))) {
	  /* then we have an argument to process */
	  if(0 == strcmp("-debug", next_argument)){
	       logfile = stderr;
	  }
	  else if(0 == strcmp("-v", next_argument)){
	       printf("%s version: %s, %s\n",
		      command_name, VERSION, WAISSEARCH_DATE);
	  }
	  else if(0 == strcmp("-h", next_argument)) {
	       if (NULL == (next_argument = next_arg(&argc, &argv)))
		    printf("Syntax error, need a host!!\n"), exit(-1);
	       else 
		    WaisGateHost = next_argument;
	  }

	  else if(0== strcmp("-s", next_argument)) {
	       if (NULL == (next_argument = next_arg(&argc, &argv)))
		    printf("Syntax error, need a sourcefile!!\n"), exit(-1);
	       else {
		    FILE *Srcfile;
		    Source Moo;

		    Moo = (Source)malloc(sizeof(_Source));

		    Srcfile = fopen(next_argument, "r");
		    if (Srcfile == NULL) {
			 printf("File \"%s\" does not exist\r\n", next_argument), exit(-1);
		    }
		    
		    ReadSource(Moo, Srcfile);

/*		    printf("server: %s, database: %s, service: %s\n", Moo->server, Moo->database, Moo->service);*/

		    strcpy(server_name, Moo->server);
		    strcpy(database, Moo->database);
		    strcpy(service, Moo->service);
	       }
	  }

	  else if(0 == strcmp("-p", next_argument)) {
	       if (NULL == (next_argument = next_arg(&argc, &argv)))
		    printf("Syntax error, need a port number!!\n"), exit(-1);
	       else 
		    WaisGatePort = next_argument;
	  }

	  else if (0== strcmp("-i", next_argument)) {
	       Searching = 0;  /** Not searching, we're fetching by docid **/
	  }

	  else{
	       panic("Don't recognize the %s option", next_argument);
	  }
     }

     if (Searching == 1 && (WaisGateHost == NULL)) {
	  printf("4Must specify a gateway host and port!\r\n.\r\n"), exit(-1);
     }

     Inputline = (char *) malloc(1024);
     gets(Inputline);
	  
     ZapCRLF(Inputline);
     
     if (Inputline[0] == '-') {
	  Searching = 0;  /** We got ourselves a docid... **/
	  Inputline++;
     }
     
     /**
      ** Next load up the name of the source...
      */
     
     {
	  FILE *Srcfile;
	  Source Moo;
	  char *cp;

	  /*
           * A search request will have a tab separator.  A document
	   * request will have a : separator.
	   */

	  if (Searching) {
	       cp = strchr(Inputline, '\t');
	       keywords = cp + 1;
	  }
	  else {
	       cp =strchr(Inputline, ':');
	       DocIdString = cp +1;
	  }

	  if (cp == NULL) {
	       /** An error occured, probably old client software... **/
	       printf(".\r\n");
	  } else
	       *cp = '\0';
	  
	  SourceName= Inputline;

	  Moo = (Source)malloc(sizeof(_Source));
	  
	  Srcfile = fopen(SourceName, "r");
	  if (Srcfile == NULL) {
	       printf("File \"%s\" does not exist\r\n.\r\n", SourceName), exit(-1);
	  }
	  
	  ReadSource(Moo, Srcfile);
	  strcpy(server_name, Moo->server);
	  strcpy(database, Moo->database);
	  strcpy(service, Moo->service);
     }

     if (server_name[0] == 0)
	  connection = NULL;
     
     else if ((connection=connect_to_server(server_name,atoi(service))) == NULL) 
     {
	  fprintf (stderr, "Error openning connection to %s via service %s.\r\n.\r\n",
		   server_name, service);
	  exit(-1);
     }
     
     request_message = (char*)s_malloc((size_t)MAX_MESSAGE_LEN * sizeof(char));
     response_message = (char*)s_malloc((size_t)MAX_MESSAGE_LEN * sizeof(char));
     
     {
     char userInfo[500];
     char hostname[80];
     
     gethostname(hostname, 80);

     sprintf(userInfo, "waisgopher %s, gateway: %s, user: gopher@boombox.micro.umn.edu",
	     VERSION, hostname );

     
     init_connection(request_message, response_message,
		     MAX_MESSAGE_LEN,
		     connection,
		     userInfo);
      }

     request_buffer_length = MAX_MESSAGE_LEN; /* how of the request is left */

     /***********************************************************************
      **** Branch off here, if we have a search request do it.
           otherwise if we have a docid, fetch it.
      ****/

     if (Searching) {

	  if(NULL ==
	     generate_search_apdu(request_message + HEADER_LENGTH, 
				  &request_buffer_length, 
				  keywords, database, NULL, MAXDOCS))
	       panic("request too large");
	  
	  
	  if(0 ==
	     interpret_message(request_message, 
			       MAX_MESSAGE_LEN - request_buffer_length, 
			       response_message,
			       MAX_MESSAGE_LEN,
			       connection,
			       false	/* true verbose */
			       )) { /* perhaps the server shut down on us, let's see: */
	       if ( connection != NULL) {
		    fclose(connection);
		    if ((connection=connect_to_server(server_name,atoi(service))) == NULL) 
		    {
			 printf("Error openning connection to %s via service %s.\n",
				  server_name, service);
			 exit(-1);
		    }
		    if(0 ==
		       interpret_message(request_message, 
					 MAX_MESSAGE_LEN - request_buffer_length, 
					 response_message,
					 MAX_MESSAGE_LEN,
					 connection,
					 false /* true verbose */
					 ))
			 panic("really couldn't deliver message");
	       }
	       else
		    panic("returned message too large");
	  }
     
	  readSearchResponseAPDU(&query_response, response_message + HEADER_LENGTH);

	  display_search_response(query_response, server_name, service, database, WaisGateHost, WaisGatePort, SourceName);

	  freeWAISSearchResponse(query_response->DatabaseDiagnosticRecords);         
	  freeSearchResponseAPDU( query_response);

     }  /*** Searching? ***/


     else  {  /*** Retrieve by docid ***/

	  any *docid;
	  int DocLen;
	  
	  docid = Gopher_to_DocId(DocIdString, &DocLen);

	  /*** First let's transform the first word into a docid ***/

	  /*** What we need from net:  DocumentLength, Types: (TEXT!), docid, **/



/*	  printf("Headline: %s\n", 
		 query_info->DocHeaders[document_number - 1]->Headline);
	  /* we must retrieve the document in parts since it might be very long*/
	  for(count = 0; 
	      count * CHARS_PER_PAGE <
	      DocLen;
	      count++){

	       char *type;
/*	       if(query_info->DocHeaders[1]->Types == NULL)*/
		    type = s_strdup("TEXT");
/*	       else
		    type = s_strdup(query_info->DocHeaders[1]->Types[0]);*/
	       request_buffer_length = MAX_MESSAGE_LEN; /* how of the request is left */
	       if(0 ==
		  generate_retrieval_apdu(request_message + HEADER_LENGTH,
					  &request_buffer_length, 
					  docid, 
					  CT_byte,
					  count * CHARS_PER_PAGE,
					  MIN((count + 1) * CHARS_PER_PAGE,
					      DocLen),
					  type,
					  database
					  ))
		    panic("request too long");
	       
	       if(0 ==
		  interpret_message(request_message, 
				    MAX_MESSAGE_LEN - request_buffer_length, 
				    response_message,
				    MAX_MESSAGE_LEN,
				    connection,
				    false /* true verbose */	
				    )) { /* perhaps the server shut down on us, let's see: */
		    if ( connection != NULL) {
			 fclose(connection);
			 if ((connection=connect_to_server(server_name,atoi(service))) == NULL) 
			 {
			      fprintf (stderr, "Error openning connection to %s via service %s.\n",
				       server_name, service);
			      exit(-1);
			 }
			 if(0 ==
			    interpret_message(request_message, 
					      MAX_MESSAGE_LEN - request_buffer_length, 
					      response_message,
					      MAX_MESSAGE_LEN,
					      connection,
					      false /* true verbose */
					      ))
			      panic("really couldn't deliver message");
		    }
		    else
			 panic("returned message too large");
	       }
	       
	       readSearchResponseAPDU(&retrieval_response, 
				      response_message + HEADER_LENGTH);
	       
	       /* display_search_response(retrieval_response); the general thing */
	       if(NULL == ((WAISSearchResponse *)retrieval_response->DatabaseDiagnosticRecords)->Text){
		    display_search_response(retrieval_response);
		    panic("No text was returned");
	       }
	       Mydisplay_text_record_completely
		    (((WAISSearchResponse *)retrieval_response->DatabaseDiagnosticRecords)->Text[0], false);
	  }
	  freeWAISSearchResponse( retrieval_response->DatabaseDiagnosticRecords); 
	  freeSearchResponseAPDU( retrieval_response);
     }
     
     close_connection(connection);
     
     s_free(request_message);
     s_free(response_message);
  
     exit(0);
}

