/*
 * Created on 11 mai 2005
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.injac.indexing.index.oaipmh;

import org.injac.indexing.index.*;
import org.injac.indexing.Consts;

import java.io.File;

import java.io.IOException;

import java.util.ResourceBundle;
import java.util.HashMap;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.injac.indexing.exceptions.ConnectionException;
import org.injac.indexing.util.*;
import org.injac.indexing.beans.ReportBean;
import ca.ulaval.bibl.lius.Lucene.AnalyzerFactory;
import ca.ulaval.bibl.lius.Lucene.LuceneActions;
import ca.ulaval.bibl.lius.config.LiusConfig;
import ca.ulaval.bibl.lius.config.LiusConfigBuilder;
import ca.ulaval.bibl.lius.index.Indexer;

// import ca.ulaval.bibl.lius.index.IndexerFactory;
import org.injac.indexing.index.GenericIndexing;
import org.injac.indexing.lius.CustomizedIndexerFactory;

/**
 * Classe executant l'indexation à partir d'une chaine XML
 * 
 * Usage : 1/ créer une instance
 * 
 * 2/ appeler index autant de fois qu'il y a de documents à indexer
 * 
 * 3/ déréférencer pour la désallocation mémoire
 * 
 * @authors François Jannin
 */
public class StringOAIIndexing extends GenericIndexing implements IIndexing{

	Indexer indexer = null;

	Analyzer analyzer = null;

	static Logger logger = Logger.getLogger(StringOAIIndexing.class);

	private ResourceBundle resBundle;

	protected String indexPath;

	protected String indexName;


	// map of yet indexed documents (key : URLFieldName from propetiy
	// exist.urlField.name
	private HashMap yetIndexedDocs;

	// field used for repoert and update tests
	private String URLFieldName;

	// config file for indexing
	private String liusConfigFile;

	// object class for config
	protected LiusConfig liusConfig;

	private ReportBean reportBean;

	private IndexWriter indexWriter = null;
	
	private boolean initialized = false;
	
	protected int optimizeFrequency = 100;
	protected int count = 0;

	/**
	 * Uses property file indexation.properties : - exist.Config : configuration
	 * file name for indexing - exist.urlField.name : field used in report as
	 * url and as comparator for managing yet indexed documents - descriptionMD :
	 * field used for report description - titleMD
	 * 
	 * @param indexName :
	 *            name of the index used
	 * @param contextPath :
	 *            file path for config file, such as complete path is :
	 *            [contexPath]/[exist.Config property]
	 */
	/**
	 * Default constructor for Class.forName instanciation
	 * to be followed by calling postCreate() 
	 */
	public StringOAIIndexing() {
		super();// !!!
	}

	public StringOAIIndexing(String indexName) {
		super();
		
		this.indexName = indexName;

		initialized = init();
	}

	public void postCreate(String indexName) {
		this.indexName = indexName;
		initialized = init();
	}

	
	
	protected IndexWriter getIndexWriter(){
		if(indexWriter==null){
			logger.error("indexWriter==null");
		}
		return indexWriter;
	}
	
	/**
	 * initialization
	 * 
	 */
	private boolean init() {
		boolean result = false;
		resBundle = ResourceBundle.getBundle("indexation");
		String optimizeFrequencyStr = resBundle.getString("oai.optimize.frequency");
		if(optimizeFrequencyStr != null){
			try{
				int propFreq = Integer.parseInt(optimizeFrequencyStr);
				optimizeFrequency = propFreq;
			}catch(Exception e){
				// NOP
			}
		}
		indexPath = resBundle.getString("indexPath");
		descriptionMD = resBundle.getString("descriptionMD");
		titleMD = resBundle.getString("titleMD");
		liusConfigFile = resBundle.getString("oai.Config");
		URLFieldName = resBundle.getString("oai.urlField.name");
		logger.debug("using liusConfigFile : " + liusConfigFile);
		try {
			liusConfig = LiusConfigBuilder.getSingletonInstance()
					.getLiusConfig(liusConfigFile);
			indexer = CustomizedIndexerFactory.getIndexer("xml");
			analyzer = AnalyzerFactory.getAnalyzer(liusConfig);

		} catch (Exception e) {
			logger.error(e + e.getLocalizedMessage());
			return result;
		}
		// bean for report
		reportBean = new ReportBean();
		updateYetIndexedDocs();
		return true;

	}
	/**
	 * Create index Writer
	 *
	 */
	public void open(boolean updateMode){
		count= 0;
		String indexDir = indexPath + "/" + indexName;
		try{
			// delete documents if not update mode
			if(!updateMode){
				LuceneActions.getSingletonInstance().deleteAllDocuments(indexDir);
			}
			
			boolean createIndex = LuceneActions.getSingletonInstance()
					.createIndexValue(liusConfig.getCreateIndex(), indexDir);

			indexWriter = new IndexWriter(indexDir, analyzer, createIndex);

			LuceneActions.getSingletonInstance().setIndexWriterProps(indexWriter,
					liusConfig);
		}catch(Exception e){
			logger.error("Can't open indexWiter : "+e + " : "+ e.getLocalizedMessage());
		}

	}
	/**
	 * Close index Writer
	 *
	 */
	public void close(){
		try{
			indexWriter.optimize();
			indexWriter.close();
		}catch(Exception e){
			logger.error(e + " : "+ e.getLocalizedMessage());
		}	
	}
	
	/**
	 * builds the list of yet indexed docs with their urls as definied in
	 * exist.urlField.name property
	 * 
	 */
	public void updateYetIndexedDocs() {
		// map for existing docs
		yetIndexedDocs = buildExistingURLList(indexPath + "/" + indexName);
	}

	/**
	 * update the list of yet indexed docs with their urls as defined in
	 * exist.urlField.name property called each time a doc is newly indexed
	 */
	protected void updateYetIndexedDocs(Document doc, int docCount) {
		if (yetIndexedDocs == null) {
			yetIndexedDocs = new HashMap();
		}
		// map for existing docs
		String urlTest = doc.get(URLFieldName);
		if (urlTest != null) {
			yetIndexedDocs.put(urlTest, "" + (docCount - 1));
		}
	}

	public void closeReport() {
		String beanReportPath = ReportBean.getPath(indexPath, indexName);
		BeanPhoenix.saveBean(reportBean, beanReportPath);
	}

	/**
	 * write a OAI record given as a String on file system in order to LIUS to
	 * index it; call actual indexing method Fonction qui crée un fichier à
	 * partir d'un ernegistrement OAI passé comme chaîne sur le File System;
	 * appelle la fonction d'indexation proprement dite
	 * 
	 * @param url :
	 *            webdav url of an OAI record
	 * @param user :
	 *            user for webdav connection
	 * @param password :
	 *            password for webdav connection
	 * @return url used as renderURL
	 * @throws ConnectionException
	 */
	public String index(String record, String repositoryName, boolean update) {
		if (!initialized)
			return null;
		String result = null;

		boolean indexabil = record != null;
		if (indexabil) {
			try {
				result = doIndex(record, repositoryName,update);
			} catch (Exception e) {
				logger.error("Error " + e.getLocalizedMessage());
				return null;
			}
		}
		return result;
	}

	/**
	 * Function d'indexation des métadonnées OAI envoyées sous form de chaîne
	 * 
	 * @param xmlRecord :
	 *            XML OAI record
	 * 
	 * @return url defined by URLFieldName or null
	 */
	private String doIndex(String xmlRecord, String repositoryName,
			boolean update) {
		String url = null;
		// get now time for duration
		java.util.Date date = new java.util.Date();
		long begin = date.getTime();

		String toIndex = xmlRecord;
		//IndexWriter indexWriter = null;

		try {
			Document luceneDoc = indexer.createLuceneDocument(toIndex,
					liusConfigFile);
			int existIndex = -1;
			// Test renderURL pour voir si le document est déja indexé
			if (update) {
				if ((existIndex = exists(luceneDoc)) != -1) {
					// doc yet indexed
					// test doc deletion before re-indexing
					// TODO : unlock index set by writer while deleting with reader
					if (!delete(indexPath + "/" + indexName, existIndex)) {
						logger
								.error("can't destroy document for updating. Not updated");
						return url;
						
					}
				}
			}
			// test existence/creation of index
		/* MODIFS OPENCLOSE
			String indexDir = indexPath + "/" + indexName;
			boolean createIndex = LuceneActions.getSingletonInstance()
					.createIndexValue(liusConfig.getCreateIndex(), indexDir);

			indexWriter = new IndexWriter(indexDir, analyzer, createIndex);

			LuceneActions.getSingletonInstance().setIndexWriterProps(indexWriter,
					liusConfig);
		FIN MODIFS OPENCLOSE */			
			String indexedURL = luceneDoc.get(URLFieldName);
			if (indexedURL == null) {
				logger.error("Can't find field : " + URLFieldName
						+ " as mapped in " + liusConfigFile + " : not indexed");
				//indexWriter.close();
				return url;
			} else {
				url = indexedURL;
				// process namespace field
				String namespace = luceneDoc.get(Consts.INDEX_NAMESPACE);
				if (namespace != null) {
					int spaceIndex = -1;
					if ((spaceIndex = namespace.indexOf(' ')) != -1) {
						namespace = namespace.substring(0, spaceIndex);
					}
					// replace field
					luceneDoc.removeField(Consts.INDEX_NAMESPACE);
					luceneDoc.add(Field.Keyword(Consts.INDEX_NAMESPACE,
							namespace));

				} else {
					// TODO tests pour établir un namespace - à changer
					if (luceneDoc.get("gen_catalog") != null) {
						namespace = "http://ltsc.ieee.org/xsd/LOM";
						luceneDoc.add(Field.Keyword(Consts.INDEX_NAMESPACE,
								namespace));
					} else if (luceneDoc.get("dc_title") != null) {
						namespace = "http://www.openarchives.org/OAI/2.0/oai_dc/";
						luceneDoc.add(Field.Keyword(Consts.INDEX_NAMESPACE,
								namespace));
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("namespace for " + url + " : " + namespace);
				}

				addCommonFields(luceneDoc, luceneDoc);
				if(repositoryName==null){
					logger.error("repositoryName is null");
					repositoryName="non name";
				}
				luceneDoc.add(Field
						.Text(Consts.REPOSITORY_NAME, repositoryName));
				// Save doc in index
				LuceneActions.getSingletonInstance().save(luceneDoc, getIndexWriter(),
						liusConfig);
				int docCount = getIndexWriter().docCount();
				if(count%optimizeFrequency == 0){
					getIndexWriter().optimize();
				}
				count++;

		//		indexWriter.close();
				// update list of yet indexed docs
				updateYetIndexedDocs(luceneDoc, docCount);
				date = new java.util.Date();
				long end = date.getTime();
				int duration = (int) ((end - begin));
				// add line in report
				if(reportBean==null){
					logger.error("reportBean==null");
				}
				reportBean.addLine(indexedURL, "ok", duration,
						getTitle(luceneDoc), getDescription(luceneDoc), false);
				return url;
			}

		} catch (Exception e) {
			logger.error(e + " : " + e.getLocalizedMessage());
			/* MODIFS OPENCLOSE
			if (indexWriter != null) {
				try {
					indexWriter.close();
				} catch (Exception ee) {
					logger.error(ee + " : Can't close stream");
				}
			}
			*/
			reportBean.addLine(url, e.getLocalizedMessage(), 0);
			logger.error(e.getLocalizedMessage());
			return null;
		}
		/* MODIFS OPENCLOSE
		catch (ca.ulaval.bibl.lius.Exception.LiusException e) {
			
			if (indexWriter != null) {
				try {
					indexWriter.close();
				} catch (Exception ee) {
					logger.error(ee + " : Can't close stream");
				}
			}
			
			reportBean.addLine(url, e.getLocalizedMessage(), 0);
			logger.error(e.getLocalizedMessage());

			return null;
		}
	*/
	}

	public void reportAlready(ReportBean.ReportLine line) {
		reportBean.getList().add(line);
	}

	/**
	 * Hook method for getting title from LuceneDocument
	 */
	protected String getTitle(Object obj) {
		Document doc = null;

		if (obj instanceof Document) {
			doc = (Document) obj;
		} else {
			logger.error(obj +"is not instanceof Document");
			return null;
		}
		String result = "";
		StringTokenizer st = new StringTokenizer(titleMD, ",");
		while (st.hasMoreTokens()) {
			result = doc.get(st.nextToken());
			if (result != null)
				break;
		}
		return result;
	}

	/**
	 * Hook method for getting description from LuceneDocument
	 */
	protected String getDescription(Object obj) {
		Document doc = null;
		if (obj instanceof Document) {
			doc = (Document) obj;
		} else {
			return null;
		}
		String result = "";
		StringTokenizer st = new StringTokenizer(descriptionMD, ",");
		while (st.hasMoreTokens()) {
			result = doc.get(st.nextToken());
			if (result != null)
				break;
		}
		return result;
	}

	/**
	 * Teste s'il existe dans l'index un fichier avec le champ URLFieldName
	 * contenu dans le parametre url
	 * 
	 * @param indexDir
	 * @param url
	 * @return int : yet indexed document index, -1 if no match
	 */
	private int exists(Document doc) {
		if (yetIndexedDocs != null) {
			String url = doc.get(URLFieldName);
			if (url != null) {
				String DocIndex = (String) yetIndexedDocs.get(url);
				if (DocIndex != null) {
					return Integer.parseInt(DocIndex);
				}
			}

		}
		return -1;
	}

	/**
	 * Construit une map (URLFieldName, index du document dans l'index)
	 * 
	 * @param indexDir
	 * @param url
	 * @return Document : null if no match
	 */
	private HashMap buildExistingURLList(String indexDir) {
		IndexReader reader = null;
		HashMap result = new HashMap();
		try {
			reader = IndexReader.open(indexDir);
			String urlTest = null;
			int n = reader.numDocs();
			for (int i = 0; i < n; i++) {
				if (!reader.isDeleted(i)) {
					Document d = reader.document(i);
					urlTest = d.get(URLFieldName);
					if (urlTest != null) {
						result.put(urlTest, "" + i);
					}
				}
			}
			reader.close();

		} catch (IOException ioe) {
			ioe.printStackTrace();
			if (reader != null) {
				try {
					reader.close();
				} catch (Exception e) {
					logger.error(e + " : stream not closed");
				}

			}
			return null;
		}
		return result;
	}

	// deleting a record from its index position
	private boolean delete(String indexDir, int docIndex) {
		try {
			Directory directory = FSDirectory.getDirectory(indexDir, false);
			IndexReader reader = IndexReader.open(directory);
			reader.delete(docIndex);
			reader.close();
			return true;
		} catch (IOException ioe) {
			ioe.printStackTrace();
			return false;
		}

	}

	// deleting a record from its URLFieldName's field value
	private boolean delete(String indexDir, String url) {
		try {
			Directory directory = FSDirectory.getDirectory(indexDir, false);
			IndexReader reader = IndexReader.open(directory);
			String renderURL = null;

			int n = reader.numDocs();
			for (int i = 0; i < n; i++) {
				if (!reader.isDeleted(i)) {
					Document d = reader.document(i);
					renderURL = d.get(URLFieldName);
					if (renderURL.equals(url)) {
						reader.delete(i);
						reader.close();
						directory.close();
						return true;
					}
				} else {
					n++;
				}
			}
			reader.close();
			directory.close();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
		return false;
	}

}
