/*
 * LIUS - Lucene Index Update and Search
 * http://sourceforge.net/projects/lius/
 *
 * Copyright (c) 2004, Laval University Library.  All rights reserved.
 *
 * This program is a free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package ca.ulaval.bibl.lius.search;



import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import java.util.List;

import java.util.Vector;

import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MultiSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searchable;
import org.apache.lucene.search.Searcher;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.output.XMLOutputter;
import org.jdom.output.DOMOutputter;

import ca.ulaval.bibl.lius.config.LiusConfig;
import ca.ulaval.bibl.lius.config.LiusConfigBuilder;
import ca.ulaval.bibl.lius.config.LiusField;
import ca.ulaval.bibl.lius.Lucene.LuceneActions;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

/**
 * Classe permettant d'effectuer des recherches au niveau de l'index.
 * <br/><br/>
 * Class that enables searches on the index.
 * @author Rida Benjelloun (rida.benjelloun@bibl.ulaval.ca)
 */
public class SearchIndex {

  //private static SearchIndex searchActIns;
  private Searcher searcher = null;
  private Searchable[] searchables = null;
  private LiusQuery lq = new LiusQuery();

  /**
   * Méthode se basant sur le fichier XML de configuration afin de
   * construire l'analyseur et l'objet Query.
   * Elle reçoit également le chemin de l'index et une expression de
   * recherche entrée par l'utilisateur.
   * <br/><br/>
   * Method based on the XML configuration file to construct the
   * analyser et the Query object. The method needs the path of the index and a
   * search expression given by a user.
   */
  public synchronized List search(String rechercheExp,
                                  String indexDir,
                                  String configFile) {
    List liusHits = new ArrayList();
    Directory fsDir = null;

    try {
      LiusConfig xc = LiusConfigBuilder.getSingletonInstance().
          getLiusConfig(configFile);
      if (LuceneActions.getSingletonInstance().indexExists(indexDir)) {
        fsDir = FSDirectory.getDirectory(indexDir, false);
      }
      searcher = new IndexSearcher(fsDir);
      Query query = lq.getQueryFactory(xc.
          getElemSearch(),
          xc, rechercheExp);
      Hits hits = searcher.search(query);
      Vector fieldsToDisplay = xc.getDisplayFields();
      for (int i = 0; i < hits.length(); i++) {
        List document = new ArrayList();
        for (int j = 0; j < fieldsToDisplay.size(); j++) {
          LiusField lf = (LiusField) fieldsToDisplay.get(j);
          LiusField lfNew = new LiusField();
          String fieldName = lf.getName();
          lfNew.setName(fieldName);
          lfNew.setLabel(lf.getLabel());
          String[] valS = hits.doc(i).getValues(fieldName);
          if (valS != null) {
            Vector v = tabToVector(valS);
            lfNew.setValues(v);
            document.add(lfNew);
          }
        }
        liusHits.add(document);
      }
    }

    catch (IOException a) {
      a.printStackTrace();
    }

    return liusHits;
  }

  /**
   * Permet d'effectuer une recherche et de retouner le résultat
   * sous forme d'un objet de type JDOM Document.
   * <br/><br/>
   * Searches and returns the result as a JDOM object.
   */
  public Document searchResultAsJdomDoc(String rechercheExp,
                                        String indexDir,
                                        String configFile) {
    List res = search(rechercheExp, indexDir, configFile);
    Document liusXmlDoc = buildJDomDocFromListOfResults(res);

    return liusXmlDoc;
  }

  /**
   * Permet d'effectuer une recherche et de retouner le résultat
   * sous forme d'un objet de type DOM Document.
   * <br/><br/>
   * Searches and returns the result as a DOM object.
   */
  public org.w3c.dom.Document searchResultAsDomDoc(String rechercheExp,
      String indexDir,
      String configFile) {
    org.w3c.dom.Document domDoc = null;
    DOMOutputter dopt = new DOMOutputter();
    Document jdomDoc = searchResultAsJdomDoc(rechercheExp, indexDir, configFile);

    try {
      domDoc = dopt.output(jdomDoc);
    }
    catch (JDOMException e) {
      e.printStackTrace();
    }
    return domDoc;
  }

  /**
   * Permet d'enregistrer le Document XML JDOM dans un fichier.
   * <br/><br/>
   * Saves the XML JDOM Document in a file.
   */
  public void toFile(Document doc, String file) {
    XMLOutputter xop = new XMLOutputter();
    try {
      xop.output(doc, new FileOutputStream(file));
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }

  /**
   * Permet de rechercher plusieurs indexes simultanément.
   * <br/><br/>
   * Searches many indexes at the same time.
   */
  public List multiIndexSearch(String rechercheExp,
                               List indexDirs,
                               String configFile) {

    List liusHits = new ArrayList();

    try {
      LiusConfig xc = LiusConfigBuilder.getSingletonInstance().
          getLiusConfig(configFile);

      searcher = new MultiSearcher(buildSearchableTab(indexDirs));

      Query query = lq.getQueryFactory(xc.
          getElemSearch(),
          xc, rechercheExp);
      Hits hits = searcher.search(query);
      Vector fieldsToDisplay = xc.getDisplayFields();
      for (int i = 0; i < hits.length(); i++) {
        List document = new ArrayList();
        for (int j = 0; j < fieldsToDisplay.size(); j++) {
          LiusField lf = (LiusField) fieldsToDisplay.get(j);
          LiusField lfNew = new LiusField();
          String fieldName = lf.getName();
          lfNew.setName(fieldName);
          lfNew.setLabel(lf.getLabel());
          String[] valS = hits.doc(i).getValues(fieldName);
          if (valS != null) {
            Vector v = tabToVector(valS);
            lfNew.setValues(v);
            document.add(lfNew);
          }
        }
        liusHits.add(document);
      }
    }
    catch (IOException a) {
      a.printStackTrace();
    }
    return liusHits;
  }

  public Document multiIndexSearchResultsAsJDom(String rechercheExp,
                                                List indexDirs,
                                                String configFile) {
    List res = multiIndexSearch(rechercheExp,
                                indexDirs,
                                configFile);

    Document xmlDoc = buildJDomDocFromListOfResults(res);
    return xmlDoc;

  }

  public org.w3c.dom.Document multiIndexSearchResultsAsDom(String rechercheExp,
      List indexDirs,
      String configFile) {
    List res = multiIndexSearch(rechercheExp,
                                indexDirs,
                                configFile);

    Document xmlDoc = buildJDomDocFromListOfResults(res);
    org.w3c.dom.Document domDoc = null;
    DOMOutputter dopt = new DOMOutputter();
    Document jdomDoc = multiIndexSearchResultsAsJDom(rechercheExp, indexDirs,
        configFile);

    try {
      domDoc = dopt.output(jdomDoc);
    }
    catch (JDOMException e) {
      e.printStackTrace();
    }

    return domDoc;

  }

  /**
   * Méthode se basant sur le fichier XML de configuration afin de
   * construire  l'analyseur et l'objet RangeQuery.
   * Elle reçoit également le chemin de l'index et deux expressions de
   * recherche entrées par l'utilisateur pour créer le RangeQuery.
   * <br/><br/>
   * Method based on the XML configuration file to construct the analyser
   * and the RangeQuery object. It needs the path of the index and two search
   * expressions given by user to create the RangeQuery.
   */
  public List searchWithRangeQuery(String lowerSearchExp1,
                                   String lowerSearchExp2,
                                   String indexDir,
                                   String configFile) {
    Directory fsDir = null;
    List liusHits = new ArrayList();
    List document = new ArrayList();
    try {

      LiusConfig xc = LiusConfigBuilder.getSingletonInstance().
          getLiusConfig(configFile);
      if (LuceneActions.getSingletonInstance().indexExists(indexDir)) {
        fsDir = FSDirectory.getDirectory(indexDir, false);
      }
      searcher = new IndexSearcher(fsDir);
      Query query = lq.createRangeQuery(xc,
          lowerSearchExp1, lowerSearchExp2);
      Hits hits = searcher.search(query);
      List fieldsToDisplay = xc.getDisplayFields();

      for (int i = 0; i < hits.length(); i++) {

        for (int j = 0; j < fieldsToDisplay.size(); j++) {
          LiusField lf = (LiusField) fieldsToDisplay.get(j);
          String fieldName = lf.getName();
          String[] valS = hits.doc(i).getValues(fieldName);
          if (valS != null) {
            Vector v = tabToVector(valS);
            lf.setValues(v);
            document.add(lf);
          }
        }
        liusHits.add(document);
      }
    }

    catch (IOException a) {
      a.printStackTrace();
    }

    return liusHits;
  }

  public Searchable[] buildSearchableTab(List indexDirs) {
    List index = putIndexs(indexDirs);
    searchables = new Searchable[index.size()];
    try{
      for (int i = 0; i < index.size(); i++) {
        Directory fsDir = FSDirectory.getDirectory( (String) index.get(i), false);
          searchables[i] = new IndexSearcher(fsDir);
      }


      }
      catch (IOException ex) {
      }


    return searchables;
  }

  private List putIndexs(List indexDirs){
    List indexExist = new ArrayList();
    for (int i = 0; i < indexDirs.size(); i++) {
      if (LuceneActions.getSingletonInstance().indexExists( (String)
          indexDirs.get(i))) {
        indexExist.add( (String) indexDirs.get(i));
      }
    }
     return indexExist;
  }

  /**
   * Méthode permettant de convertir un tableau en vecteur.
   * <br/><br/>
   * Method that converts an array to a vector.
   */
  private Vector tabToVector(String[] tab) {
    Vector v = new Vector();
    for (int i = 0; i < tab.length; i++)
      v.add(tab[i]);
    return v;
  }

  public Document buildJDomDocFromListOfResults(List res) {
    Element results = new Element("results").setAttribute(new Attribute("size",
        "" + res.size()));
    Document liusXmlDoc = new Document(results);
    for (int i = 0; i < res.size(); i++) {
      List document = (List) res.get(i);
      Element resDocument = new Element("Document");
      for (int j = 0; j < document.size(); j++) {
        LiusField lf = (LiusField) document.get(j);
        Element fieldName = new Element("Field").setAttribute(new Attribute(
            "name", lf.getName()));
        fieldName.setAttribute(new Attribute("label", lf.getLabel()));
        Vector values = lf.getValues();
        for (int k = 0; k < values.size(); k++) {
          Element value = new Element("value");
          value.addContent("" + values.get(k));
          fieldName.addContent(value);
        }
        resDocument.addContent(fieldName);
      }
      results.addContent(resDocument);

    }
    return liusXmlDoc;
  }
}