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

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;


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


import org.apache.commons.httpclient.HttpURL;
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.apache.webdav.lib.WebdavFile;
import org.apache.webdav.lib.WebdavResource;
import org.injac.indexing.exceptions.ConnectionException;
import org.injac.indexing.index.GenericIndexing;
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;

/**
 * Classe executant l'indexation proprement dite d'un fichier WebDav
 * 
 * Usage : 
 *  1/ créer une instance
 *  2/ appeler createTempDirs
 *  3/ appeler index autant de fois qu'il y a de documents à indexer
 *  4/ apperler deleteTempDirs
 *  5/ déréférencer pour la désallocation mémoire
 * @authors Marius Mirea, François Jannin
 */
public class OAIIndexing extends GenericIndexing {
	static Logger logger = Logger.getLogger(OAIIndexing.class);

	private ResourceBundle resBundle;
	private String indexPath;
	private String indexName;
	private String contextPath;
	// 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
	private LiusConfig liusConfig;

	

	private ReportBean reportBean;
	/**
	 * 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] 
	 */
	public OAIIndexing (String indexName, String contextPath) {
		super();
		this.contextPath = contextPath;
		this.indexName = indexName;
		init();
	}
	/**
	 * initialization 
	 *
	 */
	private void init(){
		resBundle = ResourceBundle.getBundle("indexation");
		indexPath = resBundle.getString("indexPath");
		descriptionMD = resBundle.getString("descriptionMD");
		titleMD = resBundle.getString("titleMD");
		liusConfigFile = contextPath + File.separator+ resBundle.getString("exist.Config");
		URLFieldName = resBundle.getString("exist.urlField.name");
		logger.debug("using liusConfigFile : " + liusConfigFile);
		try {
			liusConfig = LiusConfigBuilder.getSingletonInstance()
					.getLiusConfig(liusConfigFile);
		} catch (Exception e) {
			logger.error(e + e.getLocalizedMessage());
		}
		// bean for report
		reportBean = new ReportBean();
		updateYetIndexedDocs();

	}
	/**
	 * 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 definied 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));
		}
	}
	
	/**
	 * create temp directories
	 * 
	 */
	public boolean createTempDirs() {
		try {
			File temp = new File(indexPath + "/Temp");
			temp.mkdir();
			temp = new File(indexPath + "/Temp/" + indexName + "/");
			temp.mkdir();
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	public boolean deleteTempDirs() {
		boolean result = false;
		try {
			File temp = new File(indexPath + "/Temp/" + indexName + "/");
			result =temp.delete();
			temp = new File(indexPath + "/Temp");
			result &=temp.delete();
		} catch (Exception e) {
			logger.error("temp directories not deleted");
			return false;
		}
		return result;
	}
	public void closeReport() {
		String beanReportPath = ReportBean.getPath(indexPath, indexName);
		BeanPhoenix.saveBean(reportBean, beanReportPath);
	}

	/**
	 * Fetch a webdav file and write it on file system in order to LIUS to index it; call actual indexing method
	 * Fonction qui rapatrie un fichier webdav et le crée 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
	 * @parama password : password for webdav connection 
	 * @throws ConnectionException
	 */
	public boolean index(String url, String user, String password)
			throws ConnectionException {
		boolean result = false;
		WebdavFile webdavFile = null;
		try {
			HttpURL hrl = new HttpURL(url);
			hrl.setUserinfo(user, password);
			webdavFile = new WebdavFile(hrl);
			if (!webdavFile.exists()) {
				throw new ConnectionException();
			}
		} catch (IOException ioe) {
			logger.error("IOException : " + ioe.getLocalizedMessage());
			throw new ConnectionException();

		} catch (ConnectionException ce) {
			logger.error("Resource doesn't exist, "
					+ "or wrong credentials.<br>");
			throw new ConnectionException();
		} catch (NullPointerException npe) {
			logger.error("Resource doesn't exist, "
					+ "or wrong credentials.<br>");
			throw new ConnectionException();
		} catch (Exception we) {
			throw new ConnectionException();

		}

		String webdavName = webdavFile.getName();
		String webdavPath = webdavFile.getAbsolutePath();

		String subUrl = webdavPath.substring(0, webdavPath.length()
				- webdavName.length() - 1);
		boolean indexabil = webdavFile.isFile();
		if (indexabil) {
			File file = new File(indexPath + "/Temp/" + indexName + "/"
					+ webdavName+ ".xml");

			try {
				file.createNewFile();
				HttpURL hrl = new HttpURL(webdavPath);
				hrl.setUserinfo(webdavFile.getUser(), webdavFile.getPass());
				WebdavResource wdr = new WebdavResource(hrl);
				wdr.getMethod(file);
			} catch (IOException ie) {
				logger.error("IO Exception while indexing : "
						+ ie.getLocalizedMessage());
			}
			try {
				result = doIndex(file.getAbsolutePath(), subUrl);
			} catch (Exception e) {
				logger.error("Error " + e.getLocalizedMessage());

			}
			file.delete();
			return result;
		}
		return result;
	}
	
	/**
	 * 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
	 * @parama password : password for webdav connection 
	 * @throws ConnectionException
	 */
	public boolean index(String record) {
		boolean result = false;
		

		String webdavName = "";//webdavFile.getName();
		String webdavPath = "";//webdavFile.getAbsolutePath();

		String subUrl = "no url";
				
		boolean indexabil = record != null;
		if (indexabil) {
			File file = new File(indexPath + "/Temp/" + indexName + "/"
					+ webdavName+ ".xml");

			try {
				file.createNewFile();
				FileOutputStream fos = new FileOutputStream(file,false);
				fos.write(record.getBytes("UTF-8"));
				fos.flush();
				fos.close();
				
			} catch (IOException ie) {
				logger.error("IO Exception while indexing : "
						+ ie.getLocalizedMessage());
			}
			try {
				result = doIndex(file.getAbsolutePath(), subUrl);
			} catch (Exception e) {
				logger.error("Error " + e.getLocalizedMessage());

			}
			file.delete();
			return result;
		}
		return result;
	}

	
	

	/**
	 * Function d'indexation proprement dite. Elle fait l'indexation avec le
	 * logiciel LIUS d'un fichier et ajoute aussi a l'index des champs contenant
	 * les metadonnees sur le fichier
	 * 
	 * @param filePath
	 * @param url
	 * @param webdavName
	 * @param metadatas
	 */
	private boolean doIndex(String filePath, String url) {

		// get now time for duration
		java.util.Date date = new java.util.Date();
		long begin = date.getTime();

		String indexDir = indexPath + "/" + indexName;
		String toIndex = filePath;

		Analyzer analyzer = AnalyzerFactory.getAnalyzer(liusConfig);
		IndexWriter writer = null;
		try {
			
			Indexer indexer = IndexerFactory.getIndexer(toIndex);
			Document luceneDoc = indexer.createLuceneDocument(toIndex,
					liusConfigFile);
			int existIndex = -1;
			// Test renderURL pour voir si le document est déja indexé
			if ((existIndex = exists(luceneDoc)) != -1) {
				// doc yet indexed
				// test doc deletion before re-indexing
				if(!delete(indexPath + "/" + indexName, existIndex)){
					logger.error("can't destroy document for updating. Not updated");
					return true;
				}
			}
			// test existence/creation of index
			boolean createIndex = LuceneActions.getSingletonInstance()
			.createIndexValue(liusConfig.getCreateIndex(), indexDir);
			
			writer = new IndexWriter(indexDir, analyzer, createIndex);
			LuceneActions.getSingletonInstance().setIndexWriterProps(writer,liusConfig);
			
				
			String indexedURL = luceneDoc.get(URLFieldName);
			if(indexedURL==null){
				logger.error("Can't find field : "+URLFieldName+" as mapped in "+liusConfigFile+" : not indexed");
				writer.close();
				return false;
			}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));
					
				}
				if(logger.isDebugEnabled()){
					logger.debug("namespace for "+ url + " : "+namespace);
				}
				
				addCommonFields(luceneDoc, luceneDoc);
				
				// Save doc in index					 
				LuceneActions.getSingletonInstance().save(luceneDoc, writer,
						liusConfig);
				int docCount = writer.docCount();
				writer.optimize();
				writer.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
				reportBean.addLine(indexedURL, "ok", duration, getTitle(luceneDoc), getDescription(luceneDoc),	false);
				return true;
			}
			
		} catch (IOException e) {
			if(writer != null){
				try{
					writer.close();
				}catch(Exception ee){
					logger.error(ee+" : Can't close stream");
				}
			}
			reportBean.addLine(url, e.getLocalizedMessage(), 0);
			logger.error(e.getLocalizedMessage());
			
			return false;
		} catch (ca.ulaval.bibl.lius.Exception.LiusException e) {
			if(writer != null){
				try{
					writer.close();
				}catch(Exception ee){
					logger.error(ee+" : Can't close stream");
				}
			}
			reportBean.addLine(url, e.getLocalizedMessage(), 0);
			logger.error(e.getLocalizedMessage());
			
			return false;
		}
	}

	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{
			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;
	}
	
	
}
