/*++++++++++++++++++++
  refdbdgetrefx.c: refdb getrefx-related functions
  markus@mhoenicka.de 2007-09-08

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
   
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
   
  You should have received a copy of the GNU General Public License
  along with this program; if not, see <http://www.gnu.org/licenses/>

  +++++++++++++++++++++++++*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h> /* priority level definitions */
#include <errno.h>
#include <iconv.h>
#include <dbi/dbi.h>

#include "linklist.h"
#include "refdb.h"
#include "backend.h"
#include "refdbd.h" /* depends on backend.h */
#include "strfncs.h"
#include "tokenize.h"
#include "dbfncs.h"
#include "connect.h"
#include "refdbdgetrefx.h"
#include "writeris.h"
#include "xmlhandler.h"

/* some globals */
extern int n_log_level; /* numeric version of log_level */
extern char cs_term[];



/* todo: steal xml reading code from getbib, write handler for
   citationlistx data which creates a linked list with unique ID
   values, assemble query from that list */


/* prototypes of local functions */
static char* build_citekey_list(Lilifstring* ptr_first, char** ptr_buffer, size_t* ptr_buffer_len, dbi_conn conn);


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  getrefx(): implements the client command getrefx

  int getrefx returns 0 = no error, 1 = out of memory or db open error, 2 = subselect failed

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct bibinfo *ptr_biblio_info ptr to structure holding bibliography style

  int ref_format the output format 0 = simple screen rendering,
        1 = RIS format, 2 = DocBook format, 3 = BibTeX format,
        4 = HTML 10 = RISX 11 = XHTML

  int n_privatelist if set to 1, limit search to user's private list
                    if set to 0, search all

  struct ADDRESULT* ptr_addresult ptr to result counter structure

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int getrefx(struct CLIENT_REQUEST* ptr_clrequest, struct bibinfo *ptr_biblio_info, int ref_format, int n_privatelist, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires = NULL;
  dbi_driver driver;
  int render_res;
  int cs_status;
  int retval = 0;
  int citation_count; /* counter for citations in id handler */
  int xref_count; /* counter for xrefs in id handler */
  int nmem_error;
  int ndb_error;
  int n_client_status;
  unsigned long long nref_counter = 0;
  unsigned long long numrows = 0;
  size_t sql_command_len;
  size_t sql_command1_len;
  size_t result_len;
  size_t curr_multi_id_len;
  char *sql_command; /* these are ptrs to the buffers and temporary ptrs */
  char *new_sql_command;
  char *sql_command1;
  char *myjournal = NULL;
  char table_name[] = "getbibtemp";
  char *curr_multi_id = NULL;
  const char* db_encoding;
  const char *sql_errmsg = NULL;
  char limitstring[128] = "";
  struct lilimem sentinel;
  struct renderinfo rendinfo;
  struct getbib_data gbdata;
  struct simple_elstack *ptr_sfirst = NULL; /* start of the simple element stack */
  Lilifstring notfound_first;
  Lilifstring found_first;
  XML_Parser p;
  iconv_t conv_descriptor;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  notfound_first.ptr_next = NULL;
  *(notfound_first.token) = '\0';

  found_first.ptr_next = NULL;
  *(found_first.token) = '\0';

  /* fill in invariant elements of structure */
  rendinfo.ptr_biblio_info = ptr_biblio_info;
  rendinfo.ref_format = ref_format;
  rendinfo.nuse_citestyle = 0;
  rendinfo.dbname = NULL;
  /* todo: remove the redundance */
  rendinfo.database = ptr_clrequest->current_db;
  rendinfo.username = ptr_clrequest->username;
  rendinfo.pdfroot = ptr_clrequest->pdfroot;
  rendinfo.cgi_url = ptr_clrequest->cgi_url;
  rendinfo.ptr_clrequest = ptr_clrequest;
  rendinfo.javascript = 0;

  /* get buffer to assemble the SQL queries */
  sql_command_len = 4096; 
  sql_command = malloc(sql_command_len); 

  if (sql_command == NULL || insert_lilimem(&sentinel, (void**)&sql_command, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 2 */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  sql_command[0] = '\0'; /* start with an empty string */

  /* get another buffer to assemble the SQL queries */
  sql_command1_len = 4096;
  sql_command1 = malloc(sql_command1_len); 

  if (sql_command1 == NULL || insert_lilimem(&sentinel, (void**)&sql_command1, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 2 */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  sql_command1[0] = '\0'; /* start with an empty string */

  /* initialize "globals" */
  nmem_error = 0;
  ndb_error = 0;
  ptr_sfirst = NULL;
  citation_count = 0;
  xref_count = 0;

  /* create the parser instance */
  p = XML_ParserCreate(NULL);
  if (!p) {
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 2 */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  /* register our handlers. these handlers will be called whenever
     expat finds a start- or endtag or character data */
  XML_SetElementHandler(p, idraw_start_handler, idraw_end_handler);

  XML_SetCharacterDataHandler(p, idraw_char_handler);

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO); /* send step 2 */
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  driver = dbi_conn_get_driver(conn);

  /* get the database encoding */
  db_encoding = dbi_conn_get_encoding(conn);

  /* if we need to convert, create a conversion descriptor for iconv() */
  if (db_encoding && *(ptr_biblio_info->encoding) && strcmp(db_encoding, ptr_biblio_info->encoding)) {
    char to_encoding[64];

    if (!strcmp(ptr_biblio_info->encoding, "US-ASCII")) {
      strcpy(to_encoding, "ASCII//TRANSLIT");
    }
    else {
      snprintf(to_encoding, 64, "%s//TRANSLIT", ptr_biblio_info->encoding);
    }

    conv_descriptor = iconv_open(to_encoding, !strcmp(db_encoding, "US-ASCII") ? "ASCII" : db_encoding);
    if (conv_descriptor == (iconv_t)(-1)) {
      LOG_PRINT(LOG_WARNING, "cannot set conversion descriptor (database/output):");
      LOG_PRINT(LOG_DEBUG, db_encoding);
      LOG_PRINT(LOG_DEBUG, ptr_biblio_info->encoding);
      send_status(ptr_clrequest->fd, 701, TERM_NO); /* send step 2 */
      LOG_PRINT(LOG_WARNING, get_status_msg(701));
      delete_all_lilimem(&sentinel);
      return 1;
    }
    else {
      LOG_PRINT(LOG_DEBUG, "database encoding is:");
      LOG_PRINT(LOG_DEBUG, db_encoding);
    }
  }
  else {
    conv_descriptor = NULL;
    LOG_PRINT(LOG_DEBUG, "no character encoding conversion required");
  }

  LOG_PRINT(LOG_DEBUG, "output encoding is:");
  if (*(ptr_biblio_info->encoding)) {
    LOG_PRINT(LOG_DEBUG, ptr_biblio_info->encoding);
  }
  else {
    LOG_PRINT(LOG_DEBUG, db_encoding);
  }

  /* make data available for handlers */
  gbdata.ptr_default_db = ptr_clrequest->current_db;
  gbdata.conn = conn; /* connection to the reference db */
  gbdata.conn_refdb = NULL; /* not used here */
  gbdata.ptr_citation_count = &citation_count;
  gbdata.ptr_xref_count = &xref_count;
  gbdata.ptr_table_name = table_name;
  gbdata.ptr_curr_multi_id = curr_multi_id;
  gbdata.quoted_journal = myjournal;
  gbdata.ptr_cmid_len = &curr_multi_id_len;
  gbdata.ptr_sfirst = ptr_sfirst;
  gbdata.ptr_nmem_error = &nmem_error;
  gbdata.ptr_ndb_error = &ndb_error;
  gbdata.ptr_ndb_notfound = &(ptr_addresult->skipped);
  gbdata.ptr_clrequest = ptr_clrequest;
  gbdata.ptr_notfound_first = &notfound_first;
  gbdata.ptr_found_first = &found_first;

  XML_SetUserData(p, (void*)&gbdata);


  /* tell client that we're ready for XML data */
  send_status(ptr_clrequest->fd, 0, TERM_NO); /* send step 2 */
  
  /* read XML data from client */
  /* corresponds to steps 3 to 6 */
  if (!read_xml(ptr_clrequest->fd, p, ptr_addresult)) { /* error */
    dbi_conn_error(conn, &sql_errmsg);
    LOG_PRINT(LOG_WARNING, (char*)sql_errmsg);
    delete_idlist(ptr_sfirst);
    retval = 1;
    goto Finish;
  }
  else if (nmem_error) {
    retval = 1;
    goto Finish;
  }      
  else if (ndb_error) {
    dbi_conn_error(conn, &sql_errmsg);
    LOG_PRINT(LOG_WARNING, (char*)sql_errmsg);
    retval = 1;
    goto Finish;
  }      


  n_client_status = read_status(ptr_clrequest->fd); /* read step 7 */

/*   printf("done reading client status after transmitting xml data, now preparing to send output\n"); */
  if (n_client_status != 0) {
    LOG_PRINT(LOG_INFO, get_status_msg(112));
    retval = 1;
    goto Finish;
  }

  /* now assemble the SQL query string proper */
/*    printf("assembling query\n"); */
  /* 0  refdb_id             ID  - (if numeric)
     1  refdb_type           TY  - 
     2  refdb_pubyear        PY  - (partial)
     3  refdb_startpage      SP  - 
     4  refdb_endpage        EP  - 
     5  refdb_abstract       N2  - 
     6  refdb_title          TI  - 
     7  refdb_volume         VL  - 
     8  refdb_issue          CP  - 
     9  refdb_booktitle      BT  - 
    10  refdb_city           CY  - 
    11  refdb_publisher      PB  - 
    12  refdb_title_series   T3  - 
    13  refdb_address        AD  - 
    14  refdb_issn           SN  - 
    15  refdb_periodical_id  JO  - (indirect)
    16  refdb_pyother_info   PY  - (partial)
    17  refdb_secyear        Y2  - (partial)
    18  refdb_secother_info  Y2  - (partial)
    19  refdb_user1          U1  - 
    20  refdb_user2          U2  - 
    21  refdb_user3          U3  - 
    22  refdb_user4          U4  - 
    23  refdb_user5          U5  - 
    24  refdb_typeofwork     ..  - 
    25  refdb_area           ..  - 
    26  refdb_ostype         ..  - 
    27  refdb_degree         ..  - 
    28  refdb_runningtime    ..  - 
    29  refdb_classcodeintl  ..  - 
    30  refdb_classcodeus    ..  - 
    31  refdb_senderemail    ..  - 
    32  refdb_recipientemail ..  - 
    33  refdb_mediatype      ..  - 
    34  refdb_numvolumes     ..  - 
    35  refdb_edition        ..  - 
    36  refdb_computer       ..  - 
    37  refdb_conferencelocation   ..  - 
    38  refdb_registrynum    ..  - 
    39  refdb_classification ..  - 
    40  refdb_section        ..  - 
    41  refdb_pamphletnum    ..  - 
    42  refdb_chapternum     ..  - 
    43  refdb_citekey        ID  - (if non-numeric)
*/
  strcpy(sql_command, "SELECT DISTINCT t_refdb.refdb_id, t_refdb.refdb_type, t_refdb.refdb_pubyear, t_refdb.refdb_startpage, t_refdb.refdb_endpage, t_refdb.refdb_abstract, t_refdb.refdb_title, t_refdb.refdb_volume, t_refdb.refdb_issue, t_refdb.refdb_booktitle, t_refdb.refdb_city, t_refdb.refdb_publisher, t_refdb.refdb_title_series, t_refdb.refdb_address, t_refdb.refdb_issn, t_refdb.refdb_periodical_id, t_refdb.refdb_pyother_info, t_refdb.refdb_secyear, t_refdb.refdb_secother_info, t_refdb.refdb_user1, t_refdb.refdb_user2, t_refdb.refdb_user3, t_refdb.refdb_user4, t_refdb.refdb_user5, t_refdb.refdb_typeofwork, t_refdb.refdb_area, t_refdb.refdb_ostype, t_refdb.refdb_degree, t_refdb.refdb_runningtime, t_refdb.refdb_classcodeintl, t_refdb.refdb_classcodeus, t_refdb.refdb_senderemail, t_refdb.refdb_recipientemail, t_refdb.refdb_mediatype, t_refdb.refdb_numvolumes, t_refdb.refdb_edition, t_refdb.refdb_computer, t_refdb.refdb_conferencelocation, t_refdb.refdb_registrynum, t_refdb.refdb_classification, t_refdb.refdb_section, t_refdb.refdb_pamphletnum, t_refdb.refdb_chapternum, t_refdb.refdb_citekey FROM t_refdb WHERE ");

  if (ref_format != 1 || !strstr(ptr_biblio_info->format_string, "NOHOLES")) {
    if ((new_sql_command = mstrcat(sql_command, "refdb_type!='DUMMY' AND ", &sql_command_len, 0)) == NULL) {
      send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (conv_descriptor) {
	iconv_close(conv_descriptor);
      }
      return 1;
    }
    else {
      sql_command = new_sql_command;
    }
  }

  strcpy(sql_command1, "t_refdb.refdb_citekey IN (");

  if (build_citekey_list(gbdata.ptr_found_first, &sql_command1, &sql_command1_len, conn) == NULL) {
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }

  if ((new_sql_command = mstrcat(sql_command, sql_command1, &sql_command_len, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }
  else {
    sql_command = new_sql_command;
  }

  
  if ((new_sql_command = mstrcat(sql_command, ")", &sql_command_len, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }
  else {
    sql_command = new_sql_command;
  }

  
  /* sort the output */
  if (strncmp(ptr_biblio_info->sort_string, "PY", 2) == 0) {
    if (retval || ((new_sql_command = mstrcat(sql_command, " ORDER BY t_refdb.refdb_pubyear", &sql_command_len, 0)) == NULL)) {
      send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (conv_descriptor) {
	iconv_close(conv_descriptor);
      }
      return 1;
    }
    else {
      sql_command = new_sql_command;
    }
  }
  else {
    if (retval || ((new_sql_command = mstrcat(sql_command, " ORDER BY t_refdb.refdb_id", &sql_command_len, 0)) == NULL)) {
      send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (conv_descriptor) {
	iconv_close(conv_descriptor);
      }
      return 1;
    }
    else {
      sql_command = new_sql_command;
    }
  }

  /* see whether the query should be limited to a range */
  if (*(ptr_clrequest->limit)) {
    char *colon;
    
    colon = strchr(ptr_clrequest->limit, (int)':');
      
    if (!colon) {
      snprintf(limitstring, 128, " LIMIT %s ", ptr_clrequest->limit);
    }
    else {
      *colon = '\0';
      snprintf(limitstring, 128, " LIMIT %s OFFSET %s ", ptr_clrequest->limit, colon+1);
    }

    if ((new_sql_command = mstrcat(sql_command, limitstring, &sql_command_len, 0)) == NULL) {
      send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (conv_descriptor) {
	iconv_close(conv_descriptor);
      }
      return 1;
    }
    else {
      sql_command = new_sql_command;
    }
  }

  LOG_PRINT(LOG_DEBUG, sql_command);

  /* actually run the query */
  rendinfo.dbires = dbi_conn_query(conn, sql_command);
  if (!rendinfo.dbires) {
    send_status(ptr_clrequest->fd, 234, TERM_NO); /* send step 8 */
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }

  numrows = dbi_result_get_numrows(rendinfo.dbires);

  /* create a "header" if necessary */
  /* prepare_render resets sql_command */

  /* fill in variable elements of structure */
  rendinfo.ptr_ref = &sql_command;
  rendinfo.ptr_ref_len = &sql_command_len;
  if (!*(rendinfo.ptr_biblio_info->encoding)) {
    rendinfo.ptr_biblio_info->encoding = dbi_conn_get_encoding(conn);
  }

  /* real output starts here */
  if (prepare_render(&rendinfo)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    dbi_result_free(rendinfo.dbires);
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    if (conv_descriptor) {
      iconv_close(conv_descriptor);
    }
    return 1;
  }

  if (numrows) {
    /* fetch all articles we're interested in */
    while (dbi_result_next_row(rendinfo.dbires) != 0) {
      nref_counter++;
      /* fill in variable elements of structure */
      rendinfo.ptr_ref = &sql_command;
      rendinfo.ptr_ref_len = &sql_command_len;
      rendinfo.nref_counter = nref_counter;
      
      /* todo: count query errors and such as failed datasets, bail out only
	 after irrecoverable errors */
      if ((render_res = render_ref(&rendinfo)) != 0) {
	if (render_res == 801) {
	  retval = 1;
	  send_status(ptr_clrequest->fd, render_res, TERM_NO); /* send step 8 */
	  dbi_result_free(rendinfo.dbires);
	  dbi_conn_close(conn);
	  delete_all_lilimem(&sentinel);
	  if (conv_descriptor) {
	    iconv_close(conv_descriptor);
	  }
	  return 0;
	}
	else {
	  ptr_addresult->failure++;
	}
      }

      /* run a character encoding conversion if required */
      if (conv_descriptor && *sql_command) {
	size_t inlength;
	size_t outlength;
	char* my_sql_command = NULL; /* this ptr will be modified by iconv() */
	char* my_sql_command_start = NULL; /* records initial state of my_elvalue */
	const char* my_instring = NULL; /* this ptr will be modified by iconv() */
	/* strlen should be ok here as this can't be a multibyte encoding */
	inlength = strlen(sql_command)/*  + 1 */;
	/* with the encodings supported by our database engines, the converted
	   string can't be longer than six times the input string */
	/* todo: is this assumption correct? */
	outlength = 6*inlength;
	
	if ((my_sql_command = (char*)calloc(outlength, sizeof(char))) == NULL) {
	  retval = 1;
	  send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  dbi_result_free(rendinfo.dbires);
	  dbi_conn_close(conn);
	  delete_all_lilimem(&sentinel);
	  if (conv_descriptor) {
	    iconv_close(conv_descriptor);
	  }
	  return 0;
	}

	/* keep start of the converted string */
	my_sql_command_start = my_sql_command;

	/* variable will be modified by iconv, so don't use original */
	my_instring = (const char*)sql_command;

	/* now actually do the conversion */
	if (iconv(conv_descriptor, &my_instring, &inlength, &my_sql_command, &outlength) == (size_t)(-1)) {
	  if (errno == EILSEQ) {
	    LOG_PRINT(LOG_WARNING, "iconv: invalid input character sequence");
	  }
	  else if (errno == E2BIG) {
	    LOG_PRINT(LOG_WARNING, "iconv: output buffer too small");
	  }
	  else if (errno == EINVAL) {
	    LOG_PRINT(LOG_WARNING, "iconv: incomplete input character");
	  }
	  
	  retval = 1;
	  send_status(ptr_clrequest->fd, 702, TERM_NO); /* send step 8 */
	  dbi_result_free(rendinfo.dbires);
	  dbi_conn_close(conn);
	  delete_all_lilimem(&sentinel);
	  if (conv_descriptor) {
	    iconv_close(conv_descriptor);
	  }
	  return 0;
	}
	/* else: conversion went ok. We free the original string and replace
	   it with the converted copy */
	if (sql_command) {
	  free(sql_command);
	}
	sql_command = my_sql_command_start;
	sql_command_len = outlength;
	result_len = (size_t)(my_sql_command - my_sql_command_start - 1);
      }
      else { /* no conversion required */
	result_len = strlen(sql_command);
      }

      /* send ok status, then the terminated result string */
      send_status(ptr_clrequest->fd, 404, TERM_NO); /* send step 8 */
      iwrite(ptr_clrequest->fd, sql_command, result_len); 
      iwrite(ptr_clrequest->fd, cs_term, TERM_LEN);
      ptr_addresult->success++;
      
      /* reset buffer string */
      sql_command[0] = '\0';
      
      /* read client response */
      cs_status = read_status(ptr_clrequest->fd); /* read step 9 */

      if (cs_status) {
	break;
      }
    } /* end while */
  }
  else {
    /* no datasets, return an empty body */
    send_status(ptr_clrequest->fd, 402, TERM_NO); /* send step 8 */
    iwrite(ptr_clrequest->fd, cs_term, TERM_LEN);

    /* read client response */
    /*    cs_status = read_status(ptr_clrequest->fd);*/ /* read step 9 */
  }

  if (conv_descriptor) {
    iconv_close(conv_descriptor);
  }

  if (ptr_addresult->success) {
    /* create a "footer" if necessary */
    /* fill in variable elements of structure */
/*     rendinfo.ptr_ref = &sql_command; */
/*     rendinfo.ptr_ref_len = &sql_command_len; */

    if ((render_res = finish_render(&rendinfo)) != 0) {
      retval = 1;
      send_status(ptr_clrequest->fd, 801, TERM_NO); /* send step 8 */
      dbi_result_free(rendinfo.dbires);
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      return 0;
    }
    send_status(ptr_clrequest->fd, 402, TERM_NO); /* send step 8 */
    tiwrite(ptr_clrequest->fd, sql_command, TERM_YES); 
  }

 Finish:
  dbi_result_free(rendinfo.dbires);
  dbi_conn_close(conn);
  delete_all_lilimem(&sentinel);

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  build_citekey_list(): turns a Lilifstring list into a string with
                        comma-separated citation keys

  char* build_citekey_list returns a pointer to the result string or NULL
        if the realloc failed. As the buffer holding the destination
        string may have been reallocated, it is mandatory to use
        *ONLY* this returned pointer after the function call and
        *NEVER* the old pointer to destination

  char** ptr_buffer pointer to a malloc'ed string. The citation key list
        will be appended to the string, so it should be initialized to
        something useful before calling this function

  size_t* ptr_buffer_len points to a variable that contains the current size
       of the buffer that ptr_buffer points to. Will be modified if
       a realloc() is necessary to increase the buffer size

  dbi_conn conn database connection

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static char* build_citekey_list(Lilifstring* ptr_first, char** ptr_buffer, size_t* ptr_buffer_len, dbi_conn conn) {
  Lilifstring* ptr_curr;
  char* new_buffer;
  char* token;
  int n_isfirst = 1;
  
  ptr_curr = ptr_first;

  while ((ptr_curr = get_next_lilifstring(ptr_curr)) != NULL) {
    if (n_isfirst) {
      n_isfirst--;
    }
    else {
      /* add a separator */
      if ((new_buffer = mstrcat(*ptr_buffer, ",", ptr_buffer_len, 0)) == NULL) {
	return NULL;
      }
      else {
	*ptr_buffer = new_buffer;
      }
    }

    token = strdup(ptr_curr->token);
    if (!token) {
      return NULL;
    }

    if (dbi_conn_quote_string(conn, &token) == 0) {
      return NULL;
    }

    if ((new_buffer = mstrcat(*ptr_buffer, token, ptr_buffer_len, 0)) == NULL) {
      free(token);
      return NULL;
    }
    else {
      *ptr_buffer = new_buffer;
    }
    free(token);
  } /* end while */

  return *ptr_buffer;
}
