/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
package fr.gouv.culture.sdx.thesaurus;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.cocoon.ProcessingException;
import org.apache.excalibur.source.impl.URLSource;
import org.apache.excalibur.xml.sax.SAXParser;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import fr.gouv.culture.sdx.documentbase.LuceneDocumentBase;
import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.repository.Repository;
import fr.gouv.culture.sdx.search.lucene.query.Results;
import fr.gouv.culture.sdx.utils.Identifiable;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.ContextKeys;
import fr.gouv.culture.sdx.utils.logging.LoggingUtils;


/**
 * A lucene implementation of a thesaurus.
 *
 * This class uses a Lucene index where all concepts are stored
 * as one document, and all relations of a document are stored
 * as fields for the document.
 */
public class LuceneThesaurus extends LuceneDocumentBase implements SDXThesaurus {

	protected int defaultDepth = 0;
	protected int[] defaultRelations;
	protected URLSource source = null;
	/**Two field names*/
	public static final String FIELD_NAME_FTERM = "f" + Concept.TERM;
	public static final String FIELD_NAME_FUF = "f" + Concept.RELATION_USED_FOR;


	/**Overrides parent method and allows for configuration element &lt;sdx:thesaurus&gt;
	 * set's the path for the this thesaurus/document base
	 *
	 * @param configuration
	 * @throws ConfigurationException
	 */
	protected void configureBase(Configuration configuration) throws ConfigurationException {
		String dbDirPath = "";
		//at this point, we have a <sdx:documentBase> configuration object...
		if (configuration.getName().equals(Utilities.getElementName(SDXThesaurus.CLASS_NAME_SUFFIX))) {
			try {
				//setting the id from the configuration file
				setId(configuration.getAttribute(Identifiable.ConfigurationNode.ID));
			} catch (SDXException sdxE) {
				throw new ConfigurationException(sdxE.getMessage(), sdxE);
			}
			if (!Utilities.checkString(getId())) {
				String[] args = new String[1];
				args[0] = configuration.getLocation();
				SDXException sdxE = new SDXException(SDXExceptionCode.ERROR_INVALID_ID_VALUE, args);
				throw new ConfigurationException(sdxE.getMessage(), sdxE);
			}
			//getting the path for the location where this document base will reside
			dbDirPath = Utilities.getStringFromContext(ContextKeys.SDX.Application.THESAURI_DIRECTORY_PATH, super.getContext()) + getId() + File.separator;
		}
		//... otherwise we have a unrecognized element
		else {
			String[] args = new String[1];
			args[0] = configuration.getName();
			//not passing a super.getLog() should be logged later up the stack
			//TODOException: create a better message here
			SDXException sdxE = new SDXException(SDXExceptionCode.ERROR_UNRECOGNIZED_DOCUMENTBASE_CONFIG, args);
			throw new ConfigurationException(sdxE.getMessage(), sdxE);
		}

		//setting property : the path where this document base resides
		super.getContext().put(ContextKeys.SDX.DocumentBase.DIRECTORY_PATH, dbDirPath);
	}

	protected Configuration[] getRepositoryConfigurationList(Configuration configuration) throws ConfigurationException {
		//at this point, we COULD have a <sdx:repositories> element containing a list of repositories, but it is not necessary
		//intializing the array
		String elementName = Utilities.getElementName(Repository.CLASS_NAME_SUFFIX);
		Configuration[] repoConfList = new Configuration[configuration.getChild(Repository.ConfigurationNode.REPOSITORIES, true).getChildren(elementName).length];
		//getting an array of configuration objects from each of the <sdx:repository> subElements of <sdx:repositories> element
		repoConfList = configuration.getChild(Repository.ConfigurationNode.REPOSITORIES, true).getChildren(elementName);
		return repoConfList;

	}
	//Questions:
	//What analyzer to use?
	//What QueryParser to use for searching?
	//

	/**If this thesaurus is built from a source file we call toSAX on that source
	 * otherwise we call toSAX on the super class
	 *
	 * @param handler The handler to feed with events
	 * @throws SAXException
	 */
	/*public void toSAX(ContentHandler handler) throws SAXException {
		if (this.source != null) {
			//EmbeddedXMLPipe exp = new EmbeddedXMLPipe(handler);
			try {
				//this.source.toSAX(exp);
				SourceUtil.toSAX(this.source, handler);
			} catch (ProcessingException e) {
				throw new SAXException(e);
			}
			catch (IOException e) {
				throw new SAXException(e);
			}
		} else
			super.toSAX(handler);
	}*/

	/**
	 * Returns the number of terms in the thesaurus.
	 */
	public long size() {
		return super.getLuceneIndex().size();
	}

	/**
	 *
	 * Searches for concepts.
	 *
	 * @param   query       The concept term, already analyzed by an appropriate analyzer.
	 */
	public Concept[] search(String query) throws SDXException {
		if (Utilities.checkString(query)) {
			/*TODO: what other relation type to search, how can we make this search more broad so
			how should we be searching here i assume just like
			a normal search from outside sdx*/
			//well i think we need to search all fields
			//searchlocations, simple query, etc.
			/*
				SearchLocations sLocs = new SearchLocations();
				sLocs.enableLogging(super.getLog());
				sLocs.setUp(super.getLuceneIndex());

				ComplexQuery cq = new ComplexQuery();
				cq.enableLogging(super.getLog());
				cq.setUp(sLocs, QueryParser.OPERATOR_OR); */


			BooleanQuery bq = Utilities.newBooleanQuery(); //TODO? should we be searching more than just "uf" and "term" fields
			TermQuery tqTerm = new TermQuery(new Term(Concept.TERM, query));
			TermQuery tqFterm = new TermQuery(new Term(FIELD_NAME_FTERM, query));
			TermQuery tqUf = new TermQuery(new Term(Concept.RELATION_USED_FOR, query));
			TermQuery tqFuf = new TermQuery(new Term(FIELD_NAME_FUF, query));

			// MAJ Lucene 2.1.0
//			bq.add(tqTerm, false, false);
//			bq.add(tqFterm, false, false);
//			bq.add(tqUf, false, false);
//			bq.add(tqFuf, false, false);
			bq.add(tqTerm, BooleanClause.Occur.SHOULD);
			bq.add(tqFterm, BooleanClause.Occur.SHOULD);
			bq.add(tqUf, BooleanClause.Occur.SHOULD);
			bq.add(tqFuf, BooleanClause.Occur.SHOULD);
			/*
			 Enumeration fields = _fieldList.getFields();
			 if (fields != null) {
				 //bq = new BooleanQuery();
				 while (fields.hasMoreElements()) {
					 String fieldName = "";
					 Field f = (Field) fields.nextElement();
					 if (f != null)
						 fieldName = f.getCode();
					 if (Utilities.checkString(fieldName)) {
						 /*
						 SimpleQuery sqQueryText = new SimpleQuery();
						 sqQueryText.enableLogging(super.getLog());
						 sqQueryText.setUp(sLocs, fieldName, query);
						 cq.addComponent(sqQueryText);

						 SimpleQuery sqQueryField = new SimpleQuery();
						 sqQueryField.enableLogging(super.getLog());
						 sqQueryField.setUp(sLocs, fieldName, "|" + query + "|");
						 cq.addComponent(sqQueryField);


						 Term term = new Term(fieldName, query);
						 bq.add(new TermQuery(term), false, false);
					 }

				 }
			 }
		 */

			/*
			//building a query with the default field "term"
			SimpleQuery sqTerm = new SimpleQuery();
			sqTerm.enableLogging(super.getLog());
			sqTerm.setUp(sLocs, Concept.TERM, query);

			//building a query on the "uf" field
			SimpleQuery sqUf = new SimpleQuery();
			sqUf.enableLogging(super.getLog());
			sqUf.setUp(sLocs, Concept.RELATION_USED_FOR, query);
			*/
			/*
			//building a query on the "sn" field
			SimpleQuery sqSn = new SimpleQuery();
			sqSn.enableLogging(super.getLog());
			sqSn.setUp(sLocs, Concept.RELATION_SCOPE_NOTE, query);
			*/

			/*
			//combining the queries
			cq.addComponent(sqTerm);
			cq.addComponent(sqUf);
			//cq.addComponent(sqSn);
			//preparing the query
			cq.prepare();
			*/

			//Query lq = cq.getLuceneQuery();
			Hits hits = null;
			if (bq != null)
				hits = super.getLuceneIndex().search(bq);
			if (hits == null || hits.length() == 0)
				return null;
			else {
				ArrayList matches = new ArrayList();
				for (int i = 0; i < hits.length(); i++) {
					Document hitDoc = null;
					try {
						hitDoc = hits.doc(i);
						if (hitDoc != null)
							matches.add(getConcept(hitDoc));
					} catch (IOException e) {
						String[] args = new String[2];
						args[0] = super.getLuceneIndex().getIndexPath();
						args[1] = e.getMessage();
						//we log this message and allow the loop to continue
						new SDXException(super.getLog(), SDXExceptionCode.ERROR_LUCENE_RETRIEVE_DOCUMENT, args, e);
					}
				}
				Concept[] concepts = new Concept[matches.size()];
				concepts = (Concept[]) matches.toArray(concepts);
				return concepts;
			}
		} else
			return null;
	}

	//TODO:what other utility methods to put here
	public Results expandQuery(fr.gouv.culture.sdx.search.lucene.query.Query query) throws SDXException {
		return expandQuery(query, null, getDefaultRelations(), getDefaultDepth(), null);
	}

	public Results expandQuery(fr.gouv.culture.sdx.search.lucene.query.Query query, String fieldName) throws SDXException {
		return expandQuery(query, fieldName, getDefaultRelations(), getDefaultDepth());
	}


	public Results expandQuery(fr.gouv.culture.sdx.search.lucene.query.Query query, String fieldName, int relation, int depth) throws SDXException {
		int[] relations = new int[1];
		relations[0] = relation;
		return expandQuery(query, fieldName, relations, depth);
	}

	public Results expandQuery(fr.gouv.culture.sdx.search.lucene.query.Query query, String fieldName, int[] relations, int depth) throws SDXException {
		return expandQuery(query, fieldName, relations, depth, null);
	}

	public Results expandQuery(fr.gouv.culture.sdx.search.lucene.query.Query query, String fieldName, int[] relations, int depth, String[] langs) throws SDXException {
		Results expandedResults = null;
		if (query != null) {
			int[] l_relations = relations;
			if (l_relations == null) getDefaultRelations();
			int l_depth = depth;
			if (l_depth < 0) getDefaultDepth();
			LuceneQueryExpander lqe = new LuceneQueryExpander();
			lqe.enableLogging(super.getLog());
			lqe.setUp(this);
			Query newLQ = lqe.expandQuery(query, fieldName, l_relations, l_depth, langs);
			query.setLuceneQuery(newLQ);
			query.prepare();
			expandedResults = query.execute();
		}
		return expandedResults;

	}

	/**
	 * Adds a document.
	 *
	 * @param   concept     The document to add.
	 */
	public void addConcept(Concept concept) throws SDXException {
		try {
			super.index(concept, null, null, null);
		} catch (SAXException e) {
			//no handler so no exception should occure here
		} catch (ProcessingException e) {
			//no handler so no exception should occure here
		}
	}

	public void addConcepts(Concept[] concepts) throws SDXException {
		try {
			super.index(concepts, null, null, null);
		} catch (SAXException e) {
			//no handler so no exception should occure here
		} catch (ProcessingException e) {
			//no handler so no exception should occure here
		}
	}

	/**
	 * Removes a concept document with the given id and any sub concepts.
	 *
	 * @param   concept      The document.
	 */
	public void deleteConcept(Concept concept) throws SDXException {
		try {

			super.delete(concept, null);
		} catch (SAXException e) {
			//no handler so no exception should occure here
		} catch (ProcessingException e) {
			//no handler so no exception should occure here
		}

	}

	/**
	 * Removes a concept document with the given id and any sub concepts.
	 *
	 * @param   concepts      The documents.
	 */
	public void deleteConcepts(Concept[] concepts) throws SDXException {
		try {

			super.delete(concepts, null);
		} catch (SAXException e) {
			//no handler so no exception should occure here
		} catch (ProcessingException e) {
			//no handler so no exception should occure here
		}

	}


	// FIXME : private method never used. [MP]
	private Concept getConceptByField(String fieldName, String value) throws SDXException {
		if (Utilities.checkString(fieldName) && Utilities.checkString(value)) {
			//TODO?:should term be analyzed before be passed to this method or not?
			TermQuery tq = new TermQuery(new Term(fieldName, value));
			Hits hits = super.getLuceneIndex().search(tq);
			if (hits == null || hits.length() == 0)
				return null;
			else {
				try {
					Document hitDoc = hits.doc(0);
					if (hitDoc != null) {
						return getConcept(hitDoc);
						/*LuceneConcept concept = getConcept(hitDoc);
						if (concept.getValue() != null && !concept.getValue().equals(value))
							return concept;
						//otherwise
						LuceneConcept hack = new LuceneConcept();
						hack.setId(value);
						hack.setValue(value);
						return hack;*/
					} else
						return null;
				} catch (IOException e) {
					String[] args = new String[2];
					args[0] = super.getLuceneIndex().getIndexPath();
					args[1] = e.getMessage();
					throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LUCENE_RETRIEVE_DOCUMENT, args, e);
				}
			}
		} else
			return null;
	}

	public Concept[] getRelations(Concept[] concepts) throws SDXException {
		return getRelations(concepts, defaultRelations, defaultDepth);
	}

	public Concept[] getRelations(Concept concept) throws SDXException {
		return getRelations(concept, defaultRelations, defaultDepth);
	}

	/**
	 * Returns concepts related to a document.
	 *
	 * @param   concept     The document.
	 * @param   relation        The relation to use.
	 */
	public Concept[] getRelations(Concept concept, int relation, int depth) throws SDXException {
		//getting the relations
		//relations could be actual terms or an id so we search on bot
		Hashtable rConcepts = new Hashtable();
		String[] relations = concept.getRelations(getRelationTypeAbbreviation(relation));
		if (relations != null) {
			for (int i = 0; i < relations.length; i++) {
				Concept rConcept = null;
				rConcept = getConceptById(relations[i]);
				if (rConcept == null)
					rConcept = getConceptByName(relations[i]);
				if (rConcept != null) {
					LinkedList childConcepts = new LinkedList();
					Concept[] child = new Concept[1];
					child[0] = rConcept;
					childConcepts.add(child);
					//looping on the depth
					for (int j = 0; j < depth; j++) {
						Concept[] childrenRelations = null;
						childrenRelations = getRelations((Concept[]) childConcepts.getLast(), relation, 0);
						if (childrenRelations != null)
							childConcepts.add(childrenRelations);
					}
					if (childConcepts.size() > 0) {
						Iterator it = childConcepts.iterator();
						while (it.hasNext()) {
							Concept[] set = (Concept[]) it.next();
							for (int k = 0; k < set.length; k++)
									//same concept, ie same id will be overwritten in the hashtable
								rConcepts.put(set[k].getId(), set[k]);
						}
					}
				}
			}
		}
		if (rConcepts.size() > 0)
			return (Concept[]) rConcepts.values().toArray(new Concept[0]);

		else
			return null;
	}

	/**
	 * Returns concepts related to a document.
	 *
	 * @param   searchTerm  The search term which was used to find the concept,
	 *                      in the case that the searchTerm and related concept value are
	 *                      equal the relation value will be returned in as Concept useful
	 *                      for relation fields which are of relation word and would be found by a search(searchTerm)
	 * @param   concept     The document.
	 * @param   relation        The relation to use.
	 * @param   depth       The levels up or down indicating the extent to which the relation search should be executed
	 *
	 */
	public Concept[] getRelations(String searchTerm, Concept concept, int relation, int depth) throws SDXException {
		if (concept == null) return null;
		Hashtable rConcepts = new Hashtable();
		//getting the relations
		String[] relations = concept.getRelations(getRelationTypeAbbreviation(relation));
		if (relations != null) {
			for (int i = 0; i < relations.length; i++) {
				//relations could be actual terms or an id so we search on bot
				Concept rConcept = null;
				rConcept = getConceptById(relations[i]);
				if (rConcept == null)
					rConcept = getConceptByName(relations[i]);
				if (rConcept == null && relations[i].equalsIgnoreCase(searchTerm))
					rConcept = concept;
				if (rConcept == null && concept.getValue().equalsIgnoreCase(searchTerm)) {
					LuceneConcept hack = new LuceneConcept();
					hack.setId(relations[i]);
					hack.setValue(relations[i]);
					rConcept = hack;
				}
				if (rConcept != null) {
					//adding the related concept
//						  rConcepts.add(rConcept);
					//looping on the depth
					LinkedList childConcepts = new LinkedList();
					Concept[] child = new Concept[1];
					child[0] = rConcept;
					childConcepts.add(child);
					for (int j = 0; j < depth; j++) {
						Concept[] childrenRelations = null;
						childrenRelations = getRelations((Concept[]) childConcepts.getLast(), relation, 0);
						if (childrenRelations != null)
							childConcepts.add(childrenRelations);
					}
					if (childConcepts.size() > 0) {
						Iterator it = childConcepts.iterator();
						while (it.hasNext()) {
							Concept[] set = (Concept[]) it.next();
							for (int k = 0; k < set.length; k++)
								rConcepts.put(set[k].getId(), set[k]);
						}
					}
				}
			}
		}
		if (rConcepts.size() > 0)
			return (Concept[]) rConcepts.values().toArray(new Concept[0]);

		else
			return null;
	}


	/**
	 * Returns concepts related to a list of concepts.
	 *
	 * @param   concepts    The list of concepts.
	 * @param   relations    The relation types.
	 */
	public Concept[] getRelations(Concept[] concepts, int[] relations, int depth) throws SDXException {
		ArrayList rConcepts = new ArrayList();
		if (relations != null) {
			for (int i = 0; i < relations.length; i++) {
				Concept[] relatives = getRelations(concepts, relations[i], depth);
				if (relatives != null) {
					for (int j = 0; j < relatives.length; j++) {
						rConcepts.add(relatives[j]);
					}
				}
			}
		}

		if (rConcepts.size() > 0)
			return (Concept[]) rConcepts.toArray(new Concept[0]);
		else
			return null;


	}

	/**
	 * Returns concepts related to a list of concepts.
	 *
	 * @param   concept    The list of concepts.
	 * @param   relations    The relation types.
	 */
	public Concept[] getRelations(Concept concept, int[] relations, int depth) throws SDXException {
		ArrayList rConcepts = new ArrayList();
		if (relations != null) {
			for (int i = 0; i < relations.length; i++) {
				Concept[] relatives = getRelations(concept, relations[i], depth);
				if (relatives != null) {
					for (int j = 0; j < relatives.length; j++) {
						rConcepts.add(relatives[j]);
					}
				}
			}
		}

		if (rConcepts.size() > 0)
			return (Concept[]) rConcepts.toArray(new Concept[0]);
		else
			return null;


	}

	/**
	 * Returns concepts related to a list of concepts.
	 *
	 * @param   concepts    The list of concepts.
	 * @param   relation    The relation relation .
	 */
	public Concept[] getRelations(Concept[] concepts, int relation, int depth) throws SDXException {
		if (concepts != null) {
			ArrayList rConcepts = new ArrayList();
			for (int i = 0; i < concepts.length; i++) {
				Concept[] relations = getRelations(concepts[i], relation, depth);
				if (relations != null) {
					for (int j = 0; j < relations.length; j++)
						rConcepts.add(relations[j]);
				}
			}

			if (rConcepts.size() > 0)
				return (Concept[]) rConcepts.toArray(new Concept[0]);
			else
				return null;

		} else
			return null;
	}

	/**
	 * Returns concepts related to a list of concepts.
	 *
	 * @param   searchTerm  The search term which was used to find the concept
	 * @param   concepts    The list of concepts.
	 * @param   relation        The relation relation .
	 * @param   depth       The levels up or down indicating the extent to which the relation search should be executed
	 */
	public Concept[] getRelations(String searchTerm, Concept[] concepts, int relation, int depth) throws SDXException {
		if (concepts != null) {
			ArrayList rConcepts = new ArrayList();
			for (int i = 0; i < concepts.length; i++) {
				Concept[] relations = getRelations(searchTerm, concepts[i], relation, depth);
				if (relations != null) {
					for (int j = 0; j < relations.length; j++)
						rConcepts.add(relations[j]);
				}
			}

			if (rConcepts.size() > 0)
				return (Concept[]) rConcepts.toArray(new Concept[0]);
			else
				return null;

		} else
			return null;
	}

	/**
	 * Returns concepts related to a list of concepts.
	 *
	 * @param   searchTerm  The search term which was used to find the concept
	 * @param   concepts    The list of concepts.
	 * @param   relations        The relation types .
	 * @param   depth       The levels up or down indicating the extent to which the relation search should be executed
	 */
	public Concept[] getRelations(String searchTerm, Concept[] concepts, int[] relations, int depth) throws SDXException {
		ArrayList rConcepts = new ArrayList();
		if (relations != null) {
			for (int i = 0; i < relations.length; i++) {
				Concept[] relatives = getRelations(searchTerm, concepts, relations[i], depth);
				if (relatives != null) {
					for (int j = 0; j < relatives.length; j++) {
						rConcepts.add(relatives[j]);
					}
				}
			}
		}

		if (rConcepts.size() > 0)
			return (Concept[]) rConcepts.toArray(new Concept[0]);
		else
			return null;

	}

	/**
	 * Returns concepts related to a list of concepts.
	 *
	 * @param   searchTerm  The search term which was used to find the concept
	 * @param   concept    The list of concepts.
	 * @param   relations        The relation types .
	 * @param   depth       The levels up or down indicating the extent to which the relation search should be executed
	 */
	public Concept[] getRelations(String searchTerm, Concept concept, int[] relations, int depth) throws SDXException {
		ArrayList rConcepts = new ArrayList();
		if (relations != null) {
			for (int i = 0; i < relations.length; i++) {
				Concept[] relatives = getRelations(searchTerm, concept, relations[i], depth);
				if (relatives != null) {
					for (int j = 0; j < relatives.length; j++) {
						rConcepts.add(relatives[j]);
					}
				}
			}
		}

		if (rConcepts.size() > 0)
			return (Concept[]) rConcepts.toArray(new Concept[0]);
		else
			return null;

	}

	/**
	 * Returns a document using its term.
	 *
	 * @param   name    The document's  term name.
	 */
	public Concept getConceptByName(String name) throws SDXException {
		if (Utilities.checkString(name)) {
			//TODO?:should term be analyzed before be passed to this method or not?
			//how should we be searching here i assume just like a normal search from outside sdx
			//searchlocations, simple query, etc.
			TermQuery tq = new TermQuery(new Term(Concept.TERM, name));
			Hits hits = super.getLuceneIndex().search(tq);
			if (hits == null || hits.length() == 0)
				return null;
			else {
				try {
					Document hitDoc = hits.doc(0);
					if (hitDoc != null)
						return getConcept(hitDoc);
					else
						return null;
				} catch (IOException e) {
					String[] args = new String[2];
					args[0] = super.getLuceneIndex().getIndexPath();
					args[1] = e.getMessage();
					throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LUCENE_RETRIEVE_DOCUMENT, args, e);
				}
			}
		} else
			return null;
	}

	/**
	 * Returns a document using its id.
	 *
	 * @param   id      The document's id.
	 */
	public Concept getConceptById(String id) throws SDXException {

		if (Utilities.checkString(id)) {
			TermQuery tq = new TermQuery(new Term(LuceneDocumentBase.INTERNAL_FIELD_NAME_SDXDOCID, id));
			Hits hits = super.getLuceneIndex().search(tq);
			if (hits == null || hits.length() == 0)
				return null;
			else {
				try {
					Document hitDoc = hits.doc(0);
					if (hitDoc != null)
						return getConcept(hitDoc);
					else
						return null;
				} catch (IOException e) {
					String[] args = new String[2];
					args[0] = super.getLuceneIndex().getIndexPath();
					args[1] = e.getMessage();
					throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LUCENE_RETRIEVE_DOCUMENT, args, e);
				}
			}
		} else
			return null;
	}

	/**
	 * Saves the contents.
	 *
	 */
	public void save() {
	}

	/**
	 * Loads a thesaurus in memory.
	 */
	public void load() {
	}

	/**
	 * Unloads the memory representation of the thesaurus.
	 */
	public void unload() {
	}

	/**
	 * Builds a thesaurus from a File.
	 *
	 * @param   file    The file containing the thesaurus.
	 */
	/*   public void build(File file) throws SDXException {
		   //build and inputsource and call build(inputSource)
		   if (file != null){
			   try {
				   InputSource source = new InputSource(file.toURL().toString());
				   if (source != null) build(source);
			   } catch (MalformedURLException e) {
				   String[] args = new String[2];
				   args[0] = file.getAbsolutePath();
				   args[1] = e.getMessage();
				   throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_GET_INPUTSOURCE, args, e);
			   }
		   }
	   }
	   */

	/**
	 * Builds a thesaurus from a SAX input source.
	 *
	 * @param   source      The SAX input source where the thesaurus is.
	 */
	/*
	public void build(InputSource source) throws SDXException {
		if (source != null) {
			LuceneThesaurusBuilder ltb = new LuceneThesaurusBuilder();
			ltb.enableLogging(super.getLog());
			//setting scope for narrower term(nt) relations
			ltb.setScopeToChildren(1);
			//setting scope for broader term(bt) relations
			ltb.setScopeToParent(1);
			//parser.parse(source, ltb);
			Parser parser = null;
			try {
				parser = (Parser) this.manager.lookup(Parser.ROLE);
				parser.parse(source, ltb);
 //               this.id = ltb.getThesaurusId();
   //             this.xmlLang = ltb.getThesaurusXmlLang();
				//populateIndex(ltb);
			} catch (ComponentException e) {
				String[] args = new String[1];
				args[0] = e.getMessage();
				throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_ACQUIRE_PARSER, args, e);
			} catch (SAXException e) {
			   //unable to parse
				String[] args = new String[2];
				//is this a bad idea, we need some way to give the user more information about which document parsing has failed resides
				args[0] = source.getSystemId();
				args[1] = e.getMessage();
				throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_PARSE_DOC, args, e);
			} catch (IOException e) {
			   //unable to parse
				String[] args = new String[2];
				//is this a bad idea, we need some way to give the user more information about which document parsing has failed resides
				args[0] = source.getSystemId();
				args[1] = e.getMessage();
				throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_PARSE_DOC, args, e);
			} finally {
				if (parser != null) this.manager.release(parser);
			}
		}

	} */


	protected LuceneConcept getConcept(Document ldoc) {
		if (ldoc != null) {
			LuceneConcept concept = new LuceneConcept(ldoc);
			return concept;
		} else
			return null;
	}

	/* Returns concepts related to a document, filtered by languages.
	 *
	 * @param   concept     The document.
	 * @param   relations   The relations to use.
	 * @param   depth       The depth to which relations should be retrieved
	 * @param   langs       The list of languages for filtering concepts.
	 * @return
	 * @throws SDXException
	 */
	public Concept[] getRelations(Concept concept, int[] relations, int depth, String[] langs) throws SDXException {
		Concept[] relatives = getRelations(concept, relations, depth);
		return filterByLangs(relatives, langs);
	}

	/* Returns concepts related to a document, filtered by languages.
	 *
	 * @param   concepts     The list of concepts.
	 * @param   relations   The relations to use.
	 * @param   depth       The depth to which relations should be retrieved
	 * @param   langs       The list of languages for filtering concepts.
	 * @return
	 * @throws SDXException
	 */
	public Concept[] getRelations(Concept[] concepts, int[] relations, int depth, String[] langs) throws SDXException {
		Concept[] relatives = getRelations(concepts, relations, depth);
		return filterByLangs(relatives, langs);
	}

	/**
	 * Returns concepts related to a document, filtered by languages.
	 *
	 * @param   concept     The document.
	 * @param   relation    The relation to use.
	 * @param   depth       The depth to which relations should be retrieved
	 * @param   langs       The list of languages for filtering concepts.
	 */
	public Concept[] getRelations(Concept concept, int relation, int depth, String[] langs) throws SDXException {
		int[] relations = new int[1];
		relations[0] = relation;
		return getRelations(concept, relations, depth, langs);
	}

	/**
	 * Returns concepts related to a list of concepts, filtered by languages.
	 *
	 * @param   concepts    The list of concepts.
	 * @param   relation    The relation.
	 * @param   depth       The depth to which relations should be retrieved
	 * @param   langs       The list of languages for filtering concepts.
	 */
	public Concept[] getRelations(Concept[] concepts, int relation, int depth, String[] langs) throws SDXException {
		int[] relations = new int[1];
		relations[0] = relation;
		return getRelations(concepts, relations, depth, langs);
	}


	public void configure(Configuration configuration) throws ConfigurationException {
		//do luceneDocumentBaseConfiguration
		super.configure(configuration);
		//configuring
		//default depth param
		//default relation params
		Configuration depth = configuration.getChild(SDXThesaurus.ConfigurationNode.DEPTH, false);
		if (depth != null)
			defaultDepth = depth.getValueAsInteger(0);//<depth>n</depth> if none; then n=0

		Configuration[] relations = configuration.getChild(SDXThesaurus.ConfigurationNode.RELATIONS, true).getChildren(SDXThesaurus.ConfigurationNode.RELATION);
		if (relations != null && relations.length > 0) {
			defaultRelations = new int[relations.length];
			for (int i = 0; i < relations.length; i++) {
				String relationAbbr = relations[i].getValue();
				int relation = getRelationTypeInt(relationAbbr);
				if (relation > -1)
					defaultRelations[i] = relation;
			}
		}
	}

	public int getDefaultDepth() {
		return defaultDepth;
	}

	public int[] getDefaultRelations() {
		return defaultRelations;
	}

	public Concept[] filterByLangs(Concept[] concepts, String[] langs) {
		if (concepts == null) return null;
		if (langs == null || langs.length == 0) return concepts;

		ArrayList rConcepts = new ArrayList();
		for (int i = 0; i < concepts.length; i++) {
			String xmlLang = concepts[i].getXmlLang();
			if (Utilities.checkString(xmlLang)) {
				for (int j = 0; j < langs.length; j++) {
					if (xmlLang.equals(langs[j]))
						rConcepts.add(concepts[i]);
				}
			}
		}

		if (rConcepts.size() > 0)
			return (Concept[]) rConcepts.toArray(new Concept[0]);
		else
			return null;
	}

	public void init() throws SDXException {
		super.init();
		String url = super._configuration.getAttribute(SDXThesaurus.ConfigurationNode.SRC, null);
		File srcFile = Utilities.resolveFile(null, super._configuration.getLocation(), super.getContext(), url, false);
		try {
			url = srcFile.toURI().toURL().toExternalForm();
		} catch (MalformedURLException e) {
			String[] args = new String[1];
			args[0] = url;
			throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_BUILD_URL, args, e);
		}
		final String finalUrl = url;
		final Logger finalLogger = super.getLog();


		new Thread() {
			public void run() {
				try {
					build(finalUrl);
				} catch (SDXException e) {
					finalLogger.error(e.getMessage(), e);
				} catch (ConfigurationException e) {
					finalLogger.error(e.getMessage(), e);
				}
			}
		}.start();
	}

	public void build(String url) throws SDXException, ConfigurationException {
		if (Utilities.checkString(url)) {
			String lastmod = "";
			String sysid = "";

			try {
				this.source = new URLSource();
				this.source.init(new URL(url),new Hashtable());
			} catch (IOException e) {
				throw new ConfigurationException(e.getMessage(), e);
			}
			if (this.source != null) {
				lastmod = String.valueOf(this.source.getLastModified());
				sysid = this.source.getURI();
			}
			final String INTERNAL_FIELD_NAME_SDXTH_FILE = "sdxthfile";
			final String INTERNAL_SDXTHFILE_FIELD_VALUE = "2";
			final String INTERNAL_FIELD_NAME_SDXTH_LASTMOD = "sdxthlastmod";
			final String INTERNAL_FIELD_NAME_SDXTH_SYSID = "sdxthsysid";

			BooleanQuery q = Utilities.newBooleanQuery();
			TermQuery tqLm = new TermQuery(new Term(INTERNAL_FIELD_NAME_SDXTH_LASTMOD, lastmod));
			TermQuery tqSysid = new TermQuery(new Term(INTERNAL_FIELD_NAME_SDXTH_SYSID, sysid));
			// MAJ Lucene 2.1.0
//			q.add(tqLm, true, false);
//			q.add(tqSysid, true, false);
			q.add(tqLm, BooleanClause.Occur.MUST);
			q.add(tqSysid, BooleanClause.Occur.MUST);
			Hits hits = super.getLuceneIndex().search(q);
			if (hits == null || hits.length() == 0) {
				String[] args = {super.getId()};
				LoggingUtils.logInfo(super.getLog(), SDXExceptionCode.INFO_THESAURUS_CONFIGURING, args);
				InputSource is = new InputSource(sysid);
				build(is);
				//adding the info about when the source file was last modified
				//we delete any existing place holder lucene documents
				super.getLuceneIndex().delete(new Term(INTERNAL_FIELD_NAME_SDXTH_FILE, INTERNAL_SDXTHFILE_FIELD_VALUE));
				//we add our new place holder
				Document ldoc = new Document();
				// MAJ Lucene 2.1.0 
				//Field thfileField = Field.Keyword(INTERNAL_FIELD_NAME_SDXTH_FILE, INTERNAL_SDXTHFILE_FIELD_VALUE);
				Field thfileField = new Field(INTERNAL_FIELD_NAME_SDXTH_FILE, INTERNAL_SDXTHFILE_FIELD_VALUE, Field.Store.YES, Field.Index.UN_TOKENIZED);
				ldoc.add(thfileField);
				Field lastmodField = null;
				Field sysidField = null;
				if (Utilities.checkString(lastmod))
					// MAJ Lucene 2.1.0
					//lastmodField = Field.Keyword(INTERNAL_FIELD_NAME_SDXTH_LASTMOD, lastmod);
					lastmodField = new Field(INTERNAL_FIELD_NAME_SDXTH_LASTMOD, lastmod, Field.Store.YES, Field.Index.UN_TOKENIZED);
				if (Utilities.checkString(sysid))
					// MAJ Lucene 2.1.0
					//sysidField = Field.Keyword(INTERNAL_FIELD_NAME_SDXTH_SYSID, sysid);
					sysidField = new Field(INTERNAL_FIELD_NAME_SDXTH_SYSID, sysid, Field.Store.YES, Field.Index.UN_TOKENIZED);
				if (lastmodField != null)
					ldoc.add(lastmodField);
				if (sysidField != null)
					ldoc.add(sysidField);
				super.getLuceneIndex().writeDocument(ldoc, false);
				super.getLuceneIndex().optimize();
				LoggingUtils.logInfo(super.getLog(), SDXExceptionCode.INFO_THESAURUS_CONFIGURED,args);
			}
		}

	}

	public void build(InputSource source) throws SDXException {

		LuceneThesaurusBuilder ltb = new LuceneThesaurusBuilder();
		SAXParser parser = null;
		ServiceManager l_manager = super.getServiceManager();
		try {
			parser = (SAXParser) l_manager.lookup(SAXParser.ROLE);
			if (parser != null)
				parser.parse(source, ltb);
		} catch (ServiceException e) {
			String[] args = new String[1];
			args[0] = e.getMessage();
			throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_ACQUIRE_PARSER, args, e);
		} catch (SAXException e) {
			//unable to parse
			String[] args = new String[2];
			//is this a bad idea, we need some way to give the user more information about which document parsing has failed resides
			if (source.getSystemId() != null) args[0] = source.getSystemId();
			args[1] = e.getMessage();
			throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_PARSE_DOC, args, e);
		} catch (IOException e) {
			//unable to parse
			String[] args = new String[2];
			//is this a bad idea, we need some way to give the user more information about which document parsing has failed resides
			if (source.getSystemId() != null) args[0] = source.getSystemId();
			args[1] = e.getMessage();
			throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_PARSE_DOC, args, e);
		} finally {
			if (parser != null) l_manager.release(parser);
		}
		Concept[] concepts = ltb.getConcepts();
		if (concepts != null)
			this.addConcepts(concepts);

	}

	//
	/*
	protected void configureIdGenerator(Configuration configuration) throws ConfigurationException {
		//TODO:but maybe it would be desired in cases of flat thesauri
		//this should do nothing, id generators are not helpful when building a thesaurus
		//that could contain concepts with references to other concepts    }

	} */

	public void compile() throws SDXException {
	}

	public String getRelationTypeAbbreviation(int type) {
		switch (type) {
			case RELATION_BROADER_TERMS:
				return Concept.RELATION_BROADER_TERMS;
			case RELATION_BROADER_TERM:
				return Concept.RELATION_BROADER_TERM;
			case RELATION_NARROWER_TERM:
				return Concept.RELATION_NARROWER_TERM;
			case RELATION_USED_FOR:
				return Concept.RELATION_USED_FOR;
			case RELATION_EQUIVALENT_TERM:
				return Concept.RELATION_EQUIVALENT_TERM;
			case RELATION_RELATED_TERM:
				return Concept.RELATION_RELATED_TERM;
			case RELATION_USE:
				return Concept.RELATION_USE;
			case RELATION_PARTIAL_EQIUVALENCE:
				return Concept.RELATION_PARTIAL_EQIUVALENCE;
			default:
				return null;
		}
	}

	public int getRelationTypeInt(String abbr) {
		if (abbr.equalsIgnoreCase(Concept.RELATION_BROADER_TERMS))
			return RELATION_BROADER_TERMS;
		if (abbr.equalsIgnoreCase(Concept.RELATION_BROADER_TERM))
			return RELATION_BROADER_TERM;
		if (abbr.equalsIgnoreCase(Concept.RELATION_NARROWER_TERM))
			return RELATION_NARROWER_TERM;
		if (abbr.equalsIgnoreCase(Concept.RELATION_USE))
			return RELATION_USE;
		if (abbr.equalsIgnoreCase(Concept.RELATION_USED_FOR))
			return RELATION_USED_FOR;
		if (abbr.equalsIgnoreCase(Concept.RELATION_EQUIVALENT_TERM))
			return RELATION_EQUIVALENT_TERM;
		if (abbr.equalsIgnoreCase(Concept.RELATION_RELATED_TERM))
			return RELATION_RELATED_TERM;
		if (abbr.equalsIgnoreCase(Concept.RELATION_PARTIAL_EQIUVALENCE))
			return RELATION_PARTIAL_EQIUVALENCE;
		return -1;
	}
	
	protected boolean initToSax(){
		if(!super.initToSax())
			return false;
		else{
			this._xmlizable_objects.put("Thesaurus_Type","lucene");
			this._xmlizable_objects.put("Depth",Integer.toString(this.defaultDepth));
			this._xmlizable_objects.put("Document_Count",String.valueOf(this.size()));
			if(source != null)
				this._xmlizable_objects.put("Source",source.getURI());
			for(int i=0; i<this.defaultRelations.length; i++)
				this._xmlizable_objects.put("Relation",this.getRelationTypeAbbreviation(defaultRelations[i]));
			return true;
		}
	}
	
	/**Init the LinkedHashMap _xmlizable_volatile_objects with the objects in order to describ them in XML
	 * Some objects need to be refresh each time a toSAX is called*/
	protected void initVolatileObjectsToSax() {
		super.initVolatileObjectsToSax();
		this._xmlizable_objects.put("Depth",Integer.toString(this.defaultDepth));
		this._xmlizable_objects.put("Document_Count",String.valueOf(this.size()));
	}

}
