/*
 * server.c,v 1.1 1994/01/28 17:05:25 franktor Exp
 *
 * This is an experimental interface/layer between the SR-nett API
 * and a more simple interface (called? :1,$s/Eapi/new_name/g)
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sr-general.h>
#include <sr-api.h>
#include <high/eapi.h>
#include "server.h"
#include <malloc.h>

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

Boolean AcceptAssoc (int ref COMMA_PM_LLK_ARG(LowerLayerKind llk), presContext *pc)
{
  Client *client = add_client(ref);
  LOG(facHigh, llevTrace, "AcceptAssoc: new client %d (ref %d).",ref, client->ref);

  if (pc)
  {
    presContext *p;
    LOG(facHigh, llevTrace, "Proposed Presentation Contexts:" );
    for (p = pc; p; p = p->next)
      LOG (facHigh, llevTrace, "   %s", PC2str (p));
  }
  return True;
}

/*
 * INITIALISE REQUEST
 *
 * How to connect to the databaseserver is implementation specific.
 * It is assumed that all connections to the database server is already
 * operational, and that all configuration files have been read.
 */

void
handleInitialiseRequest (int ref)
{
  int common_protocol;
  SRInitialiseRequest *query;
  SRInitialiseResponse *response;
  Client *client;

  client = find_client(ref);

  query = SR_ReadInitialiseRequest(ref);
  if (query == NULL)
  {
    LOG(facHigh, llevExceptions, "Failed to decode initialize request, aborting assoc. %d.", ref);
    SR_AbortAssociation(ref);
    if (client)
      remove_client(client);
    return;
  }

  if (!client)
  {
    LOG(facHigh, llevExceptions,"Got InitialiseRequest from unknown client: %d.",ref);
    return;
  }

  if (client->state != clientWaiting)
  {
    LOG(facHigh, llevExceptions, "Got InitialiseRequest for busy client.");
    SR_AbortAssociation(ref);
    remove_client(client); /* Obviously something went wrong, clean up. */
    return;
  }
  response = (SRInitialiseResponse *) malloc(sizeof(SRInitialiseResponse));

/* Initializing the response first in case a diagnostic have to be sent */
  response->protocolVersion = 0;
  response->preferredMessageSize = query->preferredMessageSize;
  response->maximumMessageSize = query->maximumMessageSize;
  response->options = query->options & SUPPORTED_OPTIONS;
  response->implementationId = strdup(IMPLEMENTATION_ID);
  response->implementationName = strdup(IMPLEMENTATION_NAME);
  response->implementationVersion = strdup(IMPLEMENTATION_VERSION);
  response->userInformationField = NULL;

  common_protocol = query->protocolVersion & PROTOCOL_VERSIONS;
  if (!common_protocol) {
    LOG(facHigh, llevExceptions, "Did not find common protocol in %x & %x",
        query->protocolVersion,PROTOCOL_VERSIONS);
    response->initializationStatus = False;
  }
  else
  {
    int i;
    for (i = 32; i > 0; i--)
      if (common_protocol & (2<<(i-2)))
      {
        response->protocolVersion = common_protocol & (2<<(i-2));
        LOG(facHigh, llevDebug, "handleInitialiseRequest: SR/Z39.50 protocol version %x selected", i);
        break;
      }
    response->initializationStatus = True;
  }

/*
 * Now copy these values to the client, for future use, before
 * sending the initialise response.
 */
  client->protocolVersion = response->protocolVersion;
  client->preferredMessageSize = query->preferredMessageSize;
  client->maximumMessageSize = query->maximumMessageSize;
  client->account = NULL;
  client->user = NULL;
  client->password = NULL;
  if (query->authentication) {
    if (query->authentication->user)
      client->user = strdup(query->authentication->user->value);
    if (query->authentication->password)
      client->password = strdup(query->authentication->password->value);
    if (query->authentication->account)
      client->account = strdup(query->authentication->account->value);
  }
  if (query->implementationId)
    client->implementationId = strdup(query->implementationId);
  else
    client->implementationId = NULL;
  if (query->implementationName)
    client->implementationName = strdup(query->implementationName);
  else
    client->implementationName = NULL;
  if (query->implementationVersion)
    client->implementationVersion = strdup(query->implementationVersion);
  else
    client->implementationVersion = NULL;

  if (SR_SendInitialiseResponse(client->ref,response) == False)
  {
    LOG(facHigh, llevExceptions, "Failed to send initialise response to %d.",client->ref);
    /* Probably should abort connection here */
  }
}

/*
 * CLOSE REQUEST
 *
 * The state variable goes to "clientCloseRequest" until a close-response has
 * been returned from all open connections to the different servers which
 * have been opened by the client.
 * Note: if I don't answer the close-request, it isn't removed from the queue.
 * Is this a bug?
 */

void handleCloseRequest (int ref)
{
  /* SRCloseRequest *close_request; */
  Client *client;
  /* ClientDatabaseServer *server; */

  LOG(facHigh, llevTrace, "handleCloseRequest(%d)", ref);
  SR_ReadCloseRequest(ref);

  client = find_client(ref);
  if (!client)
  {
    LOG(facHigh, llevExceptions, "Got CloseRequest from unknown client: %d.",ref);
    return;
  }
  if (client->state != clientWaiting)
  {
    LOG(facHigh, llevExceptions, "Got CloseRequest for busy client.");
    /* TODO: Abort connection. */
    return;
  }
  client->state = clientCloseRequest;

/*
 * close_servers() will send any needed close-requests to the servers
 * It will also free the structures which for which it does not generate
 * any close-requests.
 */

  close_servers(client);

/*
 * First check if there are any open connection.
 * If not, send the close response and remove the client at once.
 */
  if (!FirstClientDatabaseServer(client, clientServerClosing))
  {
    send_close_response(client);
    remove_client(client);
    return;
  }
}

void
send_close_response (Client *client)
{
  SRCloseResponse *close_response;
  close_response = (SRCloseResponse *) malloc(sizeof(SRCloseResponse));

  close_response->closeAccepted = True;
  if (SR_SendCloseResponse(client->ref, close_response) == False)
    LOG(facHigh, llevExceptions, "Failed to close connection to client %d.",client->ref);
  else
    LOG(facHigh, llevTrace, "Sent close response to client %d.", client->ref);
}

/*
 * SEARCH REQUEST
 *
 * The state variable goes to "clientSearchRequest", then we wait for the
 * server to respond to a search request.  Then, if it reported that it had
 * any packets, we wait for the server to respond to a present request.
 * Then, after sending the search response back to the client, state goes back
 * to "clientWaiting".
 */

void handleSearchRequest (int ref)
{
  SRSearchRequest *sreq;
  EapiPacket *pkt;
  DatabaseServer *dbs, *dbs_prev=NULL;
  DatabaseName *dbn, *dbn_prev;
  int i;
  Client *client;
  ClientDatabaseServer *server;
  char buf[BUF_SIZE];

  buf[0] = '\0';

  sreq = SR_ReadSearchRequest(ref);

  client = find_client(ref);
  if (!client)
  {
    LOG(facHigh, llevExceptions, "Got SearchRequest from unknown client: %d.",ref);
    return;
  }
  if (client->state != clientWaiting)
  {
    LOG(facHigh, llevExceptions, "Got SearchRequest for busy client.");
    /* TODO: Abort connection. */
    return;
  }
  client->state = clientSearchRequest;

  for(i = 0; i < sreq->noDatabaseIds; i++)
  {
    if (i)
      strcat(buf, ", ");
    strcat(buf, sreq->databaseId[i]);
    for(dbs = first_database_server; dbs; dbs=dbs->next)
      for(dbn = dbs->databases; dbn; dbn = dbn->next)
        if(!strcmp(dbn->database, sreq->databaseId[i]))
        {
          if(dbs_prev == NULL || dbs_prev == dbs)
            dbs_prev = dbs;
          else
          {
            LOG(facHigh, llevExceptions, "Requested databases are not on same server.");
            sendSearchResponseDiagnostic(client,DIAG_BIB_1_COMBINATION_OF_DATABASES,NULL);
            client->state = clientWaiting;
            return;
          }
        }
  }
  if(dbs_prev == NULL)
  {
    LOG(facHigh, llevExceptions, "Failed to find correct database server.");
    sendSearchResponseDiagnostic(client,DIAG_BIB_1_UNSUPPORTED_SEARCH,NULL);
    client->state = clientWaiting;
    return;
  }
  if(client->pending_request) {
    LOG(facHigh, llevExceptions, "Got new search request while waiting for server.");
    sendSearchResponseDiagnostic(client,DIAG_BIB_1_UNSUPPORTED_SEARCH,NULL);
    client->state = clientWaiting;
    return;
  }

  LOG(facHigh, llevTrace, "handleSearchRequest: Using %s for database(s) %s", dbs_prev->name,  buf);

  if (OID_contents (client->preferredRecordSyntax))
    OID_free(client->preferredRecordSyntax);
  client->preferredRecordSyntax = OID_dup(sreq->preferredRecordSyntax);
  LOG(facHigh, llevDebug, "Requested RecordSyntax (SearchRequest): %s",
      OID_Oid2name(client->preferredRecordSyntax));

  if (sreq->proposedResultSetId)
  {
    ClientResultSetList *resultset;
    LOG(facHigh, llevDebug, "Proposed result set id: %s.",sreq->proposedResultSetId);
    resultset = find_ClientResultSet(client, sreq->proposedResultSetId);
    if (resultset)
    {
      if (sreq->replaceIndicator == False)
      {
        LOG(facHigh, llevExceptions, "Tried to use the same resultsetId without setting replaceIndicator.");
        sendSearchResponseDiagnostic(client,
            DIAG_BIB_1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF, NULL);
        client->state = clientWaiting;
        return;
      }
      LOG(facHigh, llevDebug, "Found previous equal resultset, deleting it.");
      remove_ClientResultSet(client, resultset);
      /*
       * TODO: Maybe I should generate a DeleteResultSetRequest and send
       * them on to the server, and wait for its response before I proceed with
       * the work on this search request?
       */
    }
  }
  else
  {
    LOG(facHigh, llevExceptions, "No proposed result set id.");
    /*
     * Should probably do something here, generate a temporary name.
     */
  }
  dbn = (DatabaseName *) NULL;
  for(i = 0, dbn_prev = NULL; i < sreq->noDatabaseIds; i++) {
    DatabaseName *tmp = (DatabaseName *) malloc(sizeof(DatabaseName));
    tmp -> next = NULL;
    tmp -> database = strdup(sreq->databaseId[i]);
    if(dbn_prev == NULL)
      dbn = tmp;
    else
      dbn_prev -> next = tmp;
    dbn_prev = tmp;
  }

  server = connectClientToDatabaseServer(client, dbs_prev);
  if (server == (ClientDatabaseServer *) NULL)
  {
    LOG(facHigh, llevExceptions, "handleSearchRequest: Failed to connect to server.");
    sendSearchResponseDiagnostic(client,DIAG_BIB_1_TEMPORARY_SYSTEM_ERROR,NULL);
    client->state = clientWaiting;
    return;
  }

  /*
   * Add a pointer to the _current_ server we are using.  This pointer
   * will be used when checking for possible post conversions after
   * we receive a packet back from the database server.
   */
  client->current_server = server;

  /*
   * Now we can check the formats, since current_server has been set.
   */
  CheckConversion(client);

  /*
   * Add a pointer from the client to the, so far, unanswered request:
   */
  client->pending_request = sreq;

  pkt = (EapiPacket *) malloc(sizeof(EapiPacket));
  pkt->type = eapiSearchRequest;
  pkt->u.search_request = (SearchRequest *) malloc(sizeof(SearchRequest));
  pkt->u.search_request->databaseNames = dbn;
  pkt->u.search_request->query = ConvertToEapiQuery(sreq->query);

  if (server->connection->state != clientServerReady)
  {
    /*
     * The service is not available yet, store the search-request.
     * It will be sent when we receive a OpenResponse from the connection.
     */
    server->pending_packet = pkt;
    return;
  }

  /*
   * The connection is ready, so send the packet at once
   */
  if (SendDBPacket(pkt, client, server->connection) == False)
  {
    LOG(facHigh, llevExceptions, "Failed to send SearchRequest to %d\n", pkt->ref);
    sendSearchResponseDiagnostic(client,DIAG_BIB_1_TEMPORARY_SYSTEM_ERROR,NULL);
    client->state = clientWaiting;
  }
  free_EapiPacket(pkt);
}

void
sendSearchResponseDiagnostic(Client *client,int condition, char *addinfo)
{
  DiagRec *diag = (DiagRec *) malloc(sizeof(DiagRec));
  SRSearchResponse *sr = (SRSearchResponse *) malloc(sizeof(SRSearchResponse));
  Records *r = (Records *) malloc(sizeof(Records));

  diag->diagnosticSetId = OID_dup(Oid_DIAGSET_BIB1);
  diag->condition = condition;
  if(addinfo)
    diag->addinfo = strdup(addinfo);
  else
    diag->addinfo = strdup("");
  r->recKind = recDiagRec;
  r->u.nonSurrogateDiagnostic = diag;
  sr->records = r;
  sr->numberOfRecordsFound = 0;
  sr->numberOfRecordsReturned = 1;
  sr->nextResultSetPosition = 0;
  sr->searchStatus = False;
  sr->presentStatus = presentStatus_ignore;
  sr->resultSetStatus = resultSetStatus_ignore;

  if (SR_SendSearchResponse(client->ref, sr) == False)
  {
    LOG(facHigh, llevExceptions, "Failed to send SearchResponse diagnostic to %d.",client->ref);
    sr->numberOfRecordsReturned = 0;
    sr->records = (Records *) NULL;
    if (SR_SendSearchResponse(client->ref, sr) == False)
    {
      LOG(facHigh, llevExceptions, "Failed repeatedly to send SearchResponse.");
      /* Probably should abort here */
    }
  }
}

/*
 * PRESENT REQUEST
 */

void
handlePresentRequest (int ref)
{
  Client *client;
  SRPresentRequest *present_request;
  ClientResultSetList *result;
  EapiPacket *pkt;
  ClientDatabaseServer *server;

  present_request = SR_ReadPresentRequest(ref);

  client = find_client(ref);
  if (!client)
  {
    LOG(facHigh, llevExceptions, "Got PresentRequest from unknown client: %d.",ref);
    return;
  }
  if (client->state != clientWaiting)
  {
    LOG(facHigh, llevExceptions, "Got PresentRequest for busy client.");
    /* TODO: Abort connection */
    return;
  }

  client->state = clientPresentRequest;

  if (OID_contents (client->preferredRecordSyntax))
    OID_free(client->preferredRecordSyntax); /* ???? Was oid_free */
  client->preferredRecordSyntax =
    OID_dup(present_request->preferredRecordSyntax);
  LOG(facHigh, llevDebug, "Requested RecordSyntax (PresentRequest): %s",
      OID_Oid2name(client->preferredRecordSyntax));

  if (!(result = find_ClientResultSet(client, present_request->resultSetId)))
  {
    sendPresentResponseDiagnostic(client, DIAG_BIB_1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST, NULL);
    client->state = clientWaiting;
    return;
  }

  /*
   * Store, in an easily accessible place, the number of records in the
   * resultset, since it is needed when calculating nextResultSetPosition.
   */
  client->numberOfRecords = result->numberOfRecords;

  if ((server = result->server) == NULL)
  {
    LOG(facHigh, llevExceptions, "No connection open when receiving present request.");
    sendPresentResponseDiagnostic(client, DIAG_BIB_1_TEMPORARY_SYSTEM_ERROR, NULL);
    client->state = clientWaiting;
    return;
  }

  if (present_request->resultSetStartPoint < 1 ||
      present_request->resultSetStartPoint + present_request->numberOfRecordsRequested > result->numberOfRecords + 1)
  {
    LOG(facHigh, llevExceptions, "Requested record not between 1 and %d.",result->numberOfRecords);
    sendPresentResponseDiagnostic(client, DIAG_BIB_1_PRESENT_REQUEST_OUT_OF_RANGE, NULL);
    client->state = clientWaiting;
    return;
  }

  /*
   * A pointer to the current server is needed later when checking for
   * possible post conversions.
   */
  client->current_server = server;

  /*
   * Now we can check for conversions, since current_server has been set.
   */
  CheckConversion(client);

  /*
   * Need to store the start point in order to calculate the
   * nextResultSetPosition later when sending back PresentResponse.
   */
  client->resultSetStartPoint = present_request->resultSetStartPoint;

  pkt = (EapiPacket *) malloc(sizeof(EapiPacket));
  pkt->type = eapiPresentRequest;

  pkt->u.present_request = (PresentRequest *) malloc(sizeof(PresentRequest));
  pkt->u.present_request->resultSetId = result->resultSetNumber;
  pkt->u.present_request->resultSetStartPoint =
    present_request->resultSetStartPoint;
  pkt->u.present_request->numberOfRecordsRequested =
    present_request->numberOfRecordsRequested;
  pkt->u.present_request->recordComposition =
    present_request->recordComposition;
  pkt->u.present_request->preferredRecordSyntax =
    OID_strdup (OID_Oid2name (present_request->preferredRecordSyntax));
  if (SendDBPacket(pkt, client, server->connection) == False)
    LOG(facHigh, llevExceptions, "Failed to send PresentRequest to %d.", ref);
  free_EapiPacket(pkt);
}

void
sendPresentResponseDiagnostic(Client *client, int condition, char *addinfo)
{
  DiagRec *diag = (DiagRec *) malloc(sizeof(DiagRec));
  SRPresentResponse *pr = (SRPresentResponse *) malloc(sizeof(SRPresentResponse));
  Records *records = (Records *) malloc(sizeof(Records));

  LOG(facHigh, llevDebug, "Sending diagnostic %d to client %d.",condition,client->ref);

  diag->diagnosticSetId = OID_dup(Oid_DIAGSET_BIB1);
  diag->condition = condition;
  if (addinfo)
    diag->addinfo = strdup(addinfo);
  else
    diag->addinfo = strdup("");

  records->recKind = recDiagRec;
  records->u.nonSurrogateDiagnostic = diag;

  pr->records = records;
  pr->numberOfRecordsReturned = 1;
  pr->nextResultSetPosition = 0;
  pr->presentStatus = presentStatus_failure;

  if(SR_SendPresentResponse(client->ref, pr) == False)
  {
    LOG(facHigh, llevExceptions, "Failed to send present diagnostic.");
    pr->records = NULL;
    pr->numberOfRecordsReturned = 0;
    if(SR_SendPresentResponse(client->ref, pr) == False)
    {
      LOG(facHigh, llevExceptions, "Failed to send empty present response.");
      /* Probably should abort the connection here */
    }
  }
}

/*
 * DELETE RESULT SET REQUEST
 */

void handleDeleteResultSetRequest (int ref)
{
  SRDeleteResultSetRequest *del_res_req;
  ResultSetList *rsl;
  Client *client;

  LOG(facHigh, llevTrace, "handleDeleteResultSetRequest(%d)",ref);

  client = find_client(ref);
  if (!client)
  {
    LOG(facHigh, llevExceptions, "Got DeleteResultSetRequest from unknown client: %d.",ref);
    return;
  }
  if (client->state != clientWaiting)
  {
    LOG(facHigh, llevExceptions, "Got DeleteResultSetRequest for busy client.");
    /* TODO: Abort connection. */
    return;
  }

  del_res_req = SR_ReadDeleteResultSetRequest(client->ref);
  if (del_res_req == NULL) {
    LOG(facHigh, llevExceptions, "Failed to read DeleteResultSetRequest.");
    /* TODO: Abort connection. */
    return;
  }
  /*
   * If we have no resultsets, the request has to be wrong, so there is
   * no need to look closer at it.
   */
  if (client->resultsets == NULL)
  {
    SRDeleteResultSetResponse *res = (SRDeleteResultSetResponse *)
      malloc(sizeof(SRDeleteResultSetResponse));
    res->deleteOperationStatus = deleteSetStatus_resultSetDidNotExist;
    res->deleteListStatuses = (ListStatuses *) NULL;
    res->numberNotDeleted = 0;
    res->bulkStatuses = (ListStatuses *) NULL;
    res->deleteMessage = (char *) NULL;
    if(SR_SendDeleteResultSetResponse(client->ref, res) == False)
    {
      LOG(facHigh, llevExceptions, "Failed to send DeleteResultSetResponse to %d.",client->ref);
      /* Probably should abort connection here */
    }
    client->state = clientWaiting;
    return;
  }

  client->state = clientDeleteResultSetRequest;
  client->pending_delete_requests = NULL;
  client->finished_delete_requests = NULL;

  if (del_res_req->deleteSetFunction == deleteSetFunction_all) {
    ClientResultSetList *resultset;

    /*
     * First check if the client is using any shared connections.
     * If that is the case, we cannot send any request to the database-server
     * requesting that "all" is to be deleted.
     * Instead we must send a request for one resultset at a time.
     */
    for (resultset = client->resultsets; resultset; resultset = resultset->next)
      if (resultset->server->connection->database_server->handle_several_clients
          == True)
        break;
    if (resultset != (ClientResultSetList *) NULL)
    {
      LOG(facHigh, llevDebug, "Deleting all resultsets, one by one, for client %d.", ref);
      for (resultset = client->resultsets; resultset; resultset = resultset->next)
      {
        ResultSetList *new = (ResultSetList *) malloc(sizeof(ResultSetList));
        new->resultSetId = strdup(resultset->resultSetId);
        new->next = client->pending_delete_requests;
        client->pending_delete_requests = new;
      }
      send_one_delete_request(client);
      return;
    }
    
    LOG(facHigh, llevDebug, "Deleting all resultsets for client %d.",ref);
    /*
     * Send all delete requests, and mark all connections as blocking
     * (that is, "waiting_for_packet" is set)
     */
    client->resultSetsLeftToDelete = 0;
    for (resultset = client->resultsets; resultset; resultset = resultset->next)
    {
      EapiPacket *pkt;
      if (resultset->server->waiting_for_packet == True)
        continue; /* Has already deleted all on this connection */
      pkt = (EapiPacket *) malloc(sizeof(EapiPacket));
      pkt->type = eapiDeleteResultSetRequest;
      pkt->u.delete_result_set_request = 
        (DeleteResultSetRequest *) malloc(sizeof(DeleteResultSetRequest));
      pkt->u.delete_result_set_request->all = True;
      pkt->u.delete_result_set_request->resultSetId = 0; /* Not used here */ 
      if (SendDBPacket(pkt, client, resultset->server->connection) == False)
        LOG(facHigh, llevExceptions, "Failed to send DeleteResultSetRequest to %d\n.",
          pkt->ref);
      else
      {
        client->resultSetsLeftToDelete++;
        resultset->server->waiting_for_packet = True;
      }
      free_EapiPacket(pkt);
    }
    free_resultsets(client->resultsets);
    client->resultsets = (ClientResultSetList *) NULL;
    /*
     * For the time being, we are making the nasty assumption that the
     * client had only one database-server connection.  Thus we send
     * back acknowledgement as soon as we receive the first response
     * from any server.
     */
    return;
  }

  LOG(facHigh, llevDebug, "Deleting specific resultsets for client %d.",ref);
  /*
   * Now transfer all the incoming delete-requests to the internal list of
   * pending delete-requests in the client-structure.
   */
  for(rsl = del_res_req->resultSetList; rsl; rsl=rsl->next) {
    ResultSetList *new = (ResultSetList *) malloc(sizeof(ResultSetList));
    new->resultSetId = strdup(rsl->resultSetId);
    new->next = client->pending_delete_requests;
    client->pending_delete_requests = new;
  }
  send_one_delete_request(client);
}

/*
 * Sends a failed delete result response back to client.
 */
void
send_delete_request_failure(Client *client, DeleteSetStatus fault, char *msg)
{
  SRDeleteResultSetResponse *req = (SRDeleteResultSetResponse *) malloc(sizeof(SRDeleteResultSetResponse));

  req->deleteOperationStatus = fault;
  req->deleteListStatuses = (ListStatuses *) NULL;
  req->numberNotDeleted = (-1);
  req->bulkStatuses = (ListStatuses *) NULL;
  if (msg)
    req->deleteMessage = strdup(msg);
  else
    req->deleteMessage = (char *) NULL;

  if (SR_SendDeleteResultSetResponse(client->ref, req) == False)
  {
    LOG(facHigh, llevExceptions, "Failed ot send DeleteResultSetResponse to %d.",client->ref);
    /* Probably should abort connection here */
  }
  client->state = clientWaiting;
  return;
}

/*
 * send_one_delete_request removes one entry in the internal list of
 * unprocessed delete-requests and sends the request to the databaseserver
 */

void
send_one_delete_request(Client *client)
{
  EapiPacket *pkt;
  ResultSetList *rsl = client->pending_delete_requests;
  /* ClientDatabaseServer *server; */
  ClientResultSetList *tmp;

  if (rsl == NULL) {
    LOG(facHigh, llevExceptions, "SendOneDeleteRequest without pending request.");
    return;
  }
  LOG(facHigh, llevTrace, "send_one_delete_request: %s", rsl->resultSetId);
  tmp = find_ClientResultSet(client, rsl->resultSetId);
  if (tmp == NULL) {
    LOG(facHigh, llevExceptions, "Can't find/delete resultset %s.",rsl->resultSetId);
    send_delete_request_failure(client, deleteSetStatus_resultSetDidNotExist, "Can't find resultset.");
    return;
  }
  pkt = (EapiPacket *) malloc(sizeof(EapiPacket));
  pkt->type = eapiDeleteResultSetRequest;
  pkt->u.delete_result_set_request =
    (DeleteResultSetRequest *) malloc(sizeof(DeleteResultSetRequest));
  pkt->u.delete_result_set_request->all = False;
  pkt->u.delete_result_set_request->resultSetId = tmp->resultSetNumber;
  client->working_delete_request = tmp; /* Must remember what we are working on */
  if (SendDBPacket(pkt, client, tmp->server->connection) == False)
  {
    LOG(facHigh, llevExceptions, "Failed to send DeleteResultSetRequest to %d.", pkt->ref);
    send_delete_request_failure(client, deleteSetStatus_systemProblemAtTarget, "Local: Failed to send DeleteResultSetRequest to high-level server.");
    free_EapiPacket(pkt);
    return;
  }

  free_EapiPacket(pkt);
  client->pending_delete_requests = rsl->next;
  free(rsl);
}

/*
 * DELETE RESULT SET RESPONSE
 */

void handleDeleteResultSetResponse (Client *client, EapiPacket *ep)
{
  ListStatuses *ls = (ListStatuses *) malloc(sizeof(ListStatuses));
  SRDeleteResultSetResponse *drsr;
  int success  = 0, failure = 0;

  LOG(facHigh, llevTrace, "DeleteResultSetResponse(%d)",client->ref);
  if (client->working_delete_request == NULL)
  {
    /*
     * This means that we requested all resultsets to be deleted...
     * We just return an acknowledgement as we receive the first response
     * from any server.  This will thus not work perfectly when a client
     * has connections to several servers at the same time.
     */
    if (client->state == clientWaiting) {
      LOG(facHigh, llevExceptions, "Ignoring trailing DeleteResultSetResponses.");
      return;
    }
    if (--(client->resultSetsLeftToDelete) > 0)
    {
      LOG(facHigh, llevDebug, "Still got %d left.", client->resultSetsLeftToDelete);
      return;
    }
    drsr = (SRDeleteResultSetResponse *)
            malloc(sizeof(SRDeleteResultSetResponse));
    if (ep->u.delete_result_set_response->status == True)
      drsr->deleteOperationStatus = deleteSetStatus_success;
    else
      drsr->deleteOperationStatus = deleteSetStatus_systemProblemAtTarget;
    drsr->deleteListStatuses = (ListStatuses *) NULL;
    drsr->numberNotDeleted = 0;
    drsr->bulkStatuses = (ListStatuses *) NULL;
    drsr->deleteMessage = (char *) NULL;
    if (SR_SendDeleteResultSetResponse(client->ref, drsr) == False)
    {
      LOG(facHigh, llevExceptions, "Failed ot send DeleteResultSetResponse to %d.",client->ref);
      /* Probably should abort connection here */
    }
    client->state = clientWaiting;
    free_EapiPacket(ep);
    return;
  }
  ls->resultSetId = strdup(client->working_delete_request->resultSetId);
  ls->deleteSetStatus = ep->u.delete_result_set_response->status;
  ls->next = client->finished_delete_requests;
  client->finished_delete_requests = ls->next;
  remove_ClientResultSet(client, client->working_delete_request);
  client->working_delete_request = NULL;

  free_EapiPacket(ep); /* Dont need it more */

  if(client->pending_delete_requests != NULL) {
    send_one_delete_request(client);
    return;
  }
  for(ls = client->finished_delete_requests; ls; ls = ls->next)
    if(ls->deleteSetStatus == deleteSetStatus_success)
      success++;
    else
      failure++;
  drsr = (SRDeleteResultSetResponse *)
            malloc(sizeof(SRDeleteResultSetResponse));
  if(failure)
   drsr->deleteOperationStatus =
     deleteSetStatus_notAllResultSetDeletedOnBulkDelete;
  else
    drsr->deleteOperationStatus = deleteSetStatus_success;
  drsr-> deleteListStatuses = client->finished_delete_requests;
  drsr-> numberNotDeleted = failure;
  drsr-> deleteMessage = (char *) NULL;
  drsr-> bulkStatuses = (ListStatuses *) NULL;
  if (SR_SendDeleteResultSetResponse( client->ref, drsr) == False)
  {
    LOG(facHigh, llevExceptions, "Failed to send DeleteResultSetResponse to %d.", client->ref);
    /* Probably should abort connection here */
  }
  client->state = clientWaiting;
}

/*
 * OPEN RESPONSE
 *
 * handleOpenResponse() tells if the database server accepted the connection
 * or not.  If it fails, a diagnostic must be sent to the client.
 */

void handleOpenResponse(Client *client, EapiPacket *ep)
{
  ClientDatabaseServer *server;

  LOG(facHigh, llevTrace, "handleOpenResponse(%d)", client->ref);

  server = fd2ClientDatabaseServer(client, client->file_descriptor);

  if (server == (ClientDatabaseServer *) NULL) {
    LOG(facHigh, llevExceptions, "No server found for client.");
    free_EapiPacket(ep);
    return;
  }
  server->connection->state = clientServerReady;

  if (server->pending_packet == NULL) {
    LOG(facHigh, llevExceptions, "Fatal: OpenResponse and no pending packet.");
    free_EapiPacket(ep);
    return;
  }

  if(server->pending_packet->type != eapiSearchRequest)
    LOG(facHigh, llevDebug, "Warning, pending packet was not SearchRequest.");

  if (ep->u.open_response->openAccepted == False) {
    LOG(facHigh, llevExceptions, "handleOpenResponse: Connection not accepted.");
    switch (server->pending_packet->type) {
    case eapiSearchRequest:
      sendSearchResponseDiagnostic(client, DIAG_BIB_1_TEMPORARY_SYSTEM_ERROR,
                                   "Can't connect to server.");
      break;
    default:
      break;
    }
    free_EapiPacket(ep);
    free_EapiPacket(server->pending_packet);
    server->pending_packet = NULL;
    return;
  }
  if (SendDBPacket(server->pending_packet, client, server->connection) == False)
    LOG(facHigh, llevExceptions, "Failed to send pending packet.");
  free_EapiPacket(server->pending_packet);
  server->pending_packet = NULL;
  free_EapiPacket(ep);
}

/*
 * CLOSE RESPONSE
 *
 * handleCloseResponse() confirms that the database server accepted the
 * termination of the connection.
 * Check if this connection was the last which should terminate.
 * If not, return;
 * If state is clientCloseRequest, send a CloseResponse to the client.
 * If state is clientAbort, just remove the association.
 */

void handleCloseResponse(Client *client, EapiPacket *ep)
{
  ClientDatabaseServer *server;

  LOG(facHigh, llevTrace, "CloseResponse(%d) from fd %d.", client->ref, client->file_descriptor);

  free_EapiPacket(ep); /* It does not really contain anything... */

  server = fd2ClientDatabaseServer(client, client->file_descriptor);
  if (!server)
  {
    LOG(facHigh, llevExceptions, "Got CloseResponse for unknown connection.");
    return;
  }
  remove_DatabaseServerConnectionToClient(server, client, True);
  if (FirstClientDatabaseServer(client, clientServerClosing))
  {
    LOG(facHigh, llevDebug, "handleCloseResponse: waiting for more.");
    return;	/* Have not closed them all yet */
  }
  switch(client->state)
  {
  case clientCloseRequest:
    send_close_response(client);
    break;
  case clientAbort:
    break;
  default:
    LOG(facHigh, llevExceptions, "Unknown state when receiving CloseResponse.");
  }
  remove_client(client);
}

/*
 * SEARCH RESPONSE
 *
 * handleSearchResponse() recieves an answer from the database server which
 * tells how many records were found.  It has to send back PresentRequest
 * packets to the database server if the client wants to recieve any
 * records along with the SearchRequest.
 */

void handleSearchResponse(Client *client, EapiPacket *ep) {
  EapiPacket *pkt;
  ClientDatabaseServer *server;
  ClientResultSetList *resultset;
  int requested;

  LOG(facHigh, llevTrace, "handleSearchResonse(%d)", client->ref);

  server = fd2ClientDatabaseServer(client, client->file_descriptor);

  if (ep->u.search_response->searchStatus == False)
  {
    /* SRSearchResponse *sr; */
    LOG(facHigh, llevExceptions, "SearchRequest failed, returning diagnostic.");
    /*
     * The high-level API interface is unable to contain diagnostic
     * messages in the SearchResponse, thus we just generate one with
     * "Unsupported search".
     */
    sendSearchResponseDiagnostic(client, DIAG_BIB_1_UNSUPPORTED_SEARCH, NULL);
    client->pending_request = NULL;
    client->state = clientWaiting;
    free_EapiPacket(ep);
    return;
  }
  if (ep->u.search_response->numberOfRecordsFound < 1)
  {
    SRSearchResponse *sr;
    LOG(facHigh, llevDebug, "Search response held no records.");
    sr = (SRSearchResponse *) malloc(sizeof(SRSearchResponse));
    sr -> numberOfRecordsFound = 0;
    sr -> numberOfRecordsReturned = 0;
    sr -> nextResultSetPosition = 0;
    sr -> searchStatus = True;
    sr -> resultSetStatus = resultSetStatus_none;
    sr -> presentStatus = presentStatus_ignore;
    sr -> records = NULL;
    if (SR_SendSearchResponse(client->ref,sr) == False)
      LOG(facHigh, llevExceptions, "handleSearchResponse: Serious internal problem.");
    client->pending_request = NULL;
    client->state = clientWaiting;
    free_EapiPacket(ep);
    return;
  }
  if(client->pending_request == NULL)
  {
    LOG(facHigh, llevExceptions, "Client %d had no pending request when receiving SearchResponse.", client->ref);
    /*
     * Protocol error...should probably abort connection instead of hanging.
     */
    free_EapiPacket(ep);
    return;
  }

  /*
   * Store the resultsetid.
   */
  resultset = add_ClientResultSet(client, server, client->pending_request->proposedResultSetId);
  resultset->resultSetNumber = ep->u.search_response->resultSetId;
  resultset->server = server;
  resultset->numberOfRecords = ep->u.search_response->numberOfRecordsFound;

  LOG(facHigh, llevDebug, "Stored resultset %s (%d).",resultset->resultSetId, resultset->resultSetNumber);
  
  /*
   * Find out how many records to ask for.
   */
  if ( client->pending_request->smallSetUpperBound >=
       ep->u.search_response->numberOfRecordsFound)
  {
    requested = ep->u.search_response->numberOfRecordsFound;
  }
  else if (client->pending_request->largeSetLowerBound >=
           ep->u.search_response->numberOfRecordsFound)
  {
    requested = client->pending_request->mediumSetPresentNumber;
  }
  else
  {
    /*
     * Too many records found, so dont return any.
     */
    SRSearchResponse *sr;
    LOG(facHigh, llevDebug, "SearchRequest successful, returning no records.");
    sr = (SRSearchResponse *) malloc(sizeof(SRSearchResponse));
    sr -> numberOfRecordsFound = ep->u.search_response->numberOfRecordsFound;
    sr -> numberOfRecordsReturned = 0;
    sr -> nextResultSetPosition = 1;
    sr -> searchStatus = True;
    sr -> resultSetStatus = resultSetStatus_none;
    sr -> presentStatus = presentStatus_success;
    sr -> records = NULL; /* Should there be a diagnostic here? */
    if (SR_SendSearchResponse(client->ref,sr) == False)
      sendSearchResponseDiagnostic(client, DIAG_BIB_1_PERMANENT_SYSTEM_ERROR, NULL);
    client->state = clientWaiting;
    client->pending_request = NULL;
    free_EapiPacket(ep);
    return;
  }

  LOG(facHigh, llevDebug, "SearchRequest successful, found %d, asking for %d records.",
      ep->u.search_response->numberOfRecordsFound, requested);

  /*
   * Store how many records were found until we are ready to send the
   * search response.
   */
  client->numberOfRecordsFound = ep->u.search_response->numberOfRecordsFound;

  pkt = (EapiPacket *) malloc(sizeof(EapiPacket));
  pkt->type = eapiPresentRequest;
  pkt->u.present_request = (PresentRequest *) malloc(sizeof(PresentRequest));

  pkt->u.present_request->resultSetId = 
    ep->u.search_response->resultSetId;
  pkt->u.present_request->resultSetStartPoint = 1;

  pkt->u.present_request->numberOfRecordsRequested = requested;
  pkt->u.present_request->recordComposition = 
           client->pending_request->smallSetRecordComposition;
  pkt->u.present_request->preferredRecordSyntax =
    OID_strdup(OID_Oid2name(client->pending_request->preferredRecordSyntax));
  
  if (SendDBPacket(pkt, client, server->connection) == False)
    LOG(facHigh, llevExceptions, "Failed to send PresentRequest to %d.",
        pkt->ref);
  free_EapiPacket(pkt);
  free_EapiPacket(ep);
}

/*
 * PRESENT RESPONSE
 *
 * handlePresentResponse() usually recieves a record from the database server.
 * It has to figure out if this was the last packet it was interrested
 * in, or if it has to query for the next packet.
 * When finished, it will send a SRSearchResponse to the client, containing
 * all packets.
 */

void handlePresentResponse(Client *client, EapiPacket *ep)
{
  /* DatabaseServer database; */

  LOG(facHigh, llevTrace, "handlePresentResponse(%d)", client->ref);
  /* Have to check limits of how many records to return in the future */

  switch(client->state) {
  case clientSearchRequest:
    {
      SRSearchResponse *sr;
      sr = (SRSearchResponse *) malloc(sizeof(SRSearchResponse));
  
      sr -> numberOfRecordsFound = 
        client->numberOfRecordsFound; /* Was stored here from search response */
      client->numberOfRecordsFound = (-1);
    
      sr -> numberOfRecordsReturned =
        ep->u.present_response->numberOfRecordsReturned;
      sr -> nextResultSetPosition = sr->numberOfRecordsReturned + 1;
      sr -> searchStatus = True;
      sr -> resultSetStatus = resultSetStatus_none; /* What is this? */
      sr -> presentStatus = ep->u.present_response->presentStatus;

      /* XXX: This is a hack to get around a bug. Now and then
ep->u.present_response->records will have the value 0xe even if sr ->
numberOfRecordsReturned is zero. This will of course result in a bus
error later on. */

      if ( sr -> numberOfRecordsReturned )
	 sr -> records = ConvertFromEapiRecords(client, ep->u.present_response->records);
      else
	 sr -> records = NULL;
      
      if (SR_SendSearchResponse(client->ref, sr) == False)
      {
        LOG(facHigh, llevExceptions, "Failed to send SearchResponse.");
        sendSearchResponseDiagnostic(client, DIAG_BIB_1_PERMANENT_SYSTEM_ERROR, NULL);
      }
    }
    break;
  case clientPresentRequest:
    {
      SRPresentResponse *pr;
      pr = (SRPresentResponse *) malloc(sizeof(SRPresentResponse));

      LOG(facHigh, llevDebug, "PresentResponse: got %d recs.", ep->u.present_response->numberOfRecordsReturned);
      pr -> numberOfRecordsReturned =
        ep->u.present_response->numberOfRecordsReturned;
      pr -> nextResultSetPosition = pr->numberOfRecordsReturned +
        client->resultSetStartPoint;
      if (pr->nextResultSetPosition > client->numberOfRecords)
        pr->nextResultSetPosition = 0;
      pr -> presentStatus = ep->u.present_response->presentStatus;
      pr -> records = ConvertFromEapiRecords(client, ep->u.present_response->records);

      if(SR_SendPresentResponse(client->ref, pr) == False)
      {
        LOG(facHigh, llevExceptions, "Failed to send PresentResponse to %d.",client->ref);
        sendPresentResponseDiagnostic(client, DIAG_BIB_1_PERMANENT_SYSTEM_ERROR, NULL);
      }
    }
    break;
  default:
    LOG(facHigh, llevExceptions, "Client was in state %d when receiving present response.", client->state);
  }

  client->state = clientWaiting;
  client->pending_request = NULL;
  free_EapiPacket(ep);
}

/*
 * ABORT & INTERRUPT RESPONSE
 *
 * I am unsure what to do with the interrupt.
 */

void handleAbort(int ref)
{
  Client *client;
  /* ClientDatabaseServer *server; */

  client = find_client(ref);
  if (!client)
  {
    LOG(facHigh, llevTrace, "Got Abort from unknown client: %d.", ref);
    return;
  }
  LOG(facHigh, llevTrace, "handleAbort(%d)", client->ref);
  client->state = clientAbort;

/*
 * close_servers() will send any needed close-requests to the servers
 * It will also free the structures which for which it does not generate
 * any close-requests.
 */

  close_servers(client);
 
/*
 * Break the connection to the client
 */

  SR_AbortAssociation(ref);

/*
 * Then check if there are any closing connections left.
 * If not, just send the CloseResponse back to the client.
 */
  if (!FirstClientDatabaseServer(client, clientServerClosing))
  {
    send_close_response(client);
    remove_client(client);
    return;
  }
  /* Will now wait for the close-response from the servers */
}

void handleInterruptResponse(Client *client, EapiPacket *packet) {
  LOG(facHigh, llevTrace, "handleInterruptResponse(%d)", client->ref);
  free_EapiPacket(packet);
  /* Log if the interrupt failed */
  /* Send response to client */
}
