/**********************************************************************
 *
 * sqlqueryaction.cpp -- 
 * Copyright (C) 2010  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 "sqlqueryaction.h"
#include "querytools.h"
#include "formattools.h"
#include "cgiutils.h"
#include "OIDtools.h"
#include "fileutil.h"
#include "text_t.h"
#include "historydb.h"
#include "htmlutils.h" // for html_safe in do_action
#include "gsdltools.h"
#include <stdlib.h> // for strtol
#include <assert.h>


sqlqueryaction::sqlqueryaction () 
  : basequeryaction()
{
  cgiarginfo arg_ainfo;

  // this action uses cgi variable "a"
  arg_ainfo.shortname = "a";
  arg_ainfo.longname = "action";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "sqlq";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "sqlqto" - 0 = not available, 1 = available
  arg_ainfo.shortname = "sqlqto";
  arg_ainfo.longname = "sqlquery type options";
  arg_ainfo.multiplechar = true; // can be empty or single char
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);
    
  // "sqlfqn" - number of fields in the query form
  arg_ainfo.shortname = "sqlfqn";
  arg_ainfo.longname = "sql form query num fields";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "4";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "sqlfqf" - the list of field names in the form query
  // - a comma separated list
  arg_ainfo.shortname = "sqlfqf";
  arg_ainfo.longname = "sql form query fields";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "sqlfqv" - the list of values in the form query
  // - a comma separated list
  arg_ainfo.shortname = "sqlfqv";
  arg_ainfo.longname = "sql form query values";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "sqlfqc" - the list of boolean operators in the form query
  // - a comma separated list
  arg_ainfo.shortname = "sqlfqc";
  arg_ainfo.longname = "sql form query combines";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // ****
  // Looks like 'multiplevalue' is left undefined (e.g. not set in
  // constructor).  This looks like a fairly major problem
  // in terms of leaving it undefine
  // => in the case of "sqlsf" leads to conflict over value it has

  // "sqlsf" - Sort field. Set to field to be used for sorting search reult
  // set 
  arg_ainfo.shortname = "sqlsf";
  arg_ainfo.longname = "sql sort field";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

   // "sqlfilter" - boolean flag to control whether "delete all" or
	//              "keep all" functionality is available
  arg_ainfo.shortname = "sqlfilter";
  arg_ainfo.longname = "whether sql filtering is active or not";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "0";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);
}

sqlqueryaction::~sqlqueryaction () 
{
}

void sqlqueryaction::configure (const text_t &key, const text_tarray &cfgline) {
  action::configure (key, cfgline);
}

bool sqlqueryaction::init (ostream &logout) 
{
  return basequeryaction::init (logout);
}


bool sqlqueryaction::check_cgiargs (cgiargsinfoclass &argsinfo,	
				    cgiargsclass &args, 
				    recptprotolistclass* protos, 
				    ostream &logout) 
{

  // check sqlfqn argument
  int arg_sqlfqn = args.getintarg("sqlfqn");
  if (arg_sqlfqn < -1) {
    logout << "Warning: \"sqlfqn\" argument less than -1 (" << arg_sqlfqn << ")\n";
    cgiarginfo *sqlfqninfo = argsinfo.getarginfo ("sqlfqn");
    if (sqlfqninfo != NULL) args["sqlfqn"] = sqlfqninfo->argdefault;
  }

  return basequeryaction::check_cgiargs(argsinfo,args,protos,logout);
}



void sqlqueryaction::define_external_macros (displayclass &disp, 
					     cgiargsclass &args, 
					     recptprotolistclass *protos, 
					     ostream &logout) 
{
  recptproto *collectproto = protos->getrecptproto (args["c"], logout);
  if (collectproto == NULL) return;

  ColInfoResponse_t *colinfo = recpt->get_collectinfo_ptr(collectproto, 
							  args["c"],
							  logout);
  comerror_t err;
  InfoFilterOptionsResponse_t response;
  InfoFilterOptionsRequest_t request;
  request.filterName = "SQLQueryFilter";
  
  collectproto->get_filteroptions (args["c"], request, response, err, logout);
  if (err == noError) {
    FilterOption_tmap::const_iterator it_dom;
    FilterOption_tmap::const_iterator it_ran;
    FilterOption_tmap::const_iterator end = response.filterOptions.end();

    // _sqlfqfselection_ field list
    it_dom = response.filterOptions.find("IndexFieldDomain");
    it_ran = response.filterOptions.find("IndexFieldRange");
    if ((it_dom!=end) && (it_ran!=end)) {

      set_option_macro ("sqlfqf",args["sqlfqf"], true,true, 
      			(*it_dom).second, (*it_ran).second, disp);

      if (args["b"] == "1") {
	// set the sort field macro
	set_sfselection_macro(args["sqlsf"], (*it_dom).second,(*it_ran).second,
			      disp);
      }
    }
  }


}


void sqlqueryaction::set_sfselection_macro(text_t current_value, 
					   const FilterOption_t &option_domain, 
					   const FilterOption_t &option_range, 
					   displayclass &disp) 
{

  // we need at least one option here to continue
  if (option_domain.validValues.size() < 1) { return; }
  if (option_range.validValues.size() < 1) { return; }

  text_t macrovalue = "<select name=\"sqlsf\">\n";
  
  if (current_value.empty()) current_value = "";
  
  text_tarray::const_iterator dom_thisvalue = option_domain.validValues.begin();
  text_tarray::const_iterator dom_endvalue = option_domain.validValues.end();

  text_tarray::const_iterator ran_thisvalue = option_range.validValues.begin();
  text_tarray::const_iterator ran_endvalue = option_range.validValues.end();

  int valid_count = 0;
  while ((dom_thisvalue != dom_endvalue) && (ran_thisvalue != ran_endvalue)) {

    if (*ran_thisvalue != "ZZ" && *ran_thisvalue != "TX") {
      ++valid_count;

      text_t option_val = *dom_thisvalue;
      option_val.replace(",","/");

      macrovalue += "<option value=\"" + option_val + "\"";
      if (current_value == *dom_thisvalue)
	macrovalue += " selected";
      macrovalue += ">_" + *ran_thisvalue + "_\n";
    }
    ++dom_thisvalue;
    ++ran_thisvalue;
  }
  macrovalue += "</select>";
  if (valid_count > 0) {
    disp.setmacro ("sqlsfselection", displayclass::defaultpackage, macrovalue);
  }  
}


void sqlqueryaction::get_formatted_query_string (text_t& formattedstring, 
					      bool segment,
					      cgiargsclass& args, 
					      displayclass& disp,
					      ostream& logout) 
{
  // A great many characters have meanings in SQL queries, including > and %, 
  // where % stands for a multi-char wildcard 
  // http://docs.oracle.com/cd/B10501_01/text.920/a96518/cqspcl.htm
  // Further, Greenstone's Advanced SQLite Search allows <, >, %, ' (rounded brackets and more) 
  // So it's best to url-decode all encoded cgi-args 
  // We do so here if normal text search or explicit query, and in the
  // parse_sql_query_form functions if dealing with forms.

  if (args["qt"]=="0" && args["sqlqto"] != "1") { // normal text search
    unsafe_cgi_arg("ALL", args["q"]);
    formattedstring = "SELECT DISTINCT docOID FROM document_metadata WHERE " + encodeForSQL(args["q"]);    
  }
  else if (args["qt"]=="1" || args["sqlqto"]=="1"){ // form search

    if (args["b"]=="1" && args["fqa"]=="1") { // explicit query
      formattedstring = args["q"];
      unsafe_cgi_arg("ALL", formattedstring);
    }
    else { // form search
      // *****
      // Consider making this its own method in basequeryaction
      // then make parse_.*reg_query_form virtual method in class

      if (args["b"]=="0") { // regular form
	parse_sqlreg_query_form(formattedstring, args, segment);
      }
      else  { // advanced form
	parse_sqladv_query_form(formattedstring, args, segment);
      }
      args["q"] = formattedstring;
      
      // reset the cgiargfqv macro - need to escape any quotes in it
      disp.setmacro("cgiargfqv", "query", escape_quotes(args["fqv"]));

      // also reset the _cgiargq_ macro as it has changed now
      disp.setmacro("cgiargq", displayclass::defaultpackage, html_safe(args["q"]));

      // reset the compressed options to include the q arg
      text_t compressedoptions = recpt->get_compressed_arg(args, logout);
      if (!compressedoptions.empty()) {
	disp.setmacro ("compressedoptions", displayclass::defaultpackage, dm_safe(compressedoptions));
	// need a decoded version of compressedoptions for use within forms
	// as browsers encode values from forms before sending to server
	// (e.g. %25 becomes %2525)
	decode_cgi_arg (compressedoptions); 
	if (args["w"] == "utf-8") { // if the encoding was utf-8, then compressed options was utf-8, and we need unicode.
    // if encoding wasn't utf-8, then compressed opotions may be screwed up, but seems to work for 8 bit encodings?
	  compressedoptions = to_uni(compressedoptions);
	}
	
	text_t dmacrovalue = dm_safe(compressedoptions);
	disp.setmacro ("decodedcompressedoptions", displayclass::defaultpackage, dmacrovalue);
	disp.setmacro ("decodedcompressedoptionsAttrsafe", displayclass::defaultpackage, encodeForHTMLAttr(dmacrovalue));
      }
    } // form search
  } // args["qt"]=1
  else {
    logout << "ERROR (sqlqueryaction::get_formatted_query_string): querytype not defined\n";
  }

}


// request.filterResultOptions and request.fields (if required) should
// be set from the calling code
void sqlqueryaction::set_queryfilter_options (FilterRequest_t &request, 
					      const text_t &querystring,
					      cgiargsclass &args) 
{
  request.filterName = "SQLQueryFilter";

  OptionValue_t option;

  option.name = "SQLWhere";
  option.value = querystring;
  request.filterOptions.push_back (option);

  set_sql_queryfilter_options(request, args);
}



// should this change for cross coll search??
bool sqlqueryaction::save_search_history (cgiargsclass &args, int numdocs, 
					  isapprox isApprox) 
{


  if (args["q"]=="") return true; // null query, dont save
  if (args["hs"]=="0") return true; // only save when submit query pressed
  
  // get userid
  text_t userid = args["z"];

  // the number of docs goes on the front of the query string
  text_t query = text_t(numdocs);
  if (isApprox==MoreThan) { // there were more docs found
    query.push_back('+');
  }
  query += "c="+args["c"];
  
  text_t qstring = args["q"];
  //text_t formattedquery =cgi_safe(qstring);
  //query += "&amp;q="+formattedquery;
  query += ";q="+qstring;
  bool display=false;
  int hd = args.getintarg("hd");
  if (hd > 0) display=true;
  if (set_history_info(userid, query, dbhome, display)) return true;
  else return false;

}

void sqlqueryaction::define_form_macros (displayclass &disp, 
					 cgiargsclass &args, 
					 recptprotolistclass *protos, 
					 ostream &logout) 
{
  // defines the following macros
  // _sqlregformlist_
  // _sqladvformlist_

  text_t form = "";
  int argsqlfqn = args.getintarg("sqlfqn");
  
  if (args["b"] == "1") { // advanced form
    form += "_firstsqladvformelement_\n";
    for (int i=1; i<argsqlfqn; ++i) {      
	form += "_sqladvformelement_\n";
    }
    disp.setmacro("sqladvformlist", "query", form);
  }
  else { // simple form
    for (int i=0; i<argsqlfqn; ++i) {
      form += "_sqlregformelement_\n"; 
    }
    disp.setmacro("sqlregformlist", "query", form);
  }
  
}


bool sqlqueryaction::do_action (cgiargsclass &args, 
				recptprotolistclass *protos, 
				browsermapclass *browsers, displayclass &disp, 
				outconvertclass &outconvert, ostream &textout, 
				ostream &logout) 
{
  return search_single_collection (args, args["c"], protos, browsers, disp, 
				   outconvert, textout, logout);
}



