/*
 *    GS2MGSearch.java
 *    Copyright (C) 2002 New Zealand Digital Library, http://www.nzdl.org
 *
 *    This program is free software; you can redistribute it and/or modify
 *   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.
 */
package org.greenstone.gsdl3.service;


// Greenstone classes
import org.greenstone.mg.*;
import org.greenstone.gsdl3.util.*;

// XML classes
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

// java
import java.util.Vector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.io.File;
import java.io.Serializable;

import org.apache.log4j.*;


public class GS2MGSearch
extends AbstractGS2TextSearch {
    
    protected static MGSearchWrapper mg_src = null;
    
    static Logger logger = Logger.getLogger (org.greenstone.gsdl3.service.GS2MGSearch.class.getName ());
    
    
    /** constructor */
    public GS2MGSearch () {
        does_chunking = true;
	if(this.mg_src == null){
	    this.mg_src = new MGSearchWrapper ();
        }
    }
    public void cleanUp () {
        super.cleanUp ();
        this.mg_src.unloadIndexData ();
    }
    
    /** configure this service */
    public boolean configure (Element info, Element extra_info) {
        if (!super.configure (info, extra_info)){
            return false;
        }
	// internally mg uses 50, so set this here
	paramDefaults.put(MAXDOCS_PARAM, "50");
        this.mg_src.setMaxNumeric (this.maxnumeric);
        return true;
    }
    
    
    
    /** do the actual query */
    protected Element processTextQuery (Element request) {
        synchronized(this.mg_src){
        // Create a new (empty) result message ('doc' is in ServiceRack.java)
	  Document result_doc = XMLConverter.newDOM();
        Element result = result_doc.createElement (GSXML.RESPONSE_ELEM);
        result.setAttribute (GSXML.FROM_ATT, QUERY_SERVICE);
        result.setAttribute (GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);
        
        // Get the parameters of the request
        Element param_list = (Element) GSXML.getChildByTagName (request, GSXML.PARAM_ELEM+GSXML.LIST_MODIFIER);
        if (param_list == null) {
            logger.error ("TextQuery request had no paramList.");
            return result;  // Return the empty result
        }
        
        // Process the request parameters
        HashMap<String, Serializable> params = GSXML.extractParams (param_list, false);
        
        // Make sure a query has been specified
        String query = (String) params.get (QUERY_PARAM);
        if (query == null || query.equals ("")) {
            return result;  // Return the empty result
        }
        
        // If an index hasn't been specified, use the default
        String index = (String) params.get (INDEX_PARAM);
        if (index == null) {
            index = this.default_index;
        }
        
        // If a subcollection index has been specified, use it
        String indexSub = (String) params.get (INDEX_SUBCOLLECTION_PARAM);
        if (indexSub != null) {
            index += indexSub;
        }
        else{
            if (!this.default_index_subcollection.equals ("")){
                index += this.default_index_subcollection;
            }
        }
        
        // If a subcollection index has been specified, use it
        String indexLang = (String) params.get (INDEX_LANGUAGE_PARAM);
        if (indexLang != null) {
            index += indexLang;
        }
        else{
            if (!this.default_index_language.equals ("")){
                index += this.default_index_language;
            }
        }
        
        // The location of the MG index and text files
        String basedir = GSFile.collectionBaseDir (this.site_home, this.cluster_name) +  File.separatorChar;  // Needed for MG
        String textdir = GSFile.collectionTextPath (this.index_stem);
        String indexpath = GSFile.collectionIndexPath (this.index_stem, index);
        this.mg_src.setIndex (indexpath);
        
        // set the mg query parameters to the values the user has specified
        setStandardQueryParams (params);
        this.mg_src.runQuery (basedir, textdir, query);
        MGQueryResult mqr = this.mg_src.getQueryResult ();
        if (mqr.isClear ()) {
            // something has gone wrong
            GSXML.addError (result, "Couldn't query the mg database", GSXML.ERROR_TYPE_SYSTEM);
            return result;
        }
        long totalDocs = mqr.getTotalDocs ();
        
        // Get the docnums out, and convert to HASH ids
        Vector docs = mqr.getDocs ();
        if (docs.size () == 0) {
            logger.error ("No results found...\n");
        }
        
        // Create a metadata list to store information about the query results
        Element metadata_list = result_doc.createElement (GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER);
        result.appendChild (metadata_list);
        
        // Add a metadata element specifying the number of matching documents
        // because teh total number is just the number returned, use numDocsReturned, not numDocsMatched
        GSXML.addMetadata (metadata_list, "numDocsReturned", ""+totalDocs);
        // add a metadata item to specify what actual query was done - eg if stuff was stripped out etc. and then we can use the query later, cos we don't know which parameter was the query
        GSXML.addMetadata (metadata_list, "query", query);
        
        if (docs.size () > 0) {
            // Create a document list to store the matching documents, and add them
            Element document_list = result_doc.createElement (GSXML.DOC_NODE_ELEM+GSXML.LIST_MODIFIER);
            result.appendChild (document_list);
            for (int d = 0; d < docs.size (); d++) {
                long docnum = ((MGDocInfo) docs.elementAt (d)).num_;
                float rank = ((MGDocInfo) docs.elementAt (d)).rank_;
                String doc_id = internalNum2OID (docnum);
                Element doc_node = createDocNode (result_doc, doc_id, Float.toString (rank));
                document_list.appendChild (doc_node);
            }
        }
        
        // Create a term list to store the term information, and add it
        Element term_list = result_doc.createElement (GSXML.TERM_ELEM+GSXML.LIST_MODIFIER);
        result.appendChild (term_list);
        Vector terms = mqr.getTerms ();
        for (int t = 0; t < terms.size (); t++) {
            MGTermInfo term_info = (MGTermInfo) terms.get (t);
            
            String term = term_info.term_;
            int stem_method = term_info.stem_method_;
            Vector equiv_terms = term_info.equiv_terms_;
            
            Element term_elem = result_doc.createElement (GSXML.TERM_ELEM);
            term_elem.setAttribute (GSXML.NAME_ATT, term);
            term_elem.setAttribute (STEM_ATT, "" + stem_method);
            
            Element equiv_term_list = result_doc.createElement (EQUIV_TERM_ELEM+GSXML.LIST_MODIFIER);
            term_elem.appendChild (equiv_term_list);
            
            long total_term_freq = 0;
            for (int et = 0; et < equiv_terms.size (); et++) {
                MGEquivTermInfo equiv_term_info = (MGEquivTermInfo) equiv_terms.get (et);
                
                Element equiv_term_elem = result_doc.createElement (GSXML.TERM_ELEM);
                equiv_term_elem.setAttribute (GSXML.NAME_ATT, equiv_term_info.term_);
                equiv_term_elem.setAttribute (NUM_DOCS_MATCH_ATT, "" + equiv_term_info.match_docs_);
                equiv_term_elem.setAttribute (FREQ_ATT, "" + equiv_term_info.term_freq_);
                equiv_term_list.appendChild (equiv_term_elem);
                
                total_term_freq += equiv_term_info.term_freq_;
            }
            
            term_elem.setAttribute (FREQ_ATT, "" + total_term_freq);
            term_list.appendChild (term_elem);
        }
        return result;
	}//end of synchronized
    }
    
    // should probably use a list rather than map
    protected boolean setStandardQueryParams(HashMap<String, Serializable> params)
    {
	// set the default settings that gs uses
      this.mg_src.setMaxDocs(Integer.parseInt(paramDefaults.get(MAXDOCS_PARAM)));
	this.mg_src.setReturnTerms(true);
	this.mg_src.setCase(paramDefaults.get(CASE_PARAM).equals(BOOLEAN_PARAM_ON) ? true : false);
	this.mg_src.setStem(paramDefaults.get(STEM_PARAM).equals(BOOLEAN_PARAM_ON) ? true : false);	
	Set entries = params.entrySet();
	Iterator i = entries.iterator();
	while (i.hasNext()) {
	    Map.Entry m = (Map.Entry)i.next();
	    String name = (String)m.getKey();
	    String value = (String)m.getValue();

	    if (name.equals(CASE_PARAM) && this.does_case) {
		boolean val = (value.equals(BOOLEAN_PARAM_ON) ? true : false);
		this.mg_src.setCase(val);
	    }
	    else if (name.equals(STEM_PARAM) && this.does_stem) {
		boolean val = (value.equals(BOOLEAN_PARAM_ON) ? true : false);
		this.mg_src.setStem(val);
	    }
	    else if (name.equals(MATCH_PARAM)) {
		int mode = (value.equals(MATCH_PARAM_ALL) ? 1 : 0);
		this.mg_src.setMatchMode(mode);
	    }
	    else if (name.equals(MAXDOCS_PARAM)) {
		int docs = Integer.parseInt(value);
		this.mg_src.setMaxDocs(docs);
	    } // ignore any others
	}
	return true;
    }
    
    
}


