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

import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.framework.Framework;
import fr.gouv.culture.sdx.search.lucene.Field;
import fr.gouv.culture.sdx.search.lucene.queryparser.DefaultQueryParser;
import fr.gouv.culture.sdx.search.lucene.queryparser.QueryParser;
import fr.gouv.culture.sdx.utils.AbstractSdxObject;
import fr.gouv.culture.sdx.utils.constants.Node;
import org.apache.excalibur.xml.sax.XMLizable;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.search.ParallelMultiSearcher;
import org.apache.lucene.search.Searchable;
import org.apache.lucene.search.Searcher;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import fr.gouv.culture.sdx.documentbase.DocumentBase;
import fr.gouv.culture.sdx.documentbase.LuceneDocumentBase;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Vector;

/**
 * A list of LuceneIndex where searches can be made.
 *
 */
public class SearchLocations extends AbstractSdxObject implements fr.gouv.culture.sdx.search.SearchLocations {

    /** The list of documentbases. */
    private Vector dbs = new Vector();
    
    /** The list of indices. */
    private Vector indices = new Vector();

    /**The query parser*/
    private QueryParser queryParser = null;

    /**
     * Builds an empty search location.
     *
     * Use enableLoggin and addIndex() afterwards to add at least one index.
     *
     * @see #enableLogging
     * @see #addIndex
     */
    public SearchLocations() {
    }

    /**
     * @deprecated
     * Builds a search location with a single index.
     *
     * @param   index       The Lucene index to use.
     */
    public void setUp(Index index) {
        this.addIndex(index);
    }

    /**
     * Builds a search location with a single documentbase.
     *
     * @param   db       The documentBase to use.
     */
    public void setUp(DocumentBase db) {
        this.addDocumentBase(db);
    }
    
    /**
     * @deprecated
     * Adds an index to locations.
     *
     * @param   index       The index to add.
     */
    /*TODO: fred and i discussed a toSAX() method could  be useful for this object, to nest in a Results.toSax(). Then,
    *this method mayb  need to take a documentbase instead of an index and we can get the index from the document base,
    *then have the id of the documentbase and also the associated index.-rbp
    */
    public void addIndex(Index index) {
        if (index == null) return;
        //if this searchlocations doesn't already contain the index we add it
        if (!contains(index))//we add the index provided externally
            this.indices.add(index);
    }
    
    
    /**
     * Add a documentBase to the search location
     * It mean it add all documentBase indexes 
     */
    public void addDocumentBase(DocumentBase db)
    {
    	if(db == null) return;
    	// add the document base if not already present
    	if(!contains(db)) this.dbs.add(db);
    }

    /**
     * Returns the default field for the first search location (first index).
     */
    public Field getDefaultField() throws SDXException {
    	try {
    		if (size() > 0) return ((LuceneDocumentBase) dbs.get(0)).getIndex().getDefaultField();
    		return null;
    	} catch (IOException e) {
    		//TODO: handle the exception
    		return null;
    	}
    }
    
    /**
     * Returns the first document bases sets up in the SearchLocations.
     * 
     * 
     * @return {@link DocumentBase} The first document base or the only document base. 
     * May be null if the SearchLocations does not contain any document base.
     */
    public DocumentBase getFirstDocumentBase () {
    	return (size() > 0) ? (DocumentBase) dbs.get(0) : null;
    }
    
    /** Returns a field given a name.
     *
     * Will return the first field having this name, <code>null</code> if none found.
     *
     * @param name The name of the field for which the Field is desired.
     * @return
     */
    public Field getField(String name) throws SDXException {
        return getFirstField(name);
    }

    /**
     * Returns the type of a field given its name.
     *
     * The first field with this name will be returned.
     *
     * @param   name    The field name.
     */
    public int getFieldType(String name) throws SDXException {
        Field f = getFirstField(name);
        if (f == null) return Field.WORD; // Default
        return f.getFieldType();
    }

    /**
     * Returns the typeName of a field given its name.
     *
     * The first field with this name will be returned.
     *
     * @param   name    The field name.
     */
    public String getTypeName(String name) throws SDXException {
        Field f = getFirstField(name);
        if (f != null)
            return f.getTypeName();
        else
            return null;
    }

    /**
     * Returns the first field object given its name.
     *
     * @param   name    The field name.
     */
    private Field getFirstField(String name) {
        if (name == null) return null;
        try {
        	if (size() > 0) return ((LuceneDocumentBase) dbs.get(0)).getIndex().getField(name);
        	// If none found, return null
        	return null;
    	} catch (IOException e) {
    		//TODO: handle the exception
    		return null;
    	}
    }
    
    
    /** Returns the document bases sets up in the SearchLocations
     * 
     * @return Vector	The document bases
     */
    public Vector getDocumentBases() {
    	return this.dbs;
    }
    
    /**
     * Returns the documents bases names sets up in the SearchLocations
     * 
     * @return HashSet	The documents bases names
     */
    public HashSet getDocumentBasesNames() {
    	HashSet _return = new HashSet(size());
    	for (int i=0, l=size(); i < l; i++) {
    		_return.add( ((DocumentBase) dbs.get(i)).getId() );
    	}
    	return _return;
    }

    /**
     * Returns the number of document bases in this search location.
     */
    public int size() {
        return dbs.size();
    }

    public boolean contains(DocumentBase db) {
        if (dbs == null)
            return false;
        return dbs.contains(db);
    }

    public boolean contains(Index index) {
        if (index == null)
            return false;
        return indices.contains(index);
    }

    /** Returns the locale for a field given its name.
     *
     * The first field found with this name will be used.
     *
     * @param name  The name of the field for which the Locale is desired.
     */
    public Locale getLocale(String name) throws SDXException {
        Field f = getFirstField(name);
        if (f == null) return null;
        return f.getLocale();
    }

    /**
     * @deprecated
     * Returns an index reader at the specified index.
     *
     * @param idx   The documentBase of the desired reader.
     */
    public IndexReader _getReader(int idx) throws SDXException {
        if (idx >= 0 && idx <= indices.size()) {
            LuceneIndex lIndex = null;
            Index index = (Index) indices.elementAt(idx);
            if (index instanceof LuceneIndex)
                lIndex = (LuceneIndex) index;
            if (lIndex != null)
                return lIndex.getReader();
            else//we have a remote searcher and can't get a reader
                return null;
        } else
            return null;
    }

    /**
     * @deprecated
     */
    public IndexReader _getReader() throws SDXException {

        try {
            IndexReader[] readers = new IndexReader[indices.size()];
            for (int i = 0; i < indices.size(); i++)
                readers[i] = ((LuceneIndex) indices.get(i)).getReader();
            return new MultiReader(readers);
        } catch (IOException e) {
            //can't build multisearcher
            String[] args = new String[1];
            args[0] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_BUILD_MULTIREADER, args, e);
        }
    }


    /**
     * Returns an index reader at the specified documentBase.
     *
     * @param idb   The documentBase of the desired reader.
     */
    public IndexReader getReader(int idb) throws SDXException {
        if (idb >= 0 && idb <= size()) {
            LuceneDocumentBase lDb = null;
            DocumentBase db = (DocumentBase) dbs.elementAt(idb);
            if (db instanceof LuceneDocumentBase)
            	lDb = (LuceneDocumentBase) db;
            if (lDb != null)
                return lDb.getIndexReader();
            else//we have a remote searcher and can't get a reader
                return null;
        } else
            return null;
    }
    
    public IndexReader getReader() throws SDXException {
        try {
            IndexReader[] readers = new IndexReader[size()];
            for (int i = 0; i < size(); i++)
                readers[i] = ((LuceneDocumentBase) dbs.get(i)).getIndexReader();
            return new MultiReader(readers);
        } catch (IOException e) {
            //can't build multisearcher
            String[] args = new String[1];
            args[0] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_BUILD_MULTIREADER, args, e);
        }
    }

    /**
     * Returns an searcher for the specified index.
     *
     * @param idx   The index of the desired searcher.
     */

    public Searcher _getSearcher(int idx) throws SDXException {
        try {
            if (idx >= 0 && idx <= indices.size()) {
                LuceneIndex lIndex = null;
                Index index = (Index) indices.elementAt(idx);
                if (index instanceof LuceneIndex)
                    lIndex = (LuceneIndex) index;
                if (lIndex != null)
                    return new ParallelMultiSearcher(new Searchable[]{lIndex.getSearcher()});
                else
                    return null;
            } else
                return null;
        } catch (IOException e) {
            //can't build multisearcher
            String[] args = new String[1];
            args[0] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_BUILD_MULTISEARCHER, args, e);
        }
    }

    /**
     * Returns a searcher for these locations.
     */
    public Searcher _getSearcher() throws SDXException {
        try {
            Searchable[] searchers = new Searcher[indices.size()];
            for (int i = 0; i < indices.size(); i++) {
                LuceneIndex index = (LuceneIndex) indices.elementAt(i);
                searchers[i] = index.getSearcher();
            }
            return new ParallelMultiSearcher(searchers);
        } catch (IOException e) {
            //can't build multisearcher
            String[] args = new String[1];
            args[0] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_BUILD_MULTISEARCHER, args, e);
        }
    }

    /**
     * Returns an searcher for the specified documentBase.
     *
     * @param idb   The documentBase of the desired searcher.
     */

    public Searcher getSearcher(int idb) throws SDXException {
        try {
            if (idb >= 0 && idb <= size()) {
                LuceneDocumentBase lDb = null;
                DocumentBase db = (DocumentBase) dbs.elementAt(idb);
                if (db instanceof LuceneDocumentBase)
                	lDb = (LuceneDocumentBase) db;
                if (lDb != null)
                    return new ParallelMultiSearcher(new Searchable[]{lDb.getSearcher()});
                else
                    return null;
            } else
                return null;
        } catch (IOException e) {
            //can't build multisearcher
            String[] args = new String[1];
            args[0] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_BUILD_MULTISEARCHER, args, e);
        }
    }

    /**
     * Returns a searcher for these locations.
     */
    public Searcher getSearcher() throws SDXException {
        try {

            Searchable[] searchers = new Searcher[size()];
            for (int i = 0; i < size(); i++) {
            	LuceneDocumentBase db = (LuceneDocumentBase) dbs.elementAt(i);
                searchers[i] = db.getSearcher();
            }
            return new ParallelMultiSearcher(searchers);
        } catch (IOException e) {
            //can't build multisearcher
            String[] args = new String[1];
            args[0] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_BUILD_MULTISEARCHER, args, e);
        }
    }


    public QueryParser getQueryParser() {
        if (this.queryParser != null) return queryParser;
        if (size() > 0) {
            try {
            	return ((LuceneDocumentBase) dbs.get(0)).getIndex().getQueryParser();
            } catch (SDXException e) {
                return new DefaultQueryParser();
            } catch (IOException e) {
                return new DefaultQueryParser();
            }
        }

        return new DefaultQueryParser();
    }

    public void setQueryParser(QueryParser qParser) {
        this.queryParser = qParser;
    }


    public void toSAX(ContentHandler hdl) throws SAXException {
        String sdxNsUri = Framework.SDXNamespaceURI;
        String sdxNsPrefix = Framework.SDXNamespacePrefix;
        String localName = Node.Name.SEARCH_LOCATIONS;
        String qName = sdxNsPrefix + ":" + localName;
        AttributesImpl atts = new AttributesImpl();
        //Let's build the locations representation
        hdl.startElement(sdxNsUri, localName, qName, atts);
        //Output each search location if possible
        for (int i = 0; i < size(); i++) {
            if (dbs.get(i) instanceof Searchable){
            	Index index = ((fr.gouv.culture.sdx.search.Searchable) dbs.get(i)).getIndex();
            	if(index instanceof XMLizable)
            		((XMLizable)index).toSAX(hdl);
            }
            //else : TODO : output some kind of message here ? -pb
        }
        //All over
        hdl.endElement(sdxNsUri, localName, qName);

    }

    protected String getClassNameSuffix() {
        return fr.gouv.culture.sdx.search.SearchLocations.CLASS_NAME_SUFFIX;
    }

	/* (non-Javadoc)
	 * @see fr.gouv.culture.sdx.utils.AbstractSdxObject#initToSax()
	 */
	protected boolean initToSax() {
		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() {
	
	}


}
