/*
 * 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.Lucene;

import java.io.*;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.DateField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.analysis.Analyzer;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Date;
import java.util.ArrayList;

import java.text.DateFormat;

import java.text.SimpleDateFormat;

import org.apache.lucene.index.Term;

import ca.ulaval.bibl.lius.Exception.LiusException;
import ca.ulaval.bibl.lius.config.*;
import ca.ulaval.bibl.lius.index.Indexer;
import ca.ulaval.bibl.lius.index.IndexerFactory;
import ca.ulaval.bibl.lius.index.JavaObject.BeanIndexer;
import ca.ulaval.bibl.lius.index.MixteIndexing.MixteIndexer;
import ca.ulaval.bibl.lius.index.XML.XmlNodeIndexer;
import java.text.*;

/**
 * Classe permettant d'effectuer des actions relatives à Lucene.
 * <br/><br/>
 * Class that executes actions related to Lucene.
 * @author Rida Benjelloun (rida.benjelloun@bibl.ulaval.ca)
 */
public class LuceneActions {



  private static LuceneActions luceneActIns;

  public static LuceneActions getSingletonInstance() {
    if (luceneActIns == null)
      luceneActIns = new LuceneActions();
    return luceneActIns;
  }

  /**
   * Methode permettant de construire un objet de type "Lucene Document"
   * à partir de plusieurs collections contenant des informations sur
       * les documents à indexer. Cette méthode est utilisée pour l'indexation mixte.
   * <br/><br/>
   * Method that constructs a Lucene document object from many collections
   * containing information on the documents to index. This method is used for mixed
   * indexation.
   */
  public Document populateLuceneDocumentFromListOfCollectionFields(List
      listCollectionsFieldsContentAndType) {
    Collection coll = new ArrayList();
    for (int i = 0; i < listCollectionsFieldsContentAndType.size(); i++) {
      Collection collList = (Collection) listCollectionsFieldsContentAndType.
          get(i);
      Iterator it = collList.iterator();
      while (it.hasNext()) {
        LiusField lf = (LiusField) it.next();
        coll.add(lf);
      }
    }
    Document doc = populateLuceneDoc(coll);
    return doc;
  }

  /**
   * Méthode permettant de construire un objet de type "Lucene document"
   * en se basant sur une collection de type LiusField.
   * Chaque objet LiusField contient des informations sur le nom du champs,
   * le type, le contenu etc.
   * <br/><br/>
   * Method that constructs a Lucene Document object base on a collection
   * of type LiusField. Each LiusField contains information on the name of the field,
   * the type, the content, etc.
   */
  public Document populateLuceneDoc(Collection fieldsContentAndType) {
    Document doc = new Document();
    System.out.println();
    System.out.println("==== Nouveau lucene document dans l'index ====");
    Iterator it = fieldsContentAndType.iterator();
    while (it.hasNext()) {
      Object nextObj = it.next();
      LiusField lf = (LiusField) nextObj;
      //LiusField lf = (LiusField) it.next();
      if (lf.getType().equals("Text") || lf.getType().equals("TextFileName")) {
        doc.add(Field.Text(lf.getName(), lf.getValue()));
        System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : " + lf.getValue());

      }
      else if (lf.getType().equals("TextReader")) {
        if (lf.getValueInputStreamReader() != null)
          doc.add(Field.Text(lf.getName(), lf.getValueInputStreamReader()));
        else if (lf.getValueReader() != null)
          doc.add(Field.Text(lf.getName(), lf.getValueReader()));
        System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : Texte ajouté");

      }

      else if (lf.getType().equals("lastModifiedDate")) {
        doc.add(Field.Keyword(lf.getName(),
                              DateField.timeToString(lf.getDateLong())));
        System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : " + (new Date(lf.getDateLong())));
      }

      else if (lf.getType().equals("Keyword")) {
        doc.add(Field.Keyword(lf.getName(), lf.getValue()));
        System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : " + lf.getValue());
      }
      /* else if (lf.getType().equals("KeywordDateToString")) {
           doc.add(Field.Keyword(lf.getName(), DateField.dateToString(lf.getDate())));
         System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                            " : " + lf.getDate());
       }*/
      else if (lf.getType().equals("ADate")) {

        //  try{
        //doc.add(Field.Keyword(lf.getName(), DateField.dateToString(lf.getDate())));
        doc.add(Field.Keyword(lf.getName(), (lf.getValue()).replaceAll("/", "")));
        System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : " + lf.getValue().replaceAll("/", ""));
        /*  }
          catch(java.text.ParseException e){
            e.printStackTrace();
          }*/
      }

      else if (lf.getType().equals("DateToString")) {
        doc.add(Field.Keyword(lf.getName(), DateField.dateToString(lf.getDate())));
        System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : " + lf.getDate().toString());
      }

      else if (lf.getType().equals("StringToDate")) {
        DateFormat formatter = new SimpleDateFormat(lf.getDateFormat());
        try {
          doc.add(Field.Keyword(lf.getName(),
                                DateField.dateToString(formatter.
              parse(lf.getValue()))));
          System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : " + lf.getValue());
        }
        catch (ParseException ex) {
          ex.printStackTrace();
        }

      }
      else if (lf.getType().equals("UnIndexed")) {
        doc.add(Field.UnIndexed(lf.getName(), lf.getValue()));
        System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : " + lf.getValue());
      }
      else if (lf.getType().equals("UnStored")) {
        doc.add(Field.UnStored(lf.getName(), lf.getValue()));
        System.out.println(lf.getName() + " (type = " + lf.getType() + ") " +
                           " : " + lf.getValue());
      }
    }

    return doc;
  }

  /**
   * Méthode permettant d'insérer une liste de documents Lucene dans l'index.
   * <br/><br/>
   * Method that inserts a list of Lucene documents in the index.
   */
  public synchronized void save(List luceneDocs, IndexWriter writer,
                                LiusConfig lc) throws
      LiusException {
    for (int i = 0; i < luceneDocs.size(); i++)
      save( (Document) luceneDocs.get(i), writer, lc);
  }

  /**
   * Méthode permettant d'insérer un document Lucene dans l'index
   * <br/><br/>
   * Méthod that inserts a Lucene document in the index.
   */
  public synchronized void save(Document luceneDoc, IndexWriter writer,
                                LiusConfig lc) throws
      LiusException {
    try {
      writer.addDocument(luceneDoc);
      System.out.println("**************Document Ajouté à l'index*********");
      System.out.println();
    }
    catch (IOException e) {
      try {
        writer.close();
      }
      catch (IOException j) {
        j.printStackTrace();
      }
      System.out.println("*************Document non Ajouté à l'index*********");
      e.printStackTrace();
    }
    if (lc.getOptimizeValue() != null) {
      if (lc.getOptimize()) {
        try {
          writer.optimize();
        }
        catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

  }

  /**
   * Méthode utilisée pour lancer le processus d'indexation.
   * Elle prépare le processus d'indexation pour la méthode fileDirectoryIndexing(),
   * en parsant le fichier de configuration, en initialisant les propriétés de
   * l'index et en créant l'analyseur.
   * Elle prend comme paramètres le fichier ou le répertoire à indexer,
       * le répertoire d'enregistrement de l'index et le fichier XML de configuration.
   * <br/><br/>
       * Method that begins the indexation process. It prepares the indexation process
       * with the method fileDirectoryIndexing(), by parsing the configuration file, by
   * initializing the index properties and by creating the analyser.
       * It takes as parameters the file or directory to index, the directory to save
   * the index and the XML configuration file.
   */

  public synchronized void index(String toIndex,
                                 String indexDir,
                                 String fichierXMLConfig) throws LiusException,
      IOException {

    LiusConfig lc = LiusConfigBuilder.getSingletonInstance().
        getLiusConfig(fichierXMLConfig);
    Analyzer analyzer = AnalyzerFactory.getAnalyzer(lc);
    IndexWriter writer = null;
    try {
      boolean createIndex = createIndexValue(lc.getCreateIndex(), indexDir);
      System.out.println(createIndex);

      Directory fsDir = FSDirectory.getDirectory(indexDir, createIndex);
      writer = new IndexWriter(fsDir, analyzer,
                               createIndex);
      fileDirectoryIndexing(toIndex, lc, writer);
      setIndexWriterProps(writer, lc);
    }
    catch (Exception e) {
      writer.close();
      e.printStackTrace();
    }
    finally {
      unLock(indexDir);
      writer.close();

    }
  }

  /* public void newIndex(String indexDir, LiusConfig lc) throws
       IOException {
     Analyzer analyzer = AnalyzerFactory.getAnalyzer(lc);
     IndexWriter writer = null;
     try {
       Directory fsDir = FSDirectory.getDirectory(indexDir, false);
       writer = new IndexWriter(fsDir, analyzer,
                                true);
     }
     catch (Exception e) {
       writer.close();
       e.printStackTrace();
     }
   }*/

  //nouveau
  /**
   * Méthode permettant de forcer l'ouverture de l'index de Lucene quand il est fermé.
   * <br/><br/>
   * Method that force the opening of Lucene index when it is closed.
   */
  public void unLock(String indexDir) {
    try {
      Directory directory = FSDirectory.getDirectory(indexDir, false);
      IndexReader.open(directory);
      if (IndexReader.isLocked(directory)) {
        IndexReader.unlock(directory);
      }
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  public synchronized void deleteAllDocuments(String indexDir) {
    try {
      Directory directory = FSDirectory.getDirectory(indexDir, false);
      IndexReader ir = IndexReader.open(directory);
      int num = ir.numDocs();
      for (int i = 0; i <= num - 1; i++) {
        ir.delete(i);
      }
      ir.close();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  public synchronized void unDeleteAllDocuments(String indexDir) {
    try {
      Directory directory = FSDirectory.getDirectory(indexDir, false);
      IndexReader ir = IndexReader.open(directory);
      ir.undeleteAll();
      ir.close();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  public synchronized void newIndex(String indexDir) {
    try {
      Directory directory = FSDirectory.getDirectory(indexDir, true);
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }

  /**
   * Méthode permettant d'initialiser les propriétés de l'index si ces dernières ont
   * été placées dans le fichier de configuration.
   * <br/><br/>
   * Method that initializes the properties of the index if those were placed in the
   * configuration file.
   */
  public void setIndexWriterProps(IndexWriter writer, LiusConfig lc) {
    if (lc.getMergeFactor() != null)
      writer.mergeFactor = (new Integer(lc.getMergeFactor())).intValue();
    if (lc.getMaxMergeDocs() != null)
      writer.maxMergeDocs = (new Integer(lc.getMaxMergeDocs())).intValue();
  }

  /**
       * Méthode appelée par la méthode index(). Elle permet d'effectuer le processus
   * d'indexation.
   * <br/><br/>
   * Method called by index(). It processes the indexation.
   */
  private void fileDirectoryIndexing(String toIndex,
                                     LiusConfig lc,
                                     IndexWriter writer) throws IOException {

    String sep = System.getProperty("file.separator");
    File typFD = new File(toIndex);

    if (typFD.isFile()) {
      fileProcessing(toIndex, lc, writer);
    }
    else if (typFD.isDirectory()) {
      File[] liste = (new File(toIndex)).listFiles();
      if (lc.getMixteIndexingElements().size() > 0) {
        Indexer mi = new MixteIndexer();
        Document doc = mi.createLuceneDocument(toIndex, lc);
        try {
          save(doc, writer, lc);
        }
        catch (LiusException e) {
          writer.close();
          e.printStackTrace();
        }
      }
      else {
        for (int i = 0; i < liste.length; i++) {
          String fileToIndexB = toIndex + sep + liste[i].getName();
          fileProcessing(fileToIndexB, lc, writer);
        }
      }
    }
  }

  //nouveau
  /**
   * Méthode permettant d'indexer des Java Beans. Elle prend comme argument une
   * liste d'objets, le chemin de l'index et le fichier de configuration.
   * <br/><br/>
   * Method that indexes JavaBeans. It takes as parameters a list of objects, the path
   * of the index and the configuration file.
   */
  public synchronized void indexJavaBeans(List beans, String indexDir,
                                          String fichierXMLConfig) throws
      IOException {

    LiusConfig lc = LiusConfigBuilder.getSingletonInstance().
        getLiusConfig(fichierXMLConfig);
    Analyzer analyzer = AnalyzerFactory.getAnalyzer(lc);
    IndexWriter writer = null;

    try {
      boolean createIndex = createIndexValue(lc.getCreateIndex(), indexDir);
      Directory fsDir = FSDirectory.getDirectory(indexDir, createIndex);
      writer = new IndexWriter(fsDir, analyzer, createIndex);
      setIndexWriterProps(writer, lc);
      for (int i = 0; i < beans.size(); i++) {
        BeanIndexer joi = new BeanIndexer();
        Document doc = joi.createLuceneDocument(beans.get(i), lc);
        save(doc, writer, lc);
      }

    }
    catch (Exception e) {
      writer.close();
      unLock(indexDir);
      e.printStackTrace();
    }
    finally {
      writer.close();
    }
  }

  //nouveau
  /**
   * Méthode permettant d'indexer un seul objet JavaBean.
   * <br/>
   * Method that indexes only one JavaBean.
   */
  public synchronized void indexJavaBean(Object bean, String indexDir,
                                         String fichierXMLConfig) throws
      IOException {

    LiusConfig lc = LiusConfigBuilder.getSingletonInstance().
        getLiusConfig(fichierXMLConfig);
    Analyzer analyzer = AnalyzerFactory.getAnalyzer(lc);
    IndexWriter writer = null;

    try {
      boolean createIndex = createIndexValue(lc.getCreateIndex(), indexDir);
      Directory fsDir = FSDirectory.getDirectory(indexDir, createIndex);
      writer = new IndexWriter(fsDir, analyzer, createIndex);
      setIndexWriterProps(writer, lc);

      BeanIndexer joi = new BeanIndexer();
      Document doc = joi.createLuceneDocument(bean, lc);
      save(doc, writer, lc);

    }
    catch (Exception e) {
      writer.close();
      unLock(indexDir);
      e.printStackTrace();
    }
    finally {
      writer.close();
    }
  }

  /**
   * Méthode appelée par la la méthode fileDirectoryIndexing(), pour indexer
   * en fonction du type de fichier.
   * <br/><br/>
   * Method called by fileDirectoryIndexing(), for indexing related to the file type.
   */
  private void fileProcessing(String fileToIndex, LiusConfig lc,
                              IndexWriter writer) {
    Indexer indexer = null;
    try {
      if ( (lc.getXmlFileFields() != null &&
            fileToIndex.toLowerCase().endsWith(".xml")) ||
          (lc.getPdfFields().size() > 0 &&
           fileToIndex.toLowerCase().endsWith(".pdf")) ||
          (lc.getMsWordFields().size() > 0 &&
           fileToIndex.toLowerCase().endsWith(".doc")) ||
          (lc.getHtmlFields().size() > 0 &&
           (fileToIndex.toLowerCase().endsWith(".html") ||
            fileToIndex.toLowerCase().endsWith(".htm")) ||
           (lc.getRtfFields().size() > 0 &&
            fileToIndex.toLowerCase().endsWith(".rtf")) ||
           (lc.getExcelFields().size() > 0 &&
            fileToIndex.toLowerCase().endsWith(".xls")))) {
        indexer = IndexerFactory.getIndexer(fileToIndex);
        if (indexer != null) {
          Document doc = indexer.createLuceneDocument(fileToIndex, lc);
          save(doc, writer, lc);
        }
      }
      else if (lc.getXmlNodesFields().size() > 0 &&
               fileToIndex.toLowerCase().endsWith(".xml")) {
        XmlNodeIndexer nodeIndexer = new XmlNodeIndexer();
        if (nodeIndexer != null) {
          List lucenedocsListe = nodeIndexer.
              createLuceneDocForEachNodeOfDocument(
              fileToIndex, lc.getXmlNodesFields());
          save(lucenedocsListe, writer, lc);
        }
      }
    }
    catch (LiusException e) {
      try {
        writer.close();
      }
      catch (IOException j) {
        j.printStackTrace();
      }
      e.printStackTrace();
    }
  }

  /**
   * Méthode permettant d'effacer un document dans l'index. Elle prend comme
       * arguments le répertoire de l'index, le nom du champs et le contenu recherché.
   * <br/><br/>
   * Method that erases a document from the index. Its parameters are the directory of
   * the index, the name of the field and the content searched.
   */
  public synchronized void deleteDoc(String indexDir, String field,
                                     String content) throws
      LiusException {
    try {
      Directory fsDir = FSDirectory.getDirectory(indexDir, false);
      IndexReader indexReader = IndexReader.open(fsDir);
      Term t = new Term(field, content);
      indexReader.delete(t);
      indexReader.close();
      System.out.println("Document supprimé");
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * Méthode permettant d'effacer un document dans l'index.
   * Elle prend comme arguments le répertoire de l'index et un objet de type
   * Lucene Term.
   * <br/><br/>
       * Method that erases a document from the index. Its parameters are the directory
   * of the index and a Lucene term object.
   */
  public synchronized void deleteDoc(String indexDir, Term t) throws
      LiusException {
    try {
      Directory fsDir = FSDirectory.getDirectory(indexDir, false);
      IndexReader indexReader = IndexReader.open(fsDir);
      indexReader.delete(t);
      indexReader.close();
      System.out.println("Document supprimé");
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * Méthode permettant de mettre à jour un document dans l'index.
       * Elle prend comme arguments le répertoire de l'index,  un objet de type lucene
   * Term, le fichier à indexer à la place de celui trouvé et le fichier XML de
   * configuration qui servira à l'indexation.
   * <br/><br/>
   * Method that updated a document in the index. Its parameters are the directory of
   * the index, an Lucene Term object, the file to index in place of the one found and
   * the XML configuration file which will serve for indexing.
   *
   * */
  public synchronized void updateDoc(String rep, Term t, String fileToReindex,
                                     String configFile) throws LiusException,
      IOException {

    deleteDoc(rep, t);
    getSingletonInstance().index(fileToReindex, rep, configFile);
    System.out.println("Document mis à jour");

  }

  /**
   * Méthode permettant de mettre à jour un document dans l'index. Elle prend
       * comme arguments le repertoire de l'index,  le nom du champs qui doit contenir
   * la valeur recherchée, le fichier à indexer à la place de
       * celui trouvé et le fichier XML de configuration qui servira à la réindexation.
   * <br/><br/>
   * Method that updates a document in the index. Its parameters are the directory of
       * the index, the name of the field which will contain the searched value, the
   * searched value, the file to index in place of the one found and the XML
   * configuration which will serve for indexing.
   */
  public synchronized void updateDoc(String rep, String field, String content,
                                     String fileToReindex, String configFile) throws
      LiusException, IOException {
    deleteDoc(rep, field, content);
    getSingletonInstance().index(fileToReindex, rep, configFile);
    System.out.println("Document mis à jour");
  }

  public boolean createIndexValue(String valueCreateIndex, String indexDir) {
    boolean createIndex = false;
    String sep = File.separator;
    if (valueCreateIndex.equals("true"))
      createIndex = true;
    else if (valueCreateIndex.equals("false"))
      createIndex = false;
    else if (valueCreateIndex.equals("auto")) {
      createIndex = !indexExists(indexDir);
    }
    return createIndex;
  }

  /**
   * Méthode permettant de vérifier le répertoire de sortie de l'index.
   * S'il n'existe pas il sera crée.
   * <br/><br/>
   * Method for verifying the output directory of index. If it does not exist
   * it will be created.
   */

  public boolean indexExists(String indexDir) {
    return IndexReader.indexExists(indexDir);
  }

}