/**********************************************************************
 *
 * z3950proxy.cpp --
 * Copyright (C) 2000  The New Zealand Digital Library Project
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include "z3950proxy.h"
#include "comtypes.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// z39.50 yaz stuff
#define min(a,b) (((a)<(b))?(a):(b))

extern "C" {
#include "yaz_zclient.h"
}

/*** 
     each z39.50 server+database pair is a GSDL collection.
***/

z3950_proxy::z3950_proxy() {
  info=NULL;
  connected=false;
  titles=NULL;
  gotsinglerecord=false;
  // for now, assume that all records will have text associated with them.
  meta["hastxt"]="1";
  // for now, assume we don't want ANY DocumentButtons.
  format["DocumentButtons"]=g_EmptyText;
}

z3950_proxy::~z3950_proxy() {
}

void z3950_proxy::setMeta(const text_t &key, const text_t &value) {
  meta[key]=value;
}

void z3950_proxy::setName(const text_t &newname) {
  title=newname;
  meta["collectionname"]=newname;
}

void z3950_proxy::addcfgAbout(const text_t &lang, const text_t &abouttext) {
    about[lang]=abouttext;
}

bool z3950_proxy::getcfgAbout(const text_t &lang, text_t &abouttxt) {
  text_tmap::iterator it;
  it=about.find(lang);
  if (it==about.end()) return (false);
  abouttxt=((*it).second);
  return (true);
}


// now functions that actually talk over the tcp connection.

// create a tcp connection to the associated target. Currently, this will
// re-initialise if we are already connected.
bool z3950_proxy::connect() {
  text_t server_and_port;
  char *zserverinfo;

  server_and_port=info->host+":"+info->port;
  // remember that info.name is the database name

  z_initialize();
  char* serv_str=server_and_port.getcstr();
  char* name_str=info->name.getcstr();
  int retval=z_cmd_open(serv_str,name_str);
  delete []serv_str;
  delete []name_str;
  if (retval==1)
    // we got a connection error
    return false;

  // get initialisation response.
  z_getnextAPDU();
  zserverinfo=z_get_initResponse();
  if (zserverinfo!=NULL) {
    z_initstr.appendcstr(zserverinfo);
  }
  free(zserverinfo);

  connected=true;
  return true;
}

void z3950_proxy::parseQuery(const text_t &query, 
			      const int querytype,
			      const text_t &fields,
			      text_t &parsed_query) {
  /****** FIXME *****/
  // We need to format the query string into RPN - 
  // by just passing it like this, it will only work for simple queries.
  // This will require us to actually come up with a query syntax and 
  // a parser. For now, we'll just do an "AND" query for all terms
  // But look at Common Command Language (CCL) query syntax (ISO 8777).

  // need to remove " chars from the query. We should really tell the server
  // to do a phrase search on the terms that are between the "s, but we
  // can't (easily) tell if the server can do that or not, 
  // so we'll currently just do a query and then post-process. (not yet
  // implemented........)

  // we need to count number of terms separated by a space
  char *ptr=query.getcstr();
  int strlength=strlen(ptr);
  bool inword=false;
  int num_terms=0;


  for (int i=0;i<strlength;++i) {
    if (*(ptr+i)=='"') { // convert " to SPACE...
      *(ptr+i)=' '; 
    }
    if (*(ptr+i)!=' ') {
      if (inword==false) {
	inword=true;
	++num_terms;
      }
    }
    else { // ptr+i is a space
      inword=false;
      }
  }
  
  // set the field(s) to search on - main ones include:
  //  1016  => Any
  //  1     => (Personal) Name
  //  4     => Title
  //  21    => Subject Heading
  //  45    => Subject precis
  // Note I have no idea how these actually work - I think some servers
  // only have limited fields, and map all subject-type requests into that
  // subject field, etc.

  parsed_query="@attr 1=";
  if (fields==".author")
    parsed_query+="1 ";
  else if (fields==".title")
    parsed_query+="4 ";
  else // fields==".any"
    parsed_query+="1016 ";

  // querytype=1 => ranked/or,  =2 => boolean/and
  // append "@and" for each term after the first
  {
    char and_str[]="@and ";
    char or_str[]="@or ";
    char *q_type;
    if (querytype==1) q_type=or_str; else q_type=and_str;
    for (int i=1;i<num_terms;++i)
      parsed_query+=q_type;
    // append the actual query
    parsed_query+=ptr;
  }
  delete []ptr;
}

text_tarray *z3950_proxy::getrecordTitles(const text_t &query, 
					   const int querytype,
					   const text_t &fields,
					   int first, int count,
					   int *nummatches, comerror_t &err) {
  /* NOTE!!!!!! Because this code currently only works in cgi-bin mode,
     we only ever do one request. Therefore, it is CURRENTLY OK to store
     (cache) the retrieved titles, because if this function is ever called
     more than once, the arguments will be the same each time.
     (I think :)
  */

  char **c_str_titles; 
  int i;
  long last;

  if (gotsinglerecord==true) {
    /* If true, then this whole execution was done to retrieve a single
       document. Therefore, the list of titles of all records matching the
       query isn't actually required. It's just that for some reason our
       filter (z3950proto::filter) gets called at least 7 times, with the
       7th being a "QueryFilter" for some reason... */
    nummatches=0;
    return NULL; /* shouldn't really return NULL, but nummatches is checked
		    first (I hope) */
  }

  //  if (titles!=NULL) delete (titles);
  if (titles!=NULL) return titles;
  titles=new text_tarray;

  /* check if connected */
  if (connected==false) 
    if (connect()==false) {
      // we could not connect.
      err=protocolError;
      return (NULL);
    }
  

  text_t expanded_query=g_EmptyText;
  parseQuery(query,querytype,fields,expanded_query);

  char* query_str=expanded_query.getcstr();
  // following functions defined in yaz_zclient.c
  *nummatches=z_cmd_dosearch(query_str); // returns # found, -1 on err.
  delete []query_str;
  if (*nummatches<=0) {
    if (*nummatches==0) {
      // no matches
      return (NULL);
    } else  if (*nummatches==-1) {
      // prefix query error
      err=protocolError;
      return (NULL);
    }  else if (*nummatches==-2) {
      // sendsearchRequest not answered by searchResponse
      err=protocolError;
      return (NULL);
    }
  }
  // could do a sort eventually, eg on date, title, etc.
  // (non-existent function)    z_sort(field, asc|desc);
  /* min of (count, first + (*nummatches) ) */
  // z_getrecordTitles ( first, howmany )
  c_str_titles=z_getrecordTitles(first,min(count,*nummatches-first+1));
  if (c_str_titles==NULL) {
    // an error occurred. we need a logout/err as an arg
    return (NULL);
  }
  if (c_str_titles[0]==0) {
    // no matches.
    return (NULL);
  }
  last=(long)c_str_titles[0];
  for (i=1;i<=last;++i) {
    titles->push_back(c_str_titles[i]);
    free(c_str_titles[i]);
  }
  free(c_str_titles);
  return (titles);
  
}

bool z3950_proxy::getfullrecord(const text_t &query, 
				 const int querytype,
				 const text_t &fields,
				 const int ID, 
				 text_t &rettitle, 
				 text_t &rettext, comerror_t &err) {

  static char **c_str_titles=NULL;
  static char *fulltext=NULL;
  /* NOTE!!!!!! Because this code currently only works in cgi-bin mode,
     we only ever do one request. Therefore, it is CURRENTLY OK to store
     (cache) the retrieved titles, because if this function is ever called
     more than once, the arguments will be the same each time.
     (I think :)
  */

  gotsinglerecord=true; // well, not yet, but we've been called...

  if (connected==false) {
    if (connect()==false) {
      // error connecting...
      err=protocolError;
      return (false);
    }
    // since we have just re-connected, we need to do the
    // query again.

    text_t expanded_query=g_EmptyText;
    parseQuery(query,querytype,fields,expanded_query);

    char* query_str=expanded_query.getcstr();
    int returned=z_cmd_dosearch(query_str);
    delete []query_str;
    if (returned<=0) {
      // 0 => none.
      // <0 => error
      err=protocolError;
      return (false);
    }
  }

  if (c_str_titles==NULL)
    c_str_titles=z_getrecordTitles(ID,1); // check this return value.
  
  if (rettitle!="unneeded") {
    //int dummy;
    if (c_str_titles!=NULL && (long)c_str_titles[0]==1) {
      rettitle.setcstr(c_str_titles[1]); // and check this 
      //////      free (c_str_titles); - we want to "cache" it
    } else {
      // we didn't get something....
      rettitle="Nothing Returned...";
    }
  }

  if (fulltext==NULL)
    // get the text
    fulltext=z_getfullRecord(ID);

  if (rettext!="unneeded") {
    rettext.setcstr(fulltext);
  }
  return (true);
  }

text_t &z3950_proxy::getzAbout() {
  text_t zserverresp;

  // Assume we have not yet connected, so that must be done here.
  if (connected==true)
    return (z_initstr);
  
  // we need to create the tcp connection to the target (server)  
  //  z_initstr=new text_t;
  
  if (connect()==false) {
    z_initstr.setcstr("<H2>Server offline</H2>Error - could not connect to server <B>");
    z_initstr += info->host;
    z_initstr += "</B> on port ";
    z_initstr += info->port;
    z_initstr += "\n";
    return (z_initstr);
  }
  
  // z_initstr currently contains the target's response. We want to
  // PREPEND the following information.
  zserverresp=z_initstr;
  z_initstr="Internet server: <b>";
  z_initstr+=info->host;
  z_initstr+="</b> on port ";
  z_initstr+=info->port;
  z_initstr+=".<br>\n";
  z_initstr+=zserverresp;

  // should close /******* WHAT IF DOING A QUERY!??!?!? ********/
  // z_cmd_close(0);
  // connected=false;
  return (z_initstr);
}

