package com.k_int.srusolr.action;

import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;
import java.util.Map.Entry;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.xpath.CachedXPathAPI;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Text;
import org.z3950.zing.cql.CQLAndNode;
import org.z3950.zing.cql.CQLBooleanNode;
import org.z3950.zing.cql.CQLNode;
import org.z3950.zing.cql.CQLNotNode;
import org.z3950.zing.cql.CQLOrNode;
import org.z3950.zing.cql.CQLProxNode;
import org.z3950.zing.cql.CQLParseException;
import org.z3950.zing.cql.CQLParser;
import org.z3950.zing.cql.CQLTermNode;

import com.k_int.svc.identity.service.*;

import com.k_int.srusolr.util.ExternalResourceResolver;
import com.k_int.srusolr.util.InvalidSRUIndexException;
import com.k_int.srusolr.util.RecordBuilder;
import com.k_int.srusolr.util.SolrIndexConverter;
import com.opensymphony.xwork2.ActionSupport;
import com.k_int.svc.spatial.service.GazetteerPlace;

import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
import org.apache.solr.client.solrj.response.*;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.*;


public class SRUBaseAction extends ActionSupport implements ServletRequestAware, ApplicationContextAware {
  public static Log log = LogFactory.getLog(SRUBaseAction.class);
  private static final long serialVersionUID = 1L;
  private static final String SIM_KEY = "strict_index_mapping";
  private HttpServletRequest request;
  private ApplicationContext ctx;
  private static Map<String, String> cql_index_to_solr_term;
  private static Map<String, Class<RecordBuilder>> schema_to_builder;
  private static Map<String, SolrIndexConverter> solr_index_converters;
  private static Boolean strict_index_mapping;
  private String stylesheet;
  private String version = "1.1";
  private String host;
  private String port;
  private String database;
  private Map<String, List<String>> valid_indexes;
  private List<String> result_docs;
  private Integer last_record;
  private long record_count;
  private Integer first_record;
  private String query;
  private String record_schema;
  private String diagnostic_uri = "";
  private String diagnostic_details = "0";
  private String diagnostic_message = "";
  private String diagnostic_addinfo = "";
  private String resource_bundle_location;
  private String resource_bundle_base_name;
  private String add_info;
  private boolean secured = false;
  private IdentityService identity_service = null;
  private SolrServer solr_server = null;
  private List<FacetField> facets = null;
  private List<GazetteerPlace> locations = null;
  private List<AlternativeExpansionDTO> alternatives = null;
  private Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
  private static long concurrent_count = 0;
  private List array_element_exceptions = null;
  private boolean debug = false;
  private String solr_query = null;
  private ModifiableSolrParams params = null;
  private com.k_int.statslogging.iface.StatsLogger stats_logger = null;
  
  public void setServletRequest(HttpServletRequest request) { this.request = request; }
  public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx;         }
  public void setResourceBundleLocation(String resource_bundle_location) { this.resource_bundle_location = resource_bundle_location; }
  public void setResourceBundleBaseName(String resource_bundle_base_name) { this.resource_bundle_base_name = resource_bundle_base_name; }
  public void setSolrServer(SolrServer solr_server) { this.solr_server = solr_server; }
  public void setFacets(List<FacetField> facets) { this.facets = facets; }
  public void setLocations(List<GazetteerPlace> locations) { this.locations = locations; }
  public void setAlternatives(List<AlternativeExpansionDTO> alternatives) { this.alternatives = alternatives; }

  public void setArrayElementExceptions(List array_element_exceptions) { this.array_element_exceptions = array_element_exceptions; }
  public List getArrayElementExceptions() { return array_element_exceptions; }

  public void setStatsLogger(com.k_int.statslogging.iface.StatsLogger stats_logger) { this.stats_logger = stats_logger; }

  // shared
  public String getStylesheet()       { return stylesheet; }
  public String getVersion()          { return version;    }
  public String getSolrQuery()        { return solr_query;    }
  public boolean getDebug()           { return debug;    }
  public void setDebug(boolean debug) { this.debug = debug; }
  public ModifiableSolrParams getParams() { return params; }

  // explain
  public String getHost()       { return host;                 }
  public String getPort()       { return port;                 }
  public String getDatabase()   { return database;             }
  public Integer getIndexSize() { return valid_indexes.size(); }
  public Map<String, List<String>> getValidIndexes() { return valid_indexes; }
 
  // searchRetrieve
  public long getRecordCount()      { return record_count; }
  public List<String> getResultDocs() { return result_docs; }
  public Integer getResultDocsSize()  { return result_docs.size(); }
  public Integer getLastRecord()      { return last_record; }
  public boolean getMoreRecords()     { return (last_record<record_count && last_record>0); }
  public Integer getNextRecord()      { return last_record+1; }
  public Integer getFirstRecord()     { return first_record; }
  public String getSafeQuery()        { return query.replaceAll("\\&","&amp;").replaceAll("<","&lt;"); }
  public String getRecordSchema()     { return record_schema; }
  public String getAddInfo()          { return add_info; }
  public SolrServer getSolrServer()   { return solr_server; }
  public List<FacetField> getFacets()   { return facets; }
  public List<GazetteerPlace> getLocations()   { return locations; }
  public List<AlternativeExpansionDTO> getAlternatives()        { return alternatives; }
  
  // diagnostics
  public String getDiagnosticUri()     { return diagnostic_uri;     }
  public String getDiagnosticDetails() { return diagnostic_details; }
  public String getDiagnosticMessage() { return diagnostic_message; }
  public String getDiagnosticAddinfo() { return diagnostic_addinfo; }

  private String username = "anonymous";

  public void setIdentityService(IdentityService identity_service) {
    this.identity_service = identity_service;
  }

  public IdentityService getIdentityService() {
    return identity_service;
  }

  public boolean getSecured() {
    return secured;
  }

  public void setSecured(boolean secured) {
    this.secured = secured;
  }
  
  public SRUBaseAction() {}

  public void init() throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, MalformedURLException {
    if ( cql_index_to_solr_term == null ) {
      initStaticData();
    }
  }

  private void initStaticData() throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, MalformedURLException {
    log.debug("*******************");
    log.debug("SRUBaseAction::SRUBaseAction");
    log.debug("*******************");

    log.info("The default charset encoding in force is "+java.nio.charset.Charset.defaultCharset().name());
    log.info("-Dfile.encoding is "+System.getProperty( "file.encoding" ));

    ResourceBundle index_mapping = null;

    try {
      index_mapping = ExternalResourceResolver.resolve(resource_bundle_location, resource_bundle_base_name, "com.k_int.aggregator.home");
      log.debug("Loaded index mapping: "+index_mapping);
    } catch (MissingResourceException mre) {
      // Don't deal with this here. If the resource bundle cannot be found feedback at request-time.
      // Stack trace for information only, no functional impact of exception at this point
      mre.printStackTrace();
    }
    
    if (index_mapping!=null) {
      cql_index_to_solr_term = new HashMap<String, String>();
      Enumeration<String> mapping_keys = index_mapping.getKeys();
    
      while (mapping_keys.hasMoreElements()) {
        String mapping_key   = mapping_keys.nextElement();
        if (!mapping_key.equals(SIM_KEY)) {
          String index_mapping_value = index_mapping.getString(mapping_key);
          if (index_mapping_value==null || index_mapping_value.trim().equals((""))) index_mapping_value=null;
          cql_index_to_solr_term.put(mapping_key, index_mapping_value);
        }
      }
      
      String sim = null;
      try {
        sim = index_mapping.getString(SIM_KEY);
      } catch (MissingResourceException mre) {
        // ignore and default to true
      }
      strict_index_mapping = (sim!=null && sim.equals("false")) ? false : true;
      
    } else {
      log.warn("No index mappings available");
      strict_index_mapping   = true;
      cql_index_to_solr_term = null;
    }
    
    ResourceBundle solr_converter_mapping = null;
    try {
      solr_converter_mapping = ResourceBundle.getBundle("solr_converter_mapping");
      log.debug("Got solr convertor mapping: "+solr_converter_mapping);
    } catch (MissingResourceException mre) {
      // Don't deal with this here. If the resource bundle cannot be found feedback at request-time.
      log.warn("Problem loading solr converters");
    }
    
    if (solr_converter_mapping!=null) {
      solr_index_converters = new HashMap<String, SolrIndexConverter>();
      Enumeration<String> mapping_keys = solr_converter_mapping.getKeys();
    
      while (mapping_keys.hasMoreElements()) {
        String mapping_key   = mapping_keys.nextElement();
        String mapping_value = solr_converter_mapping.getString(mapping_key);
        
        if (mapping_value!=null) {
          log.debug("Mapping: "+mapping_key+", "+mapping_value);
          solr_index_converters.put(mapping_key, (SolrIndexConverter) ctx.getBean(mapping_value));
        }
      }
    } else {
      log.warn("No solr converters");
      solr_index_converters = null;
    }

    // no implementations created yet.
    schema_to_builder    = new HashMap<String, Class<RecordBuilder>>();
  }
  

  public String execute() {
    long start_time = System.currentTimeMillis();
    boolean success = true;

    log.debug("SRU request. Start="+start_time+" secured="+secured+", concurrent="+(++concurrent_count));
    if ( secured ) {
      String api_key = request.getParameter("apikey");
      if ( api_key != null ) {
        try {
          SystemUserDTO user = identity_service.authenticate(api_key);
          if ( user != null ) {
            log.debug("user authenticated");
            username = user.getIdentifier();
          }
          else {
            log.debug("Unable to validate API key");
            log.info("PERF[1] elapsed:"+( System.currentTimeMillis()-start_time)+", query:"+request.getQueryString()+", concurrent:"+(--concurrent_count)+", success:false");
            diagnostic_uri     = "1/3";
            diagnostic_details = "0";
            diagnostic_message = "Unable to validate API Key";
            success = false;
            return ERROR;
          }
        }
        catch ( com.k_int.svc.identity.service.IdentityServiceException ise ) {
          log.debug("Identity Service Exception",ise);
          log.info("PERF[1] elapsed:"+( System.currentTimeMillis()-start_time)+", query:"+request.getQueryString()+", concurrent:"+(--concurrent_count)+", success:false");
          diagnostic_uri     = "1/3";
          diagnostic_details = "0";
          diagnostic_message = "Unable to validate API Key";
          success = false;
          return ERROR;
        }
      }
      else {
        log.debug("No API Key");
        log.info("PERF[1] elapsed:"+( System.currentTimeMillis() - start_time )+", query:"+request.getQueryString()+", concurrent:"+(--concurrent_count)+", success:false");
        diagnostic_uri     = "1/3";
        diagnostic_details = "0";
        diagnostic_message = "SRU Server is configured in secure mode but no apikey property could be found in the request";
        success = false;
        return ERROR;
      }
      log.debug("Security checks completed - elapsed = "+ ( System.currentTimeMillis() - start_time ));
    }

    // Thats security out of the way, continue
    
    String result = null;    

    String operation = request.getParameter("operation");
    log.debug("operation="+operation);
              

    // If the request contains a stylesheet definition
    stylesheet = request.getParameter("stylesheet");
    if (stylesheet!=null && stylesheet.length()<1) stylesheet=null;

    // Operation specific switch here
    if (operation!=null) {
      if (operation.toLowerCase().startsWith("info:srw/operation/1/")) {
        if (operation.toLowerCase().equals("info:srw/operation/1/update")) {
          result = processRecordUpdate();
        }
      } else if (operation.toLowerCase().equalsIgnoreCase("searchRetrieve")) {
        result = processSearchRetrieve();
      }
    } 
    
    if (result==null) {
      result = processExplain();
    }
    
    long elapsed = System.currentTimeMillis() - start_time;
    log.info("PERF[1] elapsed:"+elapsed+", query:"+request.getQueryString()+", concurrent:"+(--concurrent_count)+", success:"+success+", debug="+debug);
    return result;
  }

  
  /**
   * Return an SRW Explain response.
   * @return void
   */
  private String processExplain() {
    log.debug("processExplain");
    String result = "explain";
    
    host     = request.getLocalName();
    port     = ""+request.getLocalPort();
    database = request.getContextPath()+request.getServletPath();
    
    valid_indexes = new HashMap<String, List<String>>();
    
    for (Entry<String, String> index : cql_index_to_solr_term.entrySet()) {
      log.debug("testing "+index);
      if (index.getValue()!=null && !index.getValue().trim().equals("")) {
        String set = null;
        String idx = null;
        if (index.getValue().indexOf(".")>-1) {
          set = index.getValue().substring(0, index.getValue().indexOf("."));
          idx = index.getValue().substring(index.getValue().indexOf(".")+1); 
          if (set!=null && idx!=null) {
            if (valid_indexes.get(set)==null) valid_indexes.put(set, new ArrayList<String>());
            valid_indexes.get(set).add(idx);
          }
          else {
            log.warn("Set or idx was null");
          }
        }
      }
      else {
        log.warn("Index name did not contain a dot - Don't know why thats assumed to be an error");
      }
    }

    /*

     This code tries to add entries to the explain service for converter properties

    if ( solr_index_converters != null ) {
      for (Entry<String, com.k_int.srusolr.util.SolrIndexConverter> index : solr_index_converters.entrySet()) {
        if (index.getValue()!=null && !index.getValue().trim().equals("")) {
          String set = null;
          String idx = null;
          if (index.getValue().indexOf(".")>-1) {
            set = index.getValue().substring(0, index.getValue().indexOf("."));
            idx = index.getValue().substring(index.getValue().indexOf(".")+1);
            if (set!=null && idx!=null) {
              if (valid_indexes.get(set)==null) valid_indexes.put(set, new ArrayList<String>());
              valid_indexes.get(set).add(idx);
            }
            else {
              log.warn("Set or idx was null");
            }
          }
        }
      }
    }
    */
    
    return result;
  }

  
  /**
   * handler for record upload.
   * Not yet implemented fully
   * @return void
   */
  @SuppressWarnings("unchecked")
  private String processRecordUpdate() {
    String result = "recordUpdate";
    /* Unused vars.
    String identifier = request.getParameter("identifier");
    String record_source = request.getParameter("record_source");
    String publisher_identity = request.getParameter("publisher_identity");
    String publisher_credentials = request.getParameter("publisher_credentials");
    String target_collection = request.getParameter("target_collection");
    */
    try {
      boolean isMultipart = ServletFileUpload.isMultipartContent(request);
      if ( isMultipart ) {
        ServletFileUpload upload = new ServletFileUpload();
        List<FileItem> items = (List<FileItem>)upload.parseRequest(request);

        Iterator<FileItem> iter = items.iterator();
        while (iter.hasNext()) {
          FileItem item = iter.next();
          log.debug("field name:"+item.getFieldName());
          log.debug("file name:"+item.getName());
          log.debug("In mem:"+item.isInMemory());
          if (item.isFormField()) {
            //String record = item.getString();
            // publish(identifier,record_source,publisher_identity,publisher_credentials,target_collection,record);
          } else {
            // processUploadedFile(item);
            //String record = item.getString();
            // publish(identifier,record_source,publisher_identity,publisher_credentials,target_collection,record);
          }
        }
      }
      else {
        log.debug("Not multipart");
      }
    } catch (FileUploadException fue) {
      fue.printStackTrace();
    }
    
    return result;
  }

  
  /**
   * Process search retrieve response.
   * @return void
   */
  private String processSearchRetrieve() {
    log.debug("processSearchRetrieve");
    String result = "searchRetrieve";
    boolean result_indicator = true;
    String query_type = "2";

    query         = request.getParameter("query");
    version       = request.getParameter("version");
    params = new ModifiableSolrParams();
    long sr_start = System.currentTimeMillis();

    if ( version == null ) {
      diagnostic_uri     = "1/7";
      diagnostic_details = "0";
      diagnostic_message = "Version parameter not present";
      return ERROR;
    }

    record_schema = request.getParameter("recordSchema")!=null ? request.getParameter("recordSchema") : "solr"; 
    String start_record    = request.getParameter("startRecord");
    String maximum_records = request.getParameter("maximumRecords");
    String sortKeys = request.getParameter("sortKeys");
    String sortBy = request.getParameter("sortBy");
    String sortQuery = "";
    
    log.debug("Sortng: "+sortBy+"/"+sortKeys);

    if (sortBy != null) {
      // Handle the SRU 1.2 sorting method
      sortQuery = createSortByQuery(sortBy.replaceAll("geo_distance","distance"));
    } else if (sortKeys != null ) {
      // Handle the SRU 1.1 sorting method
      sortQuery = createSortKeysQuery(sortKeys.replaceAll("geo_distance","distance"));
    }
    
    if ( !"".equals(sortQuery) ) {
      if ( sortBy != null ) {
        log.debug("Setting sort: "+sortQuery);
        params.set("sort", sortQuery);
      } else if ( sortKeys != null ) {
        log.debug("Setting sort: "+sortQuery);
        params.set("sort", sortQuery);
      }
    }
    
    first_record = start_record==null ? 1 : Integer.parseInt(start_record);
    
    // StringBuffer otherParameters = new StringBuffer();
    Enumeration attributes = request.getParameterNames();
    
    while (attributes.hasMoreElements()) {
      String attr = (String)attributes.nextElement();
      if ("x-kint-fq".equals(attr) || attr.startsWith("x-kint-facet")) {
        try{
          String[] values = request.getParameterValues(attr); 
          for (String v: values)
            // otherParameters.append("&" + attr.substring(7) + "=" + URLEncoder.encode(v, "UTF-8"));
            params.add(attr.substring(7), v);
        }
        catch (Exception e){}
      }
    }


    if ( first_record == 0 ) {
      // This is really error 61 - records First record position out of range For example, if the request matches 10 records, but the start position is greater than 10.
      diagnostic_uri     = "1/61";
      diagnostic_details = "0";
      diagnostic_message = "First record position out of range";
      return ERROR;
    }

    // 1. Parse the query
    try {
      CQLParser parser = new CQLParser();
      CQLNode cql_query = parser.parse(query);

      if ( cql_query instanceof CQLTermNode ) {
        String field = ((CQLTermNode) cql_query).getIndex();
        if ( field != null && field.equalsIgnoreCase("cql.serverChoice") ) {  // Freetext
          query_type = "0";
        }
        else {
          query_type = "1:"+field;
        }
      }
      else {
        query_type = "2"; // Complex query
      }

      log.debug("Parse complete");

      GeneralQueryModel gqm = new GeneralQueryModel();

      solr_query = cqlToSolr(cql_query,cql_query, gqm);

      String filter_query = null;

      if ( ( gqm.query_type != null ) && ( gqm.query_type.equals("geo") ) ) {
        if ( gqm.radius == null ) {
          if ( request.getParameter("radius") != null )
            gqm.radius = request.getParameter("radius");
          else
            gqm.radius = "10";
        }
        // otherParameters.append("&qt=geo&lat="+gqm.lat+"&long="+gqm.lng+"&radius="+gqm.radius);
        // params.set("qt", "geo");
        params.set("lat", gqm.lat);
        params.set("long", gqm.lng);
        params.set("radius", gqm.radius);
        filter_query = "{!spatial lat="+gqm.lat+" long="+gqm.lng+" radius="+gqm.radius+" unit=miles}";
      }

      int solr_start = first_record==0 ? 0 : first_record-1;

      params.set("q", filter_query != null ? filter_query+solr_query : solr_query);
      params.set("start", solr_start);
      params.set("rows", maximum_records);
      // now deal with otherParameters

      log.debug("Mapped SRU to Solr:");
      log.debug("     cql:"+cql_query);
      log.debug("    solr:"+solr_query);
      log.debug("     gqm:"+gqm);
      log.debug("     max:"+maximum_records);
      log.debug("    sort:"+sortQuery);
      log.debug("  params:"+params);

      diagnostic_addinfo = "Params:"+params.toString();

      QueryResponse response = solr_server.query(params);

      SolrDocumentList sdl = response.getResults();
      record_count = sdl.getNumFound();
      last_record = 0;

      result_docs = new ArrayList<String>();
      for ( java.util.Iterator results_iterator = sdl.iterator(); results_iterator.hasNext(); ) {
        SolrDocument doc = (SolrDocument) results_iterator.next();
        result_docs.add(SolrDocToString(doc));
      }
      last_record = first_record+result_docs.size()-1;

      facets = response.getFacetFields();
      log.debug("Facets: "+facets);

      if (gqm.disambiguation.containsKey("Location")) {
        log.debug("Location disambiguation found");
        locations = (ArrayList<GazetteerPlace>) gqm.disambiguation.get("Location");
        buildAlternatives(locations,cql_query);
      }

    } catch (CQLParseException cpe) {
      result_indicator = false;
      log.error("problem",cpe);
      diagnostic_uri     = "1/1";
      diagnostic_details = "0";
      if ( cpe.getMessage() != null )
        diagnostic_message = cpe.getMessage();
      else 
        diagnostic_message = cpe.toString();
      result = ERROR;
    } catch (InvalidSRUIndexException isie) {
      result_indicator = false;
      result = processDiagnostic(isie);
    } catch (Exception e) {
      log.error("problem : "+e.getMessage(), e);
      diagnostic_uri     = "1/1";
      diagnostic_details = "0";
      if ( e.getMessage() != null )
        diagnostic_message = e.getMessage();
      else 
        diagnostic_message = e.toString();

      result_indicator = false;

      result = ERROR;
    }
    finally {
      long sr_end = System.currentTimeMillis();
      if ( stats_logger != null ) {
        String diagnostic = diagnostic_uri+" "+diagnostic_message+" "+diagnostic_details;
        // Replace any , characters so as not to throw out any parsing
        diagnostic.replaceAll(",",";");
        stats_logger.event("Search", sr_start, sr_end, new String[] { username, query_type, result_indicator ? "0" : "1", ""+record_count, query, diagnostic, start_record} );
        // stats_logger.event("Search", sr_start, sr_end, new String[] { username, query_type, result_indicator ? "0" : "1", ""+record_count, query, diagnostic} );
      }
    }
    
    return result;
  }
  
  
  private String processDiagnostic(InvalidSRUIndexException isie) {
    diagnostic_uri     = "1/16";
    diagnostic_details = isie.getIndex();
    diagnostic_message = isie.getMessage();
    
    return ERROR;
  }

  private String cqlToSolr(CQLNode root, CQLNode cql_query, GeneralQueryModel gqm) throws UnsupportedEncodingException, InvalidSRUIndexException {
    if (cql_index_to_solr_term==null) {
      throw new NullPointerException("Index to Term mapping was null, resource bundle was probably not found.");
    }
    
    if (solr_index_converters==null) {
      throw new NullPointerException("SOLR converter mapping was null, resource bundle was probably not found.");
    }
    
    String solr_query = "";
    if (cql_query instanceof CQLBooleanNode) {

      if (cql_query instanceof CQLAndNode) {
        CQLAndNode and = (CQLAndNode) cql_query;
        solr_query += processBool(root, and.left, and.right, "AND", gqm);
      } else if (cql_query instanceof CQLNotNode) {
        CQLNotNode not = (CQLNotNode) cql_query;
        solr_query += processBool(root, not.left, not.right, "NOT", gqm);
      } else if (cql_query instanceof CQLOrNode) {
        CQLOrNode or = (CQLOrNode) cql_query;
        solr_query += processBool(root, or.left, or.right, "OR", gqm);
      } else {
        log.warn("Not processing CQLNode class of "+cql_query.getClass());
      }
    } else if (cql_query instanceof CQLTermNode) {
      solr_query = processTermNode((CQLTermNode) cql_query, gqm);
    } else {
      log.warn("Not processing CQLNode class of "+cql_query.getClass());
    } 
    return solr_query;
  }

  private String processTermNode(CQLTermNode term, GeneralQueryModel gqm) throws UnsupportedEncodingException, InvalidSRUIndexException {
    String solr_query = "";
    String rel       = term.getRelation().getBase();

    if (URLDecoder.decode(rel, "UTF-8").equals("=")) {
      rel = ":";
    }

    String index = term.getIndex();

    // default value for cql.serverChoice
    if (index.equals("cql.serverChoice")) {
        if ("*".equals(term.getTerm()))
          solr_query = "[* TO *]";
        else {
          if ( ( rel != null ) && ( rel.equalsIgnoreCase("adj") ) ) {
            solr_query = "(\""+term.getTerm()+"\")";
          }
          else {
            solr_query = term.getTerm();
          }
        }
    } else { 
      // check if the term is convertible with a class specified by mapping properties
      log.debug("Process index "+index);
      if (solr_index_converters.containsKey(index)) {
        log.debug("Index "+index+" is subject to a converter.. processing");
        try {
          SolrIndexConverter sic = solr_index_converters.get(index);
          Map<String, String> converted_indecies = sic.convert(term.getTerm(), gqm);
          if (converted_indecies.size()>0) {
            Iterator<Entry<String, String>> idx_it = converted_indecies.entrySet().iterator();
            while (idx_it.hasNext()) {
              Entry<String, String> idx_ent = idx_it.next();
              solr_query += buildQueryComponent(idx_ent.getKey(),rel,idx_ent.getValue());
              // solr_query += idx_ent.getKey()+rel+"(\""+idx_ent.getValue()+"\")";
              if (idx_it.hasNext()) solr_query+=" AND ";
            }
          }
        }
        catch ( Exception e ) {
          log.error("Problem in index converter",e);
          throw new InvalidSRUIndexException("Problem mapping \""+index+"\" - "+e.getMessage(), index );
        }
        finally {
        }
      } else {     
        // check if the term is allowable by mapping properties
        if (!cql_index_to_solr_term.containsKey(index) && strict_index_mapping) {
          throw new InvalidSRUIndexException("There is no mapping defined from SRU index of \""+index+"\" to a SOLR term. Valid indexes:"+cql_index_to_solr_term+", strict="+strict_index_mapping, index);
        } else if (cql_index_to_solr_term.get(index)==null) {
          throw new InvalidSRUIndexException("The mapping from SRU index of \""+index+"\" to a SOLR term is explicitly denied.", index); 
        } else {
          index = cql_index_to_solr_term.get(index); 
        }

        solr_query += buildQueryComponent(index,rel,term.getTerm());
      }
    }

    return solr_query;
  }

  public String processBool(CQLNode root, CQLNode lhs, CQLNode rhs, String op, GeneralQueryModel gqm) throws java.io.UnsupportedEncodingException,com.k_int.srusolr.util.InvalidSRUIndexException {
    String lhs_solr = cqlToSolr(root,lhs,gqm);
    String rhs_solr = cqlToSolr(root,rhs,gqm);
    if ( ( lhs_solr != null ) && ( rhs_solr != null ) && ( lhs_solr.length() > 0 ) && ( rhs_solr.length() > 0 ) ) {
      return "("+lhs_solr+") "+op+" ("+rhs_solr+")";
    }
    else if ( ( lhs_solr != null ) && ( lhs_solr.length() > 0 ) ) {
      return lhs_solr;
    }
    else if ( ( rhs_solr != null ) && ( rhs_solr.length() > 0 ) ) {
      return rhs_solr;
    }

    return null;
  }

  public static String buildQueryComponent(String index, String rel, String term) throws InvalidSRUIndexException {
  
    log.debug("buildQueryComponent "+index+" "+rel+" "+term);
    if ( rel != null ) {
      if ( rel.equalsIgnoreCase(">") ) {
        return index+":{"+term+" TO *}";
      }
      if ( rel.equalsIgnoreCase(">=") ) {
        return index+":["+term+" TO *]";
      }
      else if ( rel.equalsIgnoreCase("<") ) {
        return index+":{* TO "+term+"}";
      }
      else if ( rel.equalsIgnoreCase("<=") ) {
        return index+":[* TO "+term+"]";
      }
      else if ( rel.equalsIgnoreCase("adj") ) {
        return index+":(\""+term+"\")";
      }
      else if ( rel.equalsIgnoreCase("any") ) {
        String[] terms = term.split(" ");
        java.io.StringWriter sw = new java.io.StringWriter();
        sw.write(index+":(");
        for ( int i=0; i<terms.length; i++ ) {
          sw.write(terms[i]);
          if ( i+1 < terms.length )
            sw.write(" OR ");
        }
        sw.write(")");
        // return index+":(\""+term+"\")";
        return sw.toString();
      }
      else {
        if ("*".equals(term))
            term = "[* TO *]";
        return index+":("+term+")";
      }
    }
    else {
        if ("*".equals(term))
          term = "[* TO *]";
      return index+":"+"("+term+")";
    }
  }


  private String createSortKeysQuery(String sortBy) {

    log.debug("Create sort query: "+sortBy);

    if (sortBy == null || "".equals(sortBy.trim()))
      return "";
	  
    StringBuffer query   = new StringBuffer();
    String[] fields      = sortBy.split(" ");
    for (String f: fields) {
      if(query.length()>0)
        query.append(", ");

      String[] params = f.split(",");

      log.debug("Result of split: "+params);

      String sort = params[0];
      if (params.length >= 3 && "0".equals(params[2]))
        sort += " desc";
      else
        sort += " asc";

      query.append(sort);
    }

     // System.out.println("Sort query is "+query.toString());
    return query.toString();
  }

  private String createSortByQuery(String sortBy) {

    log.debug("Create sort query: "+sortBy);

    if (sortBy == null || "".equals(sortBy.trim()))
      return "";

    StringBuffer query   = new StringBuffer();
    String[] fields      = sortBy.split(" ");
    for (String f: fields) {
      if(query.length()>0)
        query.append(", ");

      String[] params = f.split("/");

      log.debug("Result of split: "+params);

      String sort = params[0];
      if (params.length >= 2 && "sort.descending".equals(params[1]))
        sort += " desc";
      else
        sort += " asc";

      query.append(sort);
    }

     // System.out.println("Sort query is "+query.toString());
    return query.toString();
  }


  private void buildAlternatives(List<GazetteerPlace> locations, CQLNode cql_query) {
    alternatives=new ArrayList<AlternativeExpansionDTO>();
    int counter=0;
    for (GazetteerPlace location : locations) {
      String title = location.getQualifiedPlaceName();
      CQLNode replNode = cloneCQLNode(cql_query);
      replaceTermForField(replNode, "Location", title);
      alternatives.add(new AlternativeExpansionDTO(title,replNode.toCQL(),++counter));
    }
  }

  
  private CQLNode replaceTerm(CQLNode queryNode, String term, String replacement) {
    if (queryNode instanceof CQLBooleanNode) {
      ((CQLBooleanNode) queryNode).right = replaceTerm( ((CQLBooleanNode) queryNode).right, term, replacement);
      ((CQLBooleanNode) queryNode).left = replaceTerm( ((CQLBooleanNode) queryNode).left, term, replacement);
    } else if (queryNode instanceof CQLTermNode) {
      CQLTermNode termNode = (CQLTermNode) queryNode;
      if (termNode.getTerm().equalsIgnoreCase(term)) {
        CQLTermNode replNode = new CQLTermNode(termNode.getIndex(), termNode.getRelation(), replacement);
        queryNode = replNode;
      }
    }
    return queryNode;
  }

  private CQLNode replaceTermForField(CQLNode queryNode, String field, String replacement) {
    if (queryNode instanceof CQLBooleanNode) {
      ((CQLBooleanNode) queryNode).right = replaceTermForField( ((CQLBooleanNode) queryNode).right, field, replacement);
      ((CQLBooleanNode) queryNode).left = replaceTermForField( ((CQLBooleanNode) queryNode).left, field, replacement);
    } else if (queryNode instanceof CQLTermNode) {
      CQLTermNode termNode = (CQLTermNode) queryNode;
      if (termNode.getIndex().equalsIgnoreCase(field)) {
        CQLTermNode replNode = new CQLTermNode(termNode.getIndex(), termNode.getRelation(), replacement);
        queryNode = replNode;
      }
    }
    return queryNode;
  }


  private CQLNode cloneCQLNode(CQLNode node) {
    if (node instanceof CQLAndNode) {
      return new CQLAndNode(cloneCQLNode(((CQLBooleanNode) node).left),
          cloneCQLNode(((CQLBooleanNode) node).right),
          ((CQLBooleanNode) node).ms);
    }
    if (node instanceof CQLOrNode) {
      return new CQLOrNode(cloneCQLNode(((CQLBooleanNode) node).left),
          cloneCQLNode(((CQLBooleanNode) node).right),
          ((CQLBooleanNode) node).ms);
    }
    if (node instanceof CQLNotNode) {
      return new CQLNotNode(cloneCQLNode(((CQLBooleanNode) node).left),
          cloneCQLNode(((CQLBooleanNode) node).right),
          ((CQLBooleanNode) node).ms);
    }
    if (node instanceof CQLProxNode) {
      return new CQLProxNode(cloneCQLNode(((CQLBooleanNode) node).left),
          cloneCQLNode(((CQLBooleanNode) node).right),
          ((CQLBooleanNode) node).ms);
    } else if (node instanceof CQLTermNode) {
      return new CQLTermNode(((CQLTermNode) node).getIndex(),
          ((CQLTermNode) node).getRelation(), ((CQLTermNode) node)
              .getTerm());
    } else {
      return null;
    }
  }

  private String SolrDocToString(SolrDocument doc) {
    StringBuilder sb = new StringBuilder();
    sb.append("<doc>");
    for (String key : doc.keySet()) {
      outputElem(sb, doc.get(key), key);
    }
    sb.append("</doc>");
    return sb.toString();
  }

  private void outputElem(StringBuilder sb, Object value, String name) {
    if ( ( value != null ) && ( name != null ) && ( ! name.startsWith("_tier") ) ) {

      // Push out a geo_distance str for backwards compatibility
      if ( name.equalsIgnoreCase("distance") ) {
        outputValue(sb,value,"geo_distance","str");
      }

      if ( value instanceof String ) {
        outputValue(sb,value,name,"str");
      }
      else if ( value instanceof java.lang.Boolean ) {
        outputValue(sb,value,name,"bool");
      }
      else if ( value instanceof java.lang.Integer ) {
        outputValue(sb,value,name,"int");
      }
      else if ( value instanceof java.util.Date ) {
        outputValue(sb,value,name,"date");
      }
      else if ( value instanceof java.lang.Double ) {
        outputValue(sb,value,name,"double");
      }
      else if ( value instanceof java.util.ArrayList ) {
        sb.append("<arr");
        if ( name != null ) {
          sb.append(" name=\"");
          sb.append(name);
          sb.append("\"");
        }
        sb.append(">");
        for ( Object elem : (java.util.ArrayList)value) {
          outputElem(sb, elem, null);
        }
        sb.append("</arr>");
      }
      else {
        sb.append("<!--");
        sb.append(value.getClass().getName());
        sb.append("-->");
      }
    }
  }

  private void outputValue(StringBuilder sb, Object value, String name, String element) {
    boolean do_arr_exception = array_element_exceptions.contains(name);

    if ( do_arr_exception ) {
      sb.append("<arr name=\"");
      sb.append(name);
      sb.append("\">");
      sb.append("<");
      sb.append(element);
      sb.append(">");
      sb.append(value.toString().replaceAll("\\&","&amp;").replaceAll("<","&lt;"));
      sb.append("</");
      sb.append(element);
      sb.append(">");
      sb.append("</arr>");
    }
    else {
      sb.append("<");
      sb.append(element);
      if ( name != null ) {
        sb.append(" name=\"");
        sb.append(name);
        sb.append("\"");
      }
      sb.append(">");
      sb.append(value.toString().replaceAll("\\&","&amp;").replaceAll("<","&lt;"));
      sb.append("</");
      sb.append(element);
      sb.append(">");
    }

    if ( do_arr_exception ) {
    }

  }

}
