/*
 * server.c,v 1.1 1994/01/28 17:22:23 franktor Exp
 *
 * server.c
 *
 * SR Responder
 *
 * Geir Pedersen, Feb 1992
 *
 * Geir.Pedersen@usit.uio.no
 *
*/

#include <stdio.h>
#include <sr-api.h>
#include <sr-util.h>
#include <sr-general.h>
#include <sr-parser.h>
#include <sr-logger.h>
#include "trip.h"
#include <strings.h>

#define MAXCLIENTS		100
#define LARGEBUF		10000

typedef struct Client {
   int				ref;
   selection			*selections;
} Client;

char				*legalDatabases[] = { "SAMPER", "ISDS", (char *) NULL };

Client			   	Clients[MAXCLIENTS];
Client				*client; /* current client */

int				tripServer;


Boolean knownDatabase ( char *db )
{
   int				i;

   for ( i = 0; legalDatabases[i]; i++ )
      if ( strcasecmp ( db, legalDatabases[i] ) == 0 )
	 return True;

   return False;
}


Boolean AcceptAssoc ( int ref COMMA_PM_LLK_ARG(LowerLayerKind llk), presContext *pc )
{
   int				i;
   presContext			*p;

   if ( pc )
      {
	 LOG ( facApp, llevTrace, "Proposed Presentation Contexts:" );
	 for ( p = pc; p; p = p->next )
	    LOG ( facApp, llevTrace, "   %s", PC2str ( p ) );
      }

   /* find free client descriptor, and initialise it */
   for ( i = 0; i < MAXCLIENTS; i++ )
      if ( Clients[i].ref == -1 )
      {
	 Clients[i].ref = ref;
	 Clients[i].selections = (selection *) NULL;

	 LOG ( facApp, llevNotice, "Accepting assoc %d", ref );

	 return True;
      }

   LOG ( facApp, llevExceptions, "Failed to accept new client - no free slot found" );

   return False;
}

Client	*findClient ( int ref )
{
   int				i;

   for ( i = 0; i < MAXCLIENTS; i++ )
      if ( Clients[i].ref == ref )
	 return &Clients[i];

   LOG ( facApp, llevExceptions, "findClient() failed to find client for reference %d", ref );

   return (Client *) NULL;
}

void removeClient ( int ref )
{
   int				i;

   for ( i = 0; i < MAXCLIENTS; i++ )
      if ( Clients[i].ref == ref )
      {
	 /* free result sets */
	 selection		*sel;

	 for ( sel = Clients[i].selections; sel; sel = trip_freeSelection(sel) );

	 Clients[i].ref = -1;
      }
}

Boolean verifyResultSetID ( char *id )
{
   /* check that the proposed result set id is not already in use */
   selection			*sel;

   for ( sel = client->selections; sel && strcmp ( sel->name, id ); sel = sel->next );

   if ( sel )
      return False;

   return True;
}

void addResultSet ( selection *sel )
{
   sel->next = client->selections;
   client->selections = sel;
}

selection *getResultSet ( char *name )
{
   selection			*sel;

   for ( sel = client->selections; sel && strcmp ( sel->name, name ); sel = sel->next );

   return sel;
}

void removeResultSet ( selection *x )
{
   selection			*sel, *prev;

   for ( sel = client->selections, prev = (selection *) NULL; sel && x != sel; sel = sel->next );

   if ( x == sel )
   {
      if ( prev )
	 prev->next = sel->next;
      else
	 client->selections = sel->next;

      trip_freeSelection ( x );
   }
}

void removeAllResultSets ( void )
{
   selection			*sel, *next;

   for ( sel = client->selections; sel; sel = next )
   {
      next = sel->next;

      trip_freeSelection ( sel );
   }

   client->selections = (selection *) NULL;
}



/* INITIALIZE request */
Boolean handleInitialiseRequest ( int ref )
{
   SRInitialiseRequest 		*ireq;
   SRInitialiseResponse		irsp;
   Boolean			initialiseResponse	= True;

   LOG ( facApp, llevTrace, "oprInitialiseReq received" );

   if ( !(ireq = SR_ReadInitialiseRequest ( ref )) )
   {
      LOG ( facApp, llevExceptions, "Failed to read initialise request" );
      return False;
   }

   LOG ( facApp, llevTrace, "remote implementation %s", ireq->implementationId );
   LOG ( facApp, llevTrace, "  proposed protocol version=%d", ireq->protocolVersion );
   LOG ( facApp, llevTrace, "  proposed options=%d", ireq->options );

   /* check validity of protocol version */
   /* we only support version 1 */
   if ( !PROTO_V1 & ireq->protocolVersion )
      initialiseResponse = False;

   irsp.protocolVersion = PROTO_V1;
   irsp.options = OPT_SEARCH + OPT_PRESENT + OPT_DELSET;
   irsp.preferredMessageSize = ireq->preferredMessageSize;
   irsp.maximumMessageSize = ireq->maximumMessageSize;
   irsp.implementationId = strdup ( "Nordic SR-NET" );
   irsp.implementationName = strdup ( "SLAMBAM" );
   irsp.implementationVersion = strdup ( "0.1" );
   irsp.userInformationField = (EXTERN *) NULL;
   irsp.initializationStatus = initialiseResponse;

   if ( initialiseResponse )
      LOG ( facApp, llevTrace, "association accepted" );
   else
      LOG ( facApp, llevTrace, "association rejected" );

   if ( !SR_SendInitialiseResponse ( ref, &irsp ) )
   {
      LOG ( facApp, llevExceptions, "Failed to send initialise response" );
      return False;
   }

   return True;
}


/* SEARCH request */

Boolean handleSearchRequest ( int ref )
{
   SRSearchRequest		*sreq;
   SRSearchResponse		srsp;
   Boolean			saveResultSet;
   char				*resultSetName;
   selection			*sel;
   int				i;
   NamePlusRecord		*record;

   LOG ( facApp, llevTrace, "Search request received on %d", ref );

   if ( !(sreq = SR_ReadSearchRequest ( ref )) )
   {
      LOG ( facApp, llevExceptions, "Failed to read search request" );
      return False;
   }

   /* verify that requested database is legal */
   if ( sreq->noDatabaseIds != 1 || (!knownDatabase ( sreq->databaseId[0] )) )
   {
      LOG ( facApp, llevExceptions, "Client requested illegal database" );

      /* return diagnostic */

      srsp.numberOfRecordsFound = 0;
      srsp.numberOfRecordsReturned = 1;
      srsp.nextResultSetPosition = 0;
      srsp.searchStatus = False;
      srsp.resultSetStatus = resultSetStatus_none;
      srsp.presentStatus = presentStatus_ignore;
      srsp.records = (struct Records *) smalloc ( sizeof ( struct Records ) );
      srsp.records->recKind = recDiagRec;
      srsp.records->u.nonSurrogateDiagnostic = newDiagRec ( Oid_DIAGSET_BIB1, 23, NULLCP );

      goto Done;
   }

   /* verify that proposed result set name is ok */
   if ( sreq->proposedResultSetId )
   {
      saveResultSet = True;
      resultSetName = sreq->proposedResultSetId;
      LOG ( facApp, llevTrace, "   Proposed result set name: %s", resultSetName );
   }
   else
   {
      saveResultSet = False;
      resultSetName = strdup ( "" );
      LOG ( facApp, llevTrace, "   No result set name poposed, not saving result set" );
   }

   if ( saveResultSet && ((!verifyResultSetID ( resultSetName )) && !sreq->replaceIndicator) )
   {
      LOG ( facApp, llevTrace, "Result set name already in use - returning diagnostic" );

      /* return a diagnostic */
      srsp.numberOfRecordsFound = 0;
      srsp.numberOfRecordsReturned = 1;
      srsp.nextResultSetPosition = 0;
      srsp.searchStatus = False;
      srsp.resultSetStatus = resultSetStatus_none;
      srsp.presentStatus = presentStatus_ignore;
      srsp.records = (struct Records *) smalloc ( sizeof ( struct Records ) );
      srsp.records->recKind = recDiagRec;
      srsp.records->u.nonSurrogateDiagnostic = newDiagRec ( Oid_DIAGSET_BIB1, 21, NULLCP );

      goto Done;
   }

   if ( !verifyResultSetID ( resultSetName ) )
      /* remove previous result set with this name */
      removeResultSet ( getResultSet ( resultSetName ) );

   /* open the database */
   trip_openDb ( tripServer, sreq->databaseId[0] );

   sel = trip_search ( tripServer, os2str ( sreq->query->q.iso8777Query ) );
   sel->db = strdup ( sreq->databaseId[0] );
   sel->name = strdup ( resultSetName );
   if ( saveResultSet && sel->noRecords )
      addResultSet ( sel );

   srsp.numberOfRecordsFound = sel->noRecords;

   /* figure the number of records to return */
   if ( sel->noRecords <= sreq->smallSetUpperBound )
      srsp.numberOfRecordsReturned = sel->noRecords;
   else if ( sel->noRecords < sreq->largeSetLowerBound )
      srsp.numberOfRecordsReturned =
	 sreq->mediumSetPresentNumber < sel->noRecords
	    ? sreq->mediumSetPresentNumber : sel->noRecords;
   else
      srsp.numberOfRecordsReturned = 0;

   srsp.nextResultSetPosition = srsp.numberOfRecordsReturned + 1;
   srsp.searchStatus = True;
   srsp.resultSetStatus = resultSetStatus_subset;
   srsp.presentStatus = presentStatus_success;

   LOG ( facApp, llevTrace,
	"Retrieving records for searchresponse (%d records found, preparing to return %d records)",
	srsp.numberOfRecordsFound, srsp.numberOfRecordsReturned );

   if ( srsp.numberOfRecordsReturned )
   {
      srsp.records = (Records *) smalloc ( sizeof ( Records ) );
      srsp.records->recKind = recNamePlusRecord;
      for ( i = 1, srsp.records->u.databaseOrSurDiagnostics = record = (NamePlusRecord *) NULL;
	   i <= srsp.numberOfRecordsReturned;
	   i++ )
      {
	 record = (NamePlusRecord *) smalloc ( sizeof ( NamePlusRecord ) );
	 record->databaseName = strdup ( sreq->databaseId[0] );
	 record->nprKind = nprRecord;
	 record->u.databaseRecord = (EXTERN *) smalloc ( sizeof ( EXTERN ) );
	 record->u.databaseRecord->externEncoding = extEncOctetString;
	 record->u.databaseRecord->octStr = str2os ( trip_showRecord ( tripServer, sel->setNo, i ) );
	 record->next = srsp.records->u.databaseOrSurDiagnostics;
	 srsp.records->u.databaseOrSurDiagnostics = record;
      }
   }
   else
      srsp.records = (Records *) NULL;

   if ( !saveResultSet )
      (void) trip_freeSelection ( sel );

 Done:
   LOG ( facApp, llevTrace, "Returning search response" );

   if ( !SR_SendSearchResponse ( ref, &srsp ) )
   {
      LOG ( facApp, llevExceptions, "Failed to send search response" );
      return False;
   }

   return True;
}


/* PRESENT request */

Boolean handlePresentRequest ( int ref )
{
   SRPresentRequest		*preq;
   SRPresentResponse		prsp;
   selection			*sel;

   LOG ( facApp, llevTrace, "Present request received on %d", ref );

   if ( !(preq = SR_ReadPresentRequest ( ref )) )
   {
      LOG ( facApp, llevExceptions, "Failed to read present request on %d", ref );
      return False;
   }

   if ( !(sel = getResultSet ( preq->resultSetId )) )
   {
      /* unknown result set */
      prsp.numberOfRecordsReturned = 1;
      prsp.nextResultSetPosition = 0;
      prsp.presentStatus = presentStatus_failure;
      prsp.records = (struct Records *) smalloc ( sizeof ( struct Records ) );
      prsp.records->recKind = recDiagRec;
      prsp.records->u.nonSurrogateDiagnostic = newDiagRec ( Oid_DIAGSET_BIB1, 30, NULLCP );
   }
   else if ( sel->noRecords - preq->resultSetStartPoint + 1 < preq->numberOfRecordsRequested )
   {
      /* not enough records - report error */
      prsp.numberOfRecordsReturned = 1;
      prsp.nextResultSetPosition = 0;
      prsp.presentStatus = presentStatus_failure;
      prsp.records = (struct Records *) smalloc ( sizeof ( struct Records ) );
      prsp.records->recKind = recDiagRec;
      prsp.records->u.nonSurrogateDiagnostic = newDiagRec ( Oid_DIAGSET_BIB1, 13, NULLCP );
   }
   else
   {
      prsp.numberOfRecordsReturned = preq->numberOfRecordsRequested;
      prsp.nextResultSetPosition = preq->resultSetStartPoint + prsp.numberOfRecordsReturned + 1;
      prsp.presentStatus = presentStatus_success;

      LOG ( facApp, llevTrace,
	   "Retrieving records for presentresponse (preparing to return %d records)",
	   prsp.numberOfRecordsReturned );

      if ( prsp.numberOfRecordsReturned )
      {
	 int				i;
	 NamePlusRecord			*record;

	 prsp.records = (Records *) smalloc ( sizeof ( Records ) );
	 prsp.records->recKind = recNamePlusRecord;
	 for ( i = preq->resultSetStartPoint,
	       prsp.records->u.databaseOrSurDiagnostics = record = (NamePlusRecord *) NULL;
	       i < prsp.numberOfRecordsReturned + preq->resultSetStartPoint;
	       i++ )
	 {
	    char			*cp;

	    record = (NamePlusRecord *) smalloc ( sizeof ( NamePlusRecord ) );
	    record->databaseName = strdup ( sel->db );
	    record->nprKind = nprRecord;
	    record->u.databaseRecord = (EXTERN *) smalloc ( sizeof ( EXTERN ) );
	    record->u.databaseRecord->externEncoding = extEncOctetString;
	    record->u.databaseRecord->octStr = str2os ( trip_showRecord ( tripServer, sel->setNo, i ) );
	    if ( (cp = index ( record->u.databaseRecord->octStr->value, '\n' )) )
	       record->u.databaseRecord->octStr->value = cp+1;
	    record->next = prsp.records->u.databaseOrSurDiagnostics;
	    prsp.records->u.databaseOrSurDiagnostics = record;
	 }
      }
      else
	 prsp.records = (Records *) NULL;
   }

   LOG ( facApp, llevTrace, "Returning present response" );

   if ( !SR_SendPresentResponse ( ref, &prsp ) )
   {
      LOG ( facApp, llevExceptions, "Failed to send present response" );
      return False;
   }

   return True;
}


/* DELETE RESULT SET request */

Boolean handleDelResultSetRequest ( int ref )
{
   SRDeleteResultSetRequest	*dreq;
   SRDeleteResultSetResponse 	drsp;

   bzero ( (char *) &drsp, sizeof ( SRDeleteResultSetResponse ) );
   drsp.numberNotDeleted = -1;

   LOG ( facApp, llevTrace, "Delete Result Set Request received on %d", ref );

   if ( !(dreq = SR_ReadDeleteResultSetRequest ( ref )) )
   {
      LOG ( facApp, llevExceptions, "Failed to read delete result set request on %d", ref );
      return False;
   }

   switch ( dreq->deleteSetFunction )
   {
      ResultSetList		*rs;
      ListStatuses		*ls, *lls;
      Boolean			somethingDeleted;

    case deleteSetFunction_list:
      for ( rs = dreq->resultSetList, ls = lls = (ListStatuses *) NULL, somethingDeleted = False;
	    rs;
	    rs = rs->next )
      {
	 if ( !ls )
	    ls = lls = (ListStatuses *) smalloc ( sizeof ( ListStatuses ) );
	 else
	 {
	    lls->next = (ListStatuses *) smalloc ( sizeof ( ListStatuses ) );
	    lls = lls->next;
	 }

	 lls->resultSetId = safe_strdup ( rs->resultSetId );

	 if ( verifyResultSetID ( rs->resultSetId ) )
	    /* result set does not exist */
	    lls->deleteSetStatus = deleteSetStatus_resultSetDidNotExist;
	 else
	 {
	    removeResultSet ( getResultSet ( rs->resultSetId ) );
	    lls->deleteSetStatus = deleteSetStatus_success;
	    somethingDeleted = True;
	 }
      }

      /* fill in response */
      drsp.deleteOperationStatus =
	 somethingDeleted ? deleteSetStatus_success : deleteSetStatus_resultSetDidNotExist;
      drsp.deleteListStatuses = ls;
      break;

    case deleteSetFunction_all:
      /* this never fails, and therefore is pretty straight forward */
      removeAllResultSets ();
      drsp.deleteOperationStatus = deleteSetStatus_success;
      break;
   }

   /* send the response off */
   LOG ( facApp, llevTrace, "Returning delete result set response" );

   if ( !SR_SendDeleteResultSetResponse ( ref, &drsp ) )
   {
      LOG ( facApp, llevExceptions, "Failed to send delete result set response" );
      return False;
   }

   return True;
}



/* CLOSE request */

Boolean handleCloseRequest ( int ref )
{
   SRCloseResponse		closeResponse;

   (void) SR_ReadCloseRequest ( ref );

   LOG ( facApp, llevTrace, "CloseRequest received on %d", ref );

   SR_SendCloseResponse ( ref, &closeResponse );

   removeClient ( ref );

   return True;
}


int main ()
{
   int				ref;
   int				i;
   Boolean			oprResult = False;
   char				buf[LARGEBUF], hostname[40], *cp;
   SR_Address			*where;

   initLogger ( "./server.log" );

   (void) OSI_Initialise ( llevAll, stderr ); /* Moved up from under malloc_debug() */

   /* initialise array with client descriptors */
   for ( i = 0; i < MAXCLIENTS; i++ )
      Clients[i].ref = -1;

/*   if ( !loadAttributeSet ( "../bib-1/attrset.bib-1" ) )
   {
      LOG ( facApp, llevExceptions, "Failed to parse attribute set bib-1" );
      exit ( 1 );
   }
*/
   if ( !loadDiagnosticSet ( "../bib-1/diagset.bib-1" ) )
   {
      LOG ( facApp, llevExceptions, "Failed to parse diagnostic set bib-1" );
      exit ( 1 );
   }

#ifdef DEBUG
   malloc_debug ( 2 );
#endif

   hostname[0] = hostname[sizeof(hostname)-1] = '\0';
   gethostname ( hostname, sizeof (hostname) - 1 );
   if ( (cp = strchr ( hostname, '.' )) != NULL )
      *cp = '\0';
   sprintf (buf, DEFAULT_SERVER_DN_FMT, hostname );
   if ( !str2sr_address ( buf, &where, &cp ) )
   {
      LOG ( facApp, llevExceptions, "Failed to resolve address %s", cp );
      exit ( 1 );
   }
   if ( !SR_CreateListener ( where, AcceptAssoc ) )
   {
      LOG ( facApp, llevExceptions, "Failed to create listener on %s", hostname );
      exit ( 1 );
   }

   LOG ( facApp, llevTrace, "Connecting to database server" );
   tripServer = trip_openServer ( "kari.uio.no", 2001 );
/*
   LOG ( facApp, llevTrace, "Opening databases" );

   sprintf ( buf, "" );
   for ( i = 0; legalDatabases[i]; i++ )
   {
      if ( i )
	 strcat ( buf, "," );
      strcat ( buf, legalDatabases[i] );
   }
   trip_openDb ( tripServer, buf );
*/
   LOG ( facApp, llevTrace, "Ready to accept clients" );

   while ( 1 )
   {
      SROperation			opr;

      ref = -1;
      opr = SR_PollNetwork ( 0, 0, 0, 0, 0, &ref );
      if ( !(client = findClient ( ref )) )
      {
	 LOG ( facApp, llevExceptions, "Failed to find client for operation %s, ref=%d",
	       sro2str ( opr ), ref );
	 continue;
      }

      switch ( opr )
      {
       case oprInitialiseRequest:
	 oprResult = handleInitialiseRequest ( ref );
	 break;

       case oprSearchRequest:
	 oprResult = handleSearchRequest ( ref );
	 break;

       case oprPresentRequest:
	 oprResult = handlePresentRequest ( ref );
	 break;

       case oprCloseRequest:
	 oprResult = handleCloseRequest ( ref );
	 break;

       case oprDeleteResultSetRequest:
	 oprResult = handleDelResultSetRequest ( ref );
	 break;

       case oprAbort:
	 LOG ( facApp, llevExceptions, "Abort received on %d", ref );
	 SR_AbortAssociation ( ref );
	 removeClient ( ref );
	 break;

       default:
	 LOG ( facApp, llevExceptions, "Unknown indication received: %d, aborting connection %d", opr, ref );
	 SR_AbortAssociation ( ref );
	 removeClient ( ref );
	 break;
      }

      if ( !oprResult )
      {
	 LOG ( facApp, llevExceptions, "Processing of operation failed, aborting %d", ref );
	 SR_AbortAssociation ( ref );
	 removeClient ( ref );
      }
   }
}
