/*
 * server.c,v 1.1 1994/01/28 17:05:36 franktor Exp
 *
 * This is an example of an interface between the high-level API
 * and a trip database server.
 */

#ifndef VMS
#include <string.h>
#include <malloc.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <errno.h>
#include <sr-general.h>
#include <sr-api.h>
#include <high/eapi.h>
#include <high/sr_structcodec.h>
#include <trip.h>
#else
#include <string.h>
#include <time.h>
#include <resource.h>
#include <types.h>
#include <errno.h>
#include <sr-general.h>
#include <sr-api.h>
#include <eapi.h>
#include <sr_structcodec.h>
#include <trip.h>
#endif

#ifndef __CEXTRACT__
#include "proto.h"
#endif

#ifndef MAX_BUF
#define MAX_BUF 256
#endif

#define MAX_SEND_AHEAD 10 /* Must be at least 1 */

selection *first_selection = NULL;

int tripServer;
int ttylog = 0;
int keeplog = 0;

char *tripserver = "kari.uio.no";
int tripport = 2001;
char *debug_logfile = "../logs/tripserv.log";

void
init(int argc, char **argv, char **env)
{
  facLogLevel (facAll, llevTrace);
  facLogLevel (facStruct, llevDebug); /* Don't want all the trace output */

  parse_args(argc, argv);

  if (!ttylog)
  {
    if (!keeplog)
      unlink(debug_logfile);
    initLogger (debug_logfile);
  }
}

void parse_args(int argc, char **argv)
{
  int i;
  for (i = 1; i < argc; i++)
  {
    if (!strncmp(argv[i], "-nofork", strlen(argv[i])))
    {
      set_no_fork(1); /* For debugging */
      continue;
    }
    if (!strncmp(argv[i], "-debug", strlen(argv[i])))
    {
      ttylog = 1;
      continue;
    }
    if (!strncmp(argv[i], "-keeplog", strlen(argv[i])))
    {
      keeplog = 1;
      continue;
    }
    if (!strncmp(argv[i], "-verbose", strlen(argv[i])))
    {
      facLogLevel (facStruct, llevTrace); /* Turn on all debug */
      continue;
    }
    if (!strncmp(argv[i], "-oldtrip", strlen(argv[i])))
    {
      trip_setProtocol(1);
      continue;
    }
    if (!strncmp(argv[i], "-server", strlen(argv[i])))
    {
      if (++i == argc)
      {
        fprintf(stderr, "You must specify a server after the -server option.\n");
        exit(1);
      }
      tripserver = argv[i];
      continue;
    }
    if (!strncmp(argv[i], "-port", strlen(argv[i])))
    {
      if (++i == argc)
      {
        fprintf(stderr, "You must specify a port number after the -port option.\n");
        exit(1);
      }
      sscanf(argv[i], "%d", &tripport);
      continue;
    }
    if (!strncmp(argv[i], "-logfile", strlen(argv[i])))
    {
      if (++i == argc)
      {
        fprintf(stderr, "You must specify a filename after the -logfile option.\n");
        exit(1);
      }
      debug_logfile = argv[i];
      continue;
    }
    if (strncmp(argv[i], "-help", strlen(argv[i])))
      fprintf(stderr, "Unknown argument: %s\n", argv[i]);
    fprintf(stderr, "Usage: %s [-server <tripserver>] [-port <tripport>] [-nofork] [-debug] [-verbose] [-keeplog] [-oldtrip] [-logfile <filename>]\n", argv[0]);
    fprintf(stderr, "  -server:   Specifies which tripserver to connect to.\n");
    fprintf(stderr, "             Default is \"%s\".\n", tripserver);
    fprintf(stderr, "  -port:     Specifies which port to connect to.\n");
    fprintf(stderr, "             Default is \"%d\".\n", tripport);
    fprintf(stderr, "  -nofork:   (debug) Don't fork() on new connection.\n");
    fprintf(stderr, "  -debug:    Writes debug information to terminal.\n");
    fprintf(stderr, "  -verbose:  Turns on more debugging.\n");
    fprintf(stderr, "  -keeplog:  Don't delete the old logfile on startup.\n");
    fprintf(stderr, "  -oldtrip:  Use an older protocol version to trip.\n");
    fprintf(stderr, "  -logfile:  Specify an alterlnate logfile.\n");
    fprintf(stderr, "             The default logfile is \"%s\".\n", debug_logfile);
    exit(1);
  }
}

/*
 * Handle Search Request (and generate Response)
 */

SearchResponse *
handleSearch(SearchRequest *pkt)
{
  char databases[MAX_BUF];
  DatabaseName *dbn;
  char *querytxt = NULL;
  selection *sel;
  SearchResponse *opkt = (SearchResponse *) malloc(sizeof(SearchResponse));

  databases[0]='\0';
  for(dbn = pkt->databaseNames; dbn; dbn=dbn->next)
  {
    if(*databases)
      strcat(databases,",");
    strcat(databases,dbn->database);
  }
  LOG(facHigh, llevTrace, "Opening trip server with databases \"%s\".",
      databases);
  if (trip_openDb (tripServer, databases) == False)
  {
    LOG(facHigh, llevExceptions, "Failed to open Database.");
    opkt->numberOfRecordsFound = 0;
    opkt->searchStatus = False;
    opkt->resultSetStatus = resultSetStatus_ignore;
    opkt->resultSetId = 0;
    return opkt;
  }
  switch(pkt->query->kind)
  {
  case queryPrivate:
    LOG(facHigh, llevExceptions, "queryPrivate is not supported.");
    querytxt = SR_strdup("avoid_crash");
    break;
  case queryRpnQuery:
    LOG(facHigh, llevExceptions, "queryRpnQuery is not supported.");
    querytxt = SR_strdup("avoid_crash");
    break;
  case queryIso8777Query:
    querytxt = (char *) malloc(pkt->query->q.iso8777Query->len + 1);
    (void) memcpy(querytxt, pkt->query->q.iso8777Query->value,
                  pkt->query->q.iso8777Query->len);
    *(querytxt + pkt->query->q.iso8777Query->len) = '\0';
    LOG(facHigh, llevTrace, "Querying server for \"%s\" (%d).",querytxt,
        pkt->query->q.iso8777Query->len);
    /* Log warning if '\0' in text */
    break;
  }
  LOG(facHigh, llevDebug, "Searching for %s.", querytxt);
  sel = trip_search(tripServer, querytxt);
  LOG(facHigh, llevDebug, "Found %d records.", sel->noRecords);
  sel->next = first_selection;
  first_selection = sel;
  opkt = (SearchResponse *) malloc(sizeof(SearchResponse));
  opkt->numberOfRecordsFound = sel->noRecords;
  opkt->searchStatus = True; /* It's true even if no matches were found */
  opkt->resultSetStatus = resultSetStatus_ignore;
  opkt->resultSetId = sel->setNo;

  free(querytxt);
  return opkt;
}

/*
 * Handle Present Request (and generate Response)
 */

PresentResponse *
handlePresent(PresentRequest *pkt)
{
  PresentResponse *opkt = (PresentResponse *) malloc(sizeof(PresentResponse));
  selection *sel;
  int i;
  int first_rec, last_rec;
  int sent_ahead;
  EapiNamePlusRecord *previous = NULL;
  
  LOG(facHigh, llevTrace, "Entered PresentResponse().");
  for(sel = first_selection; sel != (selection *) NULL; sel = sel->next)
    if(sel->setNo == pkt->resultSetId)
      break;
  if (sel == (selection *) NULL)
  {
    LOG(facHigh, llevExceptions, "Protocol error: no selection.");
    exit(1);
  }

  last_rec = pkt->resultSetStartPoint + pkt->numberOfRecordsRequested - 1;
  first_rec = pkt->resultSetStartPoint;
  opkt->presentStatus = True;
  opkt->numberOfRecordsReturned = last_rec - first_rec + 1;
  opkt->records = (EapiRecords *) malloc(sizeof(EapiRecords));
  opkt->records->recKind = recEapiNamePlusRecord;
  opkt->records->u.databaseOrSurDiagnostics = NULL;

  LOG(facHigh, llevTrace, "Sending records from %d to %d (%d returned).",
      first_rec, last_rec, last_rec - first_rec + 1);

  sent_ahead = 0;

#if 0
  /*
   * New experimental function to try to speed it up:
   */
  trip_askSeveralRecord (tripServer, sel->setNo, first_rec, last_rec);
  sent_ahead = last_rec + 1;
#endif

  for (i = first_rec; i <= last_rec; i++)
  {
    EapiNamePlusRecord *rec;
    TripRecord *tr;

    /*
     * First send a bunch of requests at a time, to speed up things.
     */
    if (sent_ahead <= i)
      for (sent_ahead = i; sent_ahead <= last_rec && sent_ahead < i + MAX_SEND_AHEAD; sent_ahead++)
      {
        LOG(facHigh, llevTrace, "Asking for record %d.", sent_ahead);
        trip_newaskRecord (tripServer, sel->setNo, sent_ahead);
      }
      
    /*
     * Fetch a record from trip.
     */
    tr = trip_newgetRecord(tripServer);

    rec = (EapiNamePlusRecord *) malloc(sizeof(EapiNamePlusRecord));
    /*
     * If we failed, build a diagnostic surrogate record.
     * If we succeeded, build a EapiNamePlusRecord structure.
     */
    if (tr->status == False)
    {
      EapiDiagRec *d = (EapiDiagRec *) malloc(sizeof(EapiDiagRec));
      LOG(facHigh, llevTrace, "Failed to get record %d.", i);
      d->diagnosticSetId = SR_strdup(SR_DIAGSET_BIB1); /* ???? strdup(OID_Oid2name(Oid_DIAGSET_BIB1) ???? */
      d->condition = 14;
      d->addinfo = (char *) NULL;

      LOG(facHigh, llevTrace, "Failed to get record %d.", i);

      rec->recKind = recEapiSurrogateDiagRec;
      rec->u.surrogateDiagnostic = d;
    }
    else
    {
      LOG(facHigh, llevTrace, "Got Record %d.", i);
      rec->recKind = recEapiRecord;
      rec->u.record.databaseName = SR_strdup(tr->database);
      /*
       * Since trip is unable to supply null-bytes within a record, this is
       * somewhat simplified (strcpy/strlen instead of memcpy, etc).
       */
      rec->u.record.databaseRecord.value = SR_strdup(tr->record);
      rec->u.record.databaseRecord.len =
       strlen(rec->u.record.databaseRecord.value);
      rec->u.record.recordSyntax = SR_strdup("4711.42"); /* Will fix. */
      rec->next = (EapiNamePlusRecord *) NULL;
    }

    free_TripRecord(tr); /* Don't need it anymore */

    /*
     * Now link this record with the previous record (or with the
     * Eapi packet if this is the first record).
     */
    if (previous == (EapiNamePlusRecord *) NULL)
      opkt->records->u.databaseOrSurDiagnostics = rec;
    else
      previous->next = rec;
    previous = rec;
  }
  return opkt;
}

/*
 * Handle Delete Result Set Request (and generate Response)
 */

DeleteResultSetResponse *
handleDeleteResultSet(DeleteResultSetRequest *pkt)
{
  DeleteResultSetResponse *opkt;
  Boolean status = True;
  if (pkt->all == True)
    delete_all_selections();
  else
  {
    selection *sel;
    sel = find_selection_number(pkt->resultSetId);
    if (sel == NULL || delete_selection(sel) == False)
        status = False; /* Error */
  }
  opkt = (DeleteResultSetResponse *) malloc(sizeof(DeleteResultSetResponse));
  opkt->status = status;
  return opkt;
}

/*
 * Handle Close Request (and generate Response)
 */

CloseResponse *
handleClose(CloseRequest *pkt)
{
  CloseResponse *opkt = (CloseResponse *) malloc(sizeof(CloseResponse));
  delete_all_selections();
  opkt->closeAccepted = True;
  opkt->message = (char *) NULL;

  trip_closeServer(tripServer);
  return opkt;
}

/*
 * Handle Open Request (and generate Response)
 */

OpenResponse *
handleOpen(OpenRequest *pkt)
{
  OpenResponse *opkt = (OpenResponse *) malloc(sizeof(OpenResponse));
  LOG(facHigh, llevTrace, "Attempting to connect to tripserver...");

  tripServer = trip_openServer(tripserver, tripport);
  if (tripServer == (-1))
  {
    LOG(facHigh, llevExceptions, "Failed to open connection to tripserver.");
    opkt->openAccepted = False;
    opkt->message = SR_strdup("Failed to communicate with trip database-server.");
  }
  LOG(facHigh, llevTrace, "Opened connection to tripserver");

  opkt->openAccepted = True;
  opkt->message = (char *) NULL;
  return opkt;
}

/*
 * Various utility functions to administer the linked list of selections.
 */

Boolean
delete_selection(selection *set)
{
  selection *tmp, *prev = NULL;
  for(tmp = first_selection; tmp != NULL; prev = tmp, tmp = tmp->next)
    if (tmp == set)
      break;
  
  if(tmp == NULL)
    return False;
  tmp = trip_freeSelection(tmp);
  if (prev)
    prev->next = tmp;
  else
    first_selection = tmp;
  return True;
}

selection *
find_selection_name(char *name)
{
  selection *tmp;
  for (tmp = first_selection; tmp != (selection *) NULL; tmp = tmp->next)
    if(!strcmp(tmp->name,name))
      break;
  return tmp;
}

selection *
find_selection_number(int number)
{
  selection *tmp;
  for (tmp = first_selection; tmp != (selection *) NULL; tmp = tmp->next)
    if(tmp->setNo == number)
      break;
  return tmp;
}

void
delete_all_selections()
{
  selection *sel = first_selection;
  while ((sel = trip_freeSelection(sel)) != NULL);
  first_selection = NULL;
}

