

// Standard headers

#if defined(GSDL_USE_OBJECTSPACE)
#  include <ospace\std\iostream>
#  include <ospace\std\fstream>
#  include <ospace\std\sstream>
#elif defined(GSDL_USE_IOS_H)
#  include <iostream.h>
#  include <fstream.h>
#  include <strstream.h>
#else
#  include <iostream>
#  include <fstream>
#  include <sstream>
#endif

#include <string>
#include <list>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>

// Greenstone headers
#include "fileutil.h"
#include "comtypes.h"
#include "nullproto.h"

// Z39.50 server headers
#include "z3950parser.h"
#include "z3950explain.h"

#include "z3950server.h"

// Dublin Core -> MARC (d2m) headers and source files
#include "d2m4gs.h"

// these globals are local to each thread,
// one Z39.50 connection = one thread.
text_t gsdlhome; // this is set by the collectset object from the gsdlsite.cfg file
text_t collecthome; // this is set by the collectset object
list<FilterResponse_t> Response_tlist; // list of response sets
list<text_t> Response_tlist_colnames;  // collection names for the above
list<int> Response_tlist_sizes;        // sizes (number of records) for the above
z3950Server *Server   = NULL;
collectset  *Cservers = NULL;
nullproto   *Protocol = NULL;

map<text_t, gsdlCollection> Collection_map; // info and tools for collections
map<text_t, text_t> Resultsets;             // mapping from ResultSetId to GSQuery 

int z3950_verbosity_ = 3;

// change the default format here - these two must match
int default_marc_format_d2m_ = USMARC; // USMARC, UNIMARC, DANMARC, FINMARC, NORMARC, SWEMARC
oid_value default_marc_format_yaz_ = VAL_USMARC; // VAL_USMARC, VAL_UNIMARC, VAL_DANMARC, VAL_FINMARC, VAL_NORMARC, VAL_SWEMARC

// *** initialize things here ***
bend_initresult *bend_init(bend_initrequest *q)
{
  // *** called when client connects to the server ***
  if (z3950_verbosity_ > 1) {
    cerr << "Entering bend_init" << endl;
  }

  Protocol = new nullproto();  
  Cservers = new collectset(gsdlhome, collecthome);
  Protocol->set_collectset(Cservers);
  
  cerr << "Starting Z39.50 Server ..." << endl;
  
  Server = new z3950Server(Protocol, gsdlhome);
  cerr << "Server constructed" << endl;
  
  const bool status = Server->initialise();
  cerr << "Initialised server: return status = " << status << endl;
  
  text_tarray collist;
  comerror_t err;
  Protocol->get_collection_list( collist, err, cout );

  // display the list of available Greenstone collections
  text_tarray::iterator cols_here=collist.begin();
  text_tarray::iterator cols_end=collist.end();
  while (cols_here != cols_end) {
    gsdlCollection tmpcol(*cols_here, gsdlhome);
    
    Collection_map[*cols_here] = tmpcol;
    
    if (tmpcol.z3950Capeable()) {
      cerr << "  " << tmpcol.getName() << "\tis Z39.50 capable" << endl;
    }
    cols_here++;
  }

    
  bend_initresult *r = (bend_initresult *) odr_malloc (q->stream, sizeof(*r));
  //static char *dummy = "Hej fister"; // what's this for?
  
  r->errcode = 0;
  r->errstring = 0;
  r->handle = Protocol;
  q->bend_sort = ztest_sort;       /* register sort handler */
  q->bend_search = ztest_search;   /* register search handler */
  q->bend_present = ztest_present; /* register present handle */
  q->bend_esrequest = ztest_esrequest;
  q->bend_delete = ztest_delete;
  q->bend_fetch = ztest_fetch;
  q->bend_scan = ztest_scan;

  return r;
}

// *** perform query, get number of hits ***
int ztest_search (void *handle, bend_search_rr *rr)
{
  // *** called when client issues a "find" command
  if (z3950_verbosity_>1) {
    cerr << "Entering ztest_search" << endl;
  }

  Response_tlist.clear();
  Response_tlist_colnames.clear();
  Response_tlist_sizes.clear();
 
  comerror_t err;
  FilterRequest_t request;

  request.filterName = "QueryFilter";
  request.filterResultOptions = FROID | FRtermFreq | FRmetadata;  

  OptionValue_t option;

  // how many results to get. grouping all results together into
  // one set instead of breaking up into "pages". 
  // note: see if Z39.50 has a concept of pages.
  option.name = "StartResults";
  option.value = "1";
  request.filterOptions.push_back (option);
  option.name = "EndResults";
  option.value = MAXHITS;
  request.filterOptions.push_back (option);
  option.name = "Maxdocs";
  option.value = MAXHITS;
  request.filterOptions.push_back (option);

  text_t GSQuery = ZQueryToGSQuery(rr);
  option.name = "Term";
  option.value = GSQuery;
  request.filterOptions.push_back (option);
  Resultsets[Resultsets.size()+1] = GSQuery;

  option.name = "QueryType";
  option.value = "boolean";
  request.filterOptions.push_back (option);

  option.name = "MatchMode";
  option.value = "all";
  request.filterOptions.push_back (option);

  option.name = "Casefold";
  option.value = "true";
  request.filterOptions.push_back (option);

  option.name = "Stem";
  option.value = "false";
  request.filterOptions.push_back (option);

  option.name = "AccentFold";
  option.value = "false";
  request.filterOptions.push_back (option);
  
  // MG queries don't need this, MGPP queries do.
  // doesn't do any harm to include it anyway.
  // note: not all collections have document-level granularity...
  option.name = "Level";
  option.value = "Doc";
  request.filterOptions.push_back (option);

  // Z39.50 supports the searching of multiple databases
  rr->hits = 0;
  for (int i = 0; i < rr->num_bases; i++) {
    FilterResponse_t response;
    cerr << "Searching basename = " << rr->basenames[i] << endl;
    // intercept Explain requests
    if (strcmp(rr->basenames[i], "IR-Explain-1")==0) {
      //ColInfoResponse_t collectinfo;
      //Protocol->get_collectinfo("csbib", collectinfo, err, cerr);
      //response.numDocs = 1;
      //ResultDocInfo_t docinfo;
      //docinfo.metadata["foo"].values.push_back("bar");
      getExplainInfo(rr, response, err);
      //response.docInfo.push_back(docinfo);
    } else {
      cerr << "calling filter..." << endl;
      Protocol->filter(rr->basenames[i], request, response, err, cerr);
      cerr << "returned from filter." << endl;
      if (response.numDocs > MAXHITS) {
	response.numDocs = MAXHITS;
      }
    }
    rr->hits += response.numDocs;
    Response_tlist.push_back(response);
    text_t basename = rr->basenames[i];
    Response_tlist_colnames.push_back(basename);
    Response_tlist_sizes.push_back(response.numDocs);
    cerr << "search complete." << endl;
  }    

  if (rr->hits > MAXHITS) rr->hits = MAXHITS;
  return 0;
}

// *** extra code for showing results, not needed ***
int ztest_present (void *handle, bend_present_rr *rr)
{
  // *** called when client issues a "show" command, 1st of 5
  if (z3950_verbosity_>1) {
    cerr << "entering ztest_present" << endl;
  }
  return 0;
}

int ztest_esrequest (void *handle, bend_esrequest_rr *rr)
{

    if (z3950_verbosity_>1) {
      cerr << "Entering ztest_esrequest" << endl;
    }
    yaz_log(LOG_LOG, "function: %d", *rr->esr->function);
    if (rr->esr->packageName)
    	yaz_log(LOG_LOG, "packagename: %s", rr->esr->packageName);
    yaz_log(LOG_LOG, "Waitaction: %d", *rr->esr->waitAction);

    if (!rr->esr->taskSpecificParameters)
    {
        yaz_log (LOG_WARN, "No task specific parameters");
    }
    else if (rr->esr->taskSpecificParameters->which == Z_External_itemOrder)
    {
    	Z_ItemOrder *it = rr->esr->taskSpecificParameters->u.itemOrder;
	yaz_log (LOG_LOG, "Received ItemOrder");
	switch (it->which)
	{
#ifdef ASN_COMPILED
	case Z_IOItemOrder_esRequest:
#else
	case Z_ItemOrder_esRequest:
#endif
	{
	    Z_IORequest *ir = it->u.esRequest;
	    Z_IOOriginPartToKeep *k = ir->toKeep;
	    Z_IOOriginPartNotToKeep *n = ir->notToKeep;
	    
	    if (k && k->contact)
	    {
	        if (k->contact->name)
		    yaz_log(LOG_LOG, "contact name %s", k->contact->name);
		if (k->contact->phone)
		    yaz_log(LOG_LOG, "contact phone %s", k->contact->phone);
		if (k->contact->email)
		    yaz_log(LOG_LOG, "contact email %s", k->contact->email);
	    }
	    if (k->addlBilling)
	    {
	        yaz_log(LOG_LOG, "Billing info (not shown)");
	    }
	    
	    if (n->resultSetItem)
	    {
	        yaz_log(LOG_LOG, "resultsetItem");
		yaz_log(LOG_LOG, "setId: %s", n->resultSetItem->resultSetId);
		yaz_log(LOG_LOG, "item: %d", *n->resultSetItem->item);
	    }
#ifdef ASN_COMPILED
	    if (n->itemRequest)
	    {
		Z_External *r = (Z_External*) n->itemRequest;
		ILL_ItemRequest *item_req = 0;
		ILL_Request *ill_req = 0;
		if (r->direct_reference)
		{
		    oident *ent = oid_getentbyoid(r->direct_reference);
		    if (ent)
			yaz_log(LOG_LOG, "OID %s", ent->desc);
		    if (ent && ent->value == VAL_ISO_ILL_1)
		    {
			yaz_log (LOG_LOG, "ItemRequest");
			if (r->which == ODR_EXTERNAL_single)
			{
			    odr_setbuf(rr->decode,
				       (char*)(r->u.single_ASN1_type->buf),
				       r->u.single_ASN1_type->len, 0);
			    
			    if (!ill_ItemRequest (rr->decode, &item_req, 0, 0))
			    {
				yaz_log (LOG_LOG,
                                    "Couldn't decode ItemRequest %s near %d",
                                       odr_errmsg(odr_geterror(rr->decode)),
                                       odr_offset(rr->decode));
                                yaz_log(LOG_LOG, "PDU dump:");
                                odr_dumpBER(yaz_log_file(),
                                     (const char*)(r->u.single_ASN1_type->buf),
                                     r->u.single_ASN1_type->len);
                            }
			    if (rr->print)
			    {
				ill_ItemRequest (rr->print, &item_req, 0,
                                    "ItemRequest");
				odr_reset (rr->print);
 			    }
			}
			if (!item_req && r->which == ODR_EXTERNAL_single)
			{
			    yaz_log (LOG_LOG, "ILLRequest");
			    odr_setbuf(rr->decode,
				       (char*)(r->u.single_ASN1_type->buf),
				       r->u.single_ASN1_type->len, 0);
			    
			    if (!ill_Request (rr->decode, &ill_req, 0, 0))
			    {
				yaz_log (LOG_LOG,
                                    "Couldn't decode ILLRequest %s near %d",
                                       odr_errmsg(odr_geterror(rr->decode)),
                                       odr_offset(rr->decode));
                                yaz_log(LOG_LOG, "PDU dump:");
                                odr_dumpBER(yaz_log_file(),
                                     (const char*)(r->u.single_ASN1_type->buf),
                                     r->u.single_ASN1_type->len);
                            }
			    if (rr->print)
                            {
				ill_Request (rr->print, &ill_req, 0,
                                    "ILLRequest");
				odr_reset (rr->print);
			    }
			}
		    }
		}
		if (item_req)
		{
		    yaz_log (LOG_LOG, "ILL protocol version = %d",
			     *item_req->protocol_version_num);
		}
	    }
#endif
	}
	break;
	}
    }
    else if (rr->esr->taskSpecificParameters->which == Z_External_update)
    {
    	Z_IUUpdate *up = rr->esr->taskSpecificParameters->u.update;
	yaz_log (LOG_LOG, "Received DB Update");
	if (up->which == Z_IUUpdate_esRequest)
	{
	    Z_IUUpdateEsRequest *esRequest = up->u.esRequest;
	    Z_IUOriginPartToKeep *toKeep = esRequest->toKeep;
	    Z_IUSuppliedRecords *notToKeep = esRequest->notToKeep;
	    
	    yaz_log (LOG_LOG, "action");
	    if (toKeep->action)
	    {
		switch (*toKeep->action)
		{
		case Z_IUOriginPartToKeep_recordInsert:
		    yaz_log (LOG_LOG, " recordInsert");
		    break;
		case Z_IUOriginPartToKeep_recordReplace:
		    yaz_log (LOG_LOG, " recordUpdate");
		    break;
		case Z_IUOriginPartToKeep_recordDelete:
		    yaz_log (LOG_LOG, " recordDelete");
		    break;
		case Z_IUOriginPartToKeep_elementUpdate:
		    yaz_log (LOG_LOG, " elementUpdate");
		    break;
		case Z_IUOriginPartToKeep_specialUpdate:
		    yaz_log (LOG_LOG, " specialUpdate");
		    break;
		default:
		    yaz_log (LOG_LOG, " unknown (%d)", *toKeep->action);
		}
	    }
	    if (toKeep->databaseName)
	    {
		yaz_log (LOG_LOG, "database: %s", toKeep->databaseName);
		if (!strcmp(toKeep->databaseName, "fault"))
		{
		    rr->errcode = 109;
		    rr->errstring = toKeep->databaseName;
		}
		if (!strcmp(toKeep->databaseName, "accept"))
		    rr->errcode = -1;
	    }
	    if (notToKeep)
	    {
		int i;
		for (i = 0; i < notToKeep->num; i++)
		{
		    Z_External *rec = notToKeep->elements[i]->record;

		    if (rec->direct_reference)
		    {
			struct oident *oident;
			oident = oid_getentbyoid(rec->direct_reference);
			if (oident)
			    yaz_log (LOG_LOG, "record %d type %s", i,
				     oident->desc);
		    }
		    switch (rec->which)
		    {
		    case Z_External_sutrs:
			if (rec->u.octet_aligned->len > 170)
			    yaz_log (LOG_LOG, "%d bytes:\n%.168s ...",
				     rec->u.sutrs->len,
				     rec->u.sutrs->buf);
			else
			    yaz_log (LOG_LOG, "%d bytes:\n%s",
				     rec->u.sutrs->len,
				     rec->u.sutrs->buf);
                        break;
		    case Z_External_octet        :
			if (rec->u.octet_aligned->len > 170)
			    yaz_log (LOG_LOG, "%d bytes:\n%.168s ...",
				     rec->u.octet_aligned->len,
				     rec->u.octet_aligned->buf);
			else
			    yaz_log (LOG_LOG, "%d bytes\n%s",
				     rec->u.octet_aligned->len,
				     rec->u.octet_aligned->buf);
		    }
		}
	    }
	}
    }
    else
    {
        yaz_log (LOG_WARN, "Unknown Extended Service(%d)",
		 rr->esr->taskSpecificParameters->which);
	
    }
    return 0;
}

int ztest_delete (void *handle, bend_delete_rr *rr)
{
  // *** called when client issues a "delete" command
  if (z3950_verbosity_>1) {
    cerr << "Entering ztest_delete" << endl;
  }
  if (rr->num_setnames == 1 && !strcmp (rr->setnames[0], "1"))
    rr->delete_status = Z_DeleteStatus_success;
  else
    rr->delete_status = Z_DeleteStatus_resultSetDidNotExist;
  return 0;
}

// *** do we want to sort the results? ***
/* Our sort handler really doesn't sort... */
int ztest_sort (void *handle, bend_sort_rr *rr)
{
  if (z3950_verbosity_>1) {
    cerr << "entering ztest_sort" << endl;
  }
  rr->errcode = 0;
  rr->sort_status = Z_SortStatus_success;
  return 0;
}

static int atoin (const char *buf, int n)
{
  // *** called when client issues a "show" command, 5th of 5
  // *** NOT called if the "show" command has an invalid argument
    if (z3950_verbosity_>1) {
      cerr << "Entering atoin" << endl;
    }
    int val = 0;
    while (--n >= 0)
    {
        if (isdigit(*buf))
            val = val*10 + (*buf - '0');
        buf++;
    }
    return val;
}


bool get_sutrs_record (int recnum, text_t &MetadataResult, text_t &CollectionName)
{
  if (z3950_verbosity_>1) {
    cerr << "Entering get_sutrs_record" << endl;
  }
  int total_records = 0;

  list<FilterResponse_t>::iterator RESPONSE_here = Response_tlist.begin();
  list<FilterResponse_t>::iterator RESPONSE_end = Response_tlist.end();
  list<text_t>::iterator RESPONSE_COLNAMES_here = Response_tlist_colnames.begin();
  list<text_t>::iterator RESPONSE_COLNAMES_end = Response_tlist_colnames.end();
  list<int>::iterator RESPONSE_SIZES_here = Response_tlist_sizes.begin();
  list<int>::iterator RESPONSE_SIZES_end = Response_tlist_sizes.end();

  RESPONSE_here = Response_tlist.begin();
  RESPONSE_end = Response_tlist.end();
  while (RESPONSE_here != RESPONSE_end) {
    total_records += (*RESPONSE_here).numDocs;
    RESPONSE_here++;
  }

  // check that the index is within bounds
  if (recnum < 1 || recnum > total_records) {
    cerr << "get_sutrs_record failed" << endl;
    return false; // failed
  } else {
    FilterResponse_t response;
    // iterate through Response_tlist to find the right response
    RESPONSE_here = Response_tlist.begin();
    RESPONSE_end = Response_tlist.end();
    RESPONSE_COLNAMES_here = Response_tlist_colnames.begin();
    RESPONSE_COLNAMES_end = Response_tlist_colnames.end();
    RESPONSE_SIZES_here = Response_tlist_sizes.begin();
    RESPONSE_SIZES_end = Response_tlist_sizes.end();
    int n = 0;

    while (RESPONSE_here != RESPONSE_end) {
      n += *RESPONSE_SIZES_here;
      if (n >= recnum) {
	response = *RESPONSE_here;
	CollectionName = *RESPONSE_COLNAMES_here;
	recnum = *RESPONSE_SIZES_here - (n - recnum);
	break;
      }
      // these should all have the same number of elements,
      // so only need to check that one is within bounds.
      // may want to combine these into a larger data structure later.
      RESPONSE_here++;
      RESPONSE_COLNAMES_here++;
      RESPONSE_SIZES_here++;
    }

    // locate the desired record in the response 
   
    MetadataInfo_tmap::iterator METAFIELD_here = response.docInfo[recnum-1].metadata.begin();
    MetadataInfo_tmap::iterator METAFIELD_end = response.docInfo[recnum-1].metadata.end();

    while (METAFIELD_here!=METAFIELD_end) {
      text_tarray::iterator METAVALUE_here = METAFIELD_here->second.values.begin();
      text_tarray::iterator METAVALUE_end = METAFIELD_here->second.values.end();
      while (METAVALUE_here!=METAVALUE_end) {
	// construct a text_t containing the record:
	// there are multiple metadata fields,
	// each field can have multiple values.
	/*
	// don't include fields starting with a lowercase letter,
	// these are system fields.
	// later: include these anyway, since Explain fields start with lowercase letters
	if (METAFIELD_here->first[0] >= 'A' &&
	    METAFIELD_here->first[0] <= 'Z') {
	*/
	  MetadataResult += METAFIELD_here->first; // field name
	  MetadataResult += " ";
	  MetadataResult += *METAVALUE_here; // field contents
	  MetadataResult += "\n";
	  //}
	METAVALUE_here++;
      }
      METAFIELD_here++;
    }
    return true; // succeeded
  }
}

bool get_marc_record (int recnum, list<metatag*> &Metatag_list, text_t &CollectionName)
{
  if (z3950_verbosity_>1) {
    cerr << "Entering get_marc_record" << endl;
  }
  int total_records = 0;

  list<FilterResponse_t>::iterator RESPONSE_here = Response_tlist.begin();
  list<FilterResponse_t>::iterator RESPONSE_end = Response_tlist.end();
  list<text_t>::iterator RESPONSE_COLNAMES_here = Response_tlist_colnames.begin();
  list<text_t>::iterator RESPONSE_COLNAMES_end = Response_tlist_colnames.end();
  list<int>::iterator RESPONSE_SIZES_here = Response_tlist_sizes.begin();
  list<int>::iterator RESPONSE_SIZES_end = Response_tlist_sizes.end();

  RESPONSE_here = Response_tlist.begin();
  RESPONSE_end = Response_tlist.end();
  while (RESPONSE_here != RESPONSE_end) {
    total_records += (*RESPONSE_here).numDocs;
    RESPONSE_here++;
  }

  // check that the index is within bounds
  if (recnum < 1 || recnum > total_records) {
    cerr << "get_marc_record failed" << endl;
    return false; // failed
  }
   
  FilterResponse_t response;
  // iterate through Response_tlist to find the right response
  RESPONSE_here = Response_tlist.begin();
  RESPONSE_end = Response_tlist.end();
  RESPONSE_COLNAMES_here = Response_tlist_colnames.begin();
  RESPONSE_COLNAMES_end = Response_tlist_colnames.end();
  RESPONSE_SIZES_here = Response_tlist_sizes.begin();
  RESPONSE_SIZES_end = Response_tlist_sizes.end();
  int n = 0;
  while (RESPONSE_here != RESPONSE_end) {
    n += *RESPONSE_SIZES_here;
    if (n >= recnum) {
      response = *RESPONSE_here;
      CollectionName = *RESPONSE_COLNAMES_here;
      recnum = *RESPONSE_SIZES_here - (n - recnum);
      break;
    }
    // these should all have the same number of elements,
    // so only need to check that one is within bounds.
    // may want to combine these into a larger data structure later.
    RESPONSE_here++;
    RESPONSE_COLNAMES_here++;
    RESPONSE_SIZES_here++;
  }

  // locate the desired record in the response and gather all the metadata
    
  MetadataInfo_tmap::iterator METAFIELD_here = response.docInfo[recnum-1].metadata.begin();
  MetadataInfo_tmap::iterator METAFIELD_end = response.docInfo[recnum-1].metadata.end();
  
  while (METAFIELD_here!=METAFIELD_end) {
    text_t meta_name = METAFIELD_here->first;
    text_t::const_iterator pos = findchar(meta_name.begin(), meta_name.end(), '.');
    // strip off namespace and subfield
    if (pos != meta_name.end()) {
      meta_name = substr(pos+1, meta_name.end());
    }
    pos = findchar(meta_name.begin(), meta_name.end(), '^');
    if (pos != meta_name.end()) {
      meta_name = substr(meta_name.begin(), pos);
    }
    if (meta_name[0] >= 'A' &&
	meta_name[0] <= 'Z') {
      text_tarray::iterator METAVALUE_here = METAFIELD_here->second.values.begin();
      text_tarray::iterator METAVALUE_end = METAFIELD_here->second.values.end();
      while (METAVALUE_here!=METAVALUE_end) {
	
	metatag *tmptag = new metatag();
	tmptag->name = meta_name.getcstr();
	tmptag->type = "";
	tmptag->scheme = "";
	tmptag->value = (*METAVALUE_here).getcstr();
	Metatag_list.push_back(tmptag);
	
	METAVALUE_here++;
      }
    }
    
    METAFIELD_here++;
  }
  return true; // succeeded

}

// *** implement this ***
int ztest_fetch(void *handle, bend_fetch_rr *r)
{
  // *** called when client issues a "show" command, 2nd of 5
    if (z3950_verbosity_>1) {
      cerr << "Entering ztest_fetch" << endl;
    }
    char *cp;
    r->errstring = 0;
    r->last_in_set = 0;
    r->basename = "DUMMY"; // this is set below
    r->output_format = r->request_format;

    // simple unstructured text
    if (r->request_format == VAL_SUTRS)
    {
      // copies record into r, errcode will be 13 if fails
      text_t MetadataResult;
      text_t CollectionName;
      if (!get_sutrs_record(r->number, MetadataResult, CollectionName)) {
	r->errcode = 13;
	return 0;
      } else {			
	r->len = MetadataResult.size();
	r->record = (char*) odr_malloc(r->stream, r->len+1);
	char *tmptxt = MetadataResult.getcstr();
	strcpy(r->record, tmptxt);
	delete tmptxt;
	r->basename = (char*) odr_malloc(r->stream, CollectionName.size()+1);
	tmptxt = CollectionName.getcstr();
	strcpy(r->basename, tmptxt);
	delete tmptxt;
      }
    }
    // *** do we want to use this format? ***
    else if (r->request_format == VAL_GRS1)
    {
      // bail out, since we don't support grs (yet?)
      r->errcode = 107; // "Query type not supported"
      return 0;
      
    } 
    // assume that here on in is some sort of MARC format
    else {
      marcrec *mrec = new marcrec;

      mrec = (marcrec*) malloc(sizeof(*mrec));
      
      mrec->marcline = (char*) malloc(100000);
      mrec->partitle = (char*) malloc(10000);
      mrec->subtitle = (char*) malloc(10000);
      mrec->year     = (char*) malloc(1000);
      mrec->url      = (char*) malloc(1000);
      mrec->fmat     = (char*) malloc(1000);
      mrec->s008     = (char*) malloc(41);
      
      mrec->ncreators = 0;
      mrec->ntitles   = 0;
      *mrec->marcline = 0;
      *mrec->subtitle = 0;
      *mrec->partitle = 0;
      *mrec->year     = 0;
      *mrec->url      = 0;
      *mrec->fmat     = 0;
      strcpy(mrec->s008, "                                        ");

      text_t CollectionName;
      list<metatag*> Metatag_list;

      if (!get_marc_record(r->number, Metatag_list, CollectionName)) {
	r->errcode = 13;
	return 0;
      }

      // work out the d2m format number
      int record_format;
      switch(r->request_format) {
      case VAL_DANMARC: 
	record_format = DANMARC;
	break;
      case VAL_FINMARC:
	record_format = FINMARC;
	break;
      case VAL_NORMARC:
	record_format = NORMARC;
	break;
      case VAL_SWEMARC:
	record_format = SWEMARC;
	break;
      case VAL_UNIMARC:
	record_format = UNIMARC;
	break;
      default:
	// we don;t know any other format, so assume usMARC
	record_format = default_marc_format_d2m_;
	// set above to be request_format, but this may change it
	r->output_format = default_marc_format_yaz_;
      }
      
      

      list<metatag*>::iterator METATAG_here = Metatag_list.begin();
      list<metatag*>::iterator METATAG_end = Metatag_list.end();
      while (METATAG_here != METATAG_end) {
	//usMARC(*METATAG_here, mrec);
	MARCmake(*METATAG_here, mrec, record_format);
	METATAG_here++;
      }

      // set the date in the marc record to the current date
      char today[32];
      time_t d;
      struct tm *time_struct;
      //putenv("TZ=NFT-1DFT");
      d = time(NULL);
      time_struct = localtime(&d);
      strftime(today, 16, "%y%m%d", time_struct);
      put008(mrec->s008, today, F008_DATE_ENTERED);
      put008(mrec->s008, "s", F008_TYPE_OF_DATE);

      // to do: make this safer + more efficient
      cp = new char[1000000];
      char *cp2 = new char[1000000];
	
      MARCtidy(mrec, cp, record_format);
	
      sprintf(cp2, "%s", (char*) MARC2709(cp, 0, 6, 'n', 'm', ' '));
      r->len = strlen(cp2);
      r->record = (char*) odr_malloc(r->stream, r->len+1);
      strcpy(r->record, cp2);     
      r->basename = (char*) odr_malloc(r->stream, CollectionName.size()+1);
      char *tmptxt;
      tmptxt = CollectionName.getcstr();
      strcpy(r->basename, tmptxt);
      delete tmptxt;
      delete cp2;      
      cerr << "dumping record:`" << cp << "'" << endl;
      delete cp;
      
    }

    r->errcode = 0;
    return 0;
}

/*
 * silly dummy-scan what reads words from a file.
 */
int ztest_scan(void *handle, bend_scan_rr *q)
{
  // *** called when client issues a "scan" command
  if (z3950_verbosity_>1) {
    cerr << "Entering ztest_scan" << endl;
  }
    
  // bail out, since we don't support scan (yet?)
  q->errcode = 107; // "Query type not supported"
  return 0;

    static FILE *f = 0;
    static struct scan_entry list[200];
    static char entries[200][80];
    int hits[200];
    char term[80], *p;
    int i, pos;
    int term_position_req = q->term_position;
    int num_entries_req = q->num_entries;

    q->errcode = 0;
    q->errstring = 0;
    q->entries = list;
    q->status = BEND_SCAN_SUCCESS;
    if (!f && !(f = fopen("dummy-words", "r")))
    {
	perror("dummy-words");
	exit(1);
    }
    if (q->term->term->which != Z_Term_general)
    {
    	q->errcode = 229; /* unsupported term type */
	return 0;
    }
    if (*q->step_size != 0)
    {
	q->errcode = 205; /*Only zero step size supported for Scan */
	return 0;
    }
    if (q->term->term->u.general->len >= 80)
    {
    	q->errcode = 11; /* term too long */
	return 0;
    }
    if (q->num_entries > 200)
    {
    	q->errcode = 31;
	return 0;
    }
    memcpy(term, q->term->term->u.general->buf, q->term->term->u.general->len);
    term[q->term->term->u.general->len] = '\0';
    for (p = term; *p; p++)
    	if (islower(*p))
	    *p = toupper(*p);

    fseek(f, 0, SEEK_SET);
    q->num_entries = 0;

    for (i = 0, pos = 0; fscanf(f, " %79[^:]:%d", entries[pos], &hits[pos]) == 2;
	i++, pos < 199 ? pos++ : (pos = 0))
    {
    	if (!q->num_entries && strcmp(entries[pos], term) >= 0) /* s-point fnd */
	{
	    if ((q->term_position = term_position_req) > i + 1)
	    {
	    	q->term_position = i + 1;
		q->status = BEND_SCAN_PARTIAL;
	    }
	    for (; q->num_entries < q->term_position; q->num_entries++)
	    {
	    	int po;

		po = pos - q->term_position + q->num_entries+1; /* find pos */
		if (po < 0)
		    po += 200;

		if (!strcmp (term, "SD") && q->num_entries == 2)
		{
		    list[q->num_entries].term = entries[pos];
		    list[q->num_entries].occurrences = -1;
		    list[q->num_entries].errcode = 233;
		    list[q->num_entries].errstring = "SD for Scan Term";
		}
		else
		{
		    list[q->num_entries].term = entries[po];
		    list[q->num_entries].occurrences = hits[po];
		}
	    }
	}
	else if (q->num_entries)
	{
	    list[q->num_entries].term = entries[pos];
	    list[q->num_entries].occurrences = hits[pos];
	    q->num_entries++;
	}
	if (q->num_entries >= num_entries_req)
	    break;
    }
    if (feof(f))
    	q->status = BEND_SCAN_PARTIAL;
    return 0;
}



void bend_close(void *handle)
{
  if (z3950_verbosity_>1) {
    cerr << "Entering bend_close" << endl;
  }
  delete Server;
  delete Cservers;
  delete Protocol;
  if (z3950_verbosity_>1) {
    cerr << "heap cleaned up, exiting bend_close" << endl;
  }
  return;
}

int main (int argc, char *argv[])
{
  
  const int statserv_var =  statserv_main(argc, argv, bend_init, bend_close);
  cerr << "statserv_main returns: " << statserv_var << endl;
  return statserv_var;
  
  return 0;
}


