/* 
 *  Copyright (C) 2000 - 2013 TagServlet Ltd
 *
 *  This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
 *  
 *  OpenBD is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  Free Software Foundation,version 3.
 *  
 *  OpenBD 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 OpenBD.  If not, see http://www.gnu.org/licenses/
 *  
 *  Additional permission under GNU GPL version 3 section 7
 *  
 *  If you modify this Program, or any covered work, by linking or combining 
 *  it with any of the JARS listed in the README.txt (or a modified version of 
 *  (that library), containing parts covered by the terms of that JAR, the 
 *  licensors of this Program grant you additional permission to convey the 
 *  resulting work. 
 *  README.txt @ http://openbd.org/license/README.txt
 *  
 *  http://openbd.org/
 *  
 *  $Id: Collection.java 2523 2015-02-22 16:23:11Z alan $
 */

package com.bluedragon.search.collection;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.FileSystems;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.NIOFSDirectory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Constants;

import com.bluedragon.search.AnalyzerFactory;
import com.bluedragon.search.DocumentWrap;
import com.bluedragon.search.index.DocumentWriter;
import com.nary.io.FileUtils;


public class Collection extends Object {

	private	String 	name;
	private	long		created;
	private boolean	bStoreBody;
	private String	language, collectionpath;
	
	// Lucene specific variables
	private Directory	directory = null;
	private IndexSearcher	indexsearcher = null;
	private IndexWriter	indexwriter = null;
	private int	totalDocs = -1;
	private DocumentWriter documentwriter = null;
	private long lastUsed	= System.currentTimeMillis();
	
	public String toString(){
		return "[Collection name=" + name + "; path=" + collectionpath + "]";
	}
	
	public long getTimeSinceLastUsed(){
		return System.currentTimeMillis() - lastUsed;
	}
	
	public void setName(String string) throws Exception {
		this.name	= string;
		
		if ( this.name == null || this.name.length() == 0 )
			throw new Exception("invalid collection name");
	}


	public void setLanguage(String string) {
		this.language	= string;
	}

	public String getLanguage(){
		return this.language;
	}
	
	public void setStoreBody(boolean boolean1) {
		bStoreBody	= boolean1;
	}

	public boolean bStoreBody(){
		return bStoreBody;
	}

	public String getName() {
		return name;
	}
	
	public long getLastModified(){
		return lastUsed;
	}

	public long getCreated(){
		return created;
	}
	
	public String getPath(){
		return collectionpath;
	}
	
	public void setDirectory(String path) throws Exception {
		File	filePath;
		if ( !path.endsWith(name) )
			filePath = new File( path, name );
		else
			filePath = new File( path );

		if ( !filePath.exists() || !filePath.isDirectory() )
			throw new Exception( "invalid collection path: " + path );
		
		collectionpath	= filePath.getCanonicalPath();
	}

	
	public void close(){
		closeReader();
		
		if ( indexwriter != null ){
			try {
				indexwriter.close();
			} catch (IOException e){}
			indexwriter = null;
		}
	}
	
	
	public void closeReader(){
		if ( indexsearcher != null ){
			try {
				indexsearcher.getIndexReader().close();
			} catch ( IOException ignoreThisException ) {}
			indexsearcher = null;
		}
	}
	
	/**
	 * Retrieves the IndexSearcher object.  This object is cached against this collection and is intended
	 * to be used amongst several threads.   This is the recommended approach.
	 * 
	 * If new content is added to this collection then it won't be available for searching until a new
	 * indexsearcher is created
	 * 
	 * @return
	 * @throws CorruptIndexException
	 * @throws IOException
	 */
	public synchronized IndexSearcher getIndexSearcher() throws CorruptIndexException, IOException{
		lastUsed	= System.currentTimeMillis();
		
		if ( indexsearcher != null )
			return indexsearcher;
		
		setDirectory();
		indexsearcher	= new IndexSearcher( DirectoryReader.open(directory) );
		
		totalDocs			= indexsearcher.getIndexReader().numDocs();
		
		return indexsearcher;
	}
	
	
	public int	getTotalDocs(){
		return totalDocs;
	}
	
	
	public int size(){
		int size = 0;
		try {
			setDirectory();

			String[] files = directory.listAll();
  		for ( int i = 0; i < files.length; i++ ){
				try {
					size += directory.fileLength( files[i] );
				} catch (IOException e) {}
			}
		} catch (IOException e1) {}
		
		return size;
	}
	
	private void setDirectory() throws IOException {
		if ( directory != null )
			return;
		
    if (Constants.WINDOWS) {
    	directory = new SimpleFSDirectory( FileSystems.getDefault().getPath(collectionpath) );
    } else {
    	directory = new NIOFSDirectory( FileSystems.getDefault().getPath(collectionpath) );
    }
    
    File touchFile	= new File( collectionpath, "openbd.created" );
    if ( touchFile.exists() )
    	created = touchFile.lastModified();
    else
    	created = System.currentTimeMillis();
	}

	
	/**
	 * Creates an empty collection to get it up and running
	 */
	public synchronized void create( boolean _errorOnExists ) throws IOException {
		setDirectory();
		
		if ( directory.listAll().length > 2 ) {
			if ( _errorOnExists ) {
				throw new IOException( "directory not empty; possible collection already present" );
			}else {
				if ( DirectoryReader.indexExists( directory ) ) {
					return;
				}// otherwise an index doesn't exist so allow the creation code to execute
			}
		}

		IndexWriterConfig iwc = new IndexWriterConfig( AnalyzerFactory.get(language) );
		iwc.setOpenMode( OpenMode.CREATE );
		
		indexwriter = new IndexWriter(directory, iwc);
		indexwriter.commit();
		indexwriter.close();
		indexwriter = null;
		
		// throw an openbd.create file in there so we know when it was created
		created	= System.currentTimeMillis();
		File touchFile	= new File( collectionpath, "openbd.created" );
		Writer	fw	= new FileWriter( touchFile );
		fw.close();
	}
	
	
	/**
	 * Deletes this collection, removing all files
	 * @throws IOException 
	 */
	public synchronized void delete() throws IOException {
		close();

		// delete the file
		File f = new File(collectionpath);
    if (f.exists() && f.isDirectory())
      FileUtils.recursiveDelete(f, true);
	}

	public synchronized DocumentWriter getDocumentWriter() throws CorruptIndexException, LockObtainFailedException, IOException {
		if ( documentwriter != null )
			return documentwriter;
		
		documentwriter	= new DocumentWriter( this );
		return documentwriter;
	}

	private void setIndexWriter() throws IOException{
		if ( indexwriter != null )
			return;
		
		setDirectory();
		IndexWriterConfig iwc = new IndexWriterConfig( AnalyzerFactory.get(language) );
		iwc.setOpenMode( OpenMode.CREATE_OR_APPEND );
		indexwriter = new IndexWriter(directory, iwc);
	}
	
	public synchronized void addDocuments(java.util.Collection<Document> pageCollection) throws CorruptIndexException, IOException {
		setIndexWriter();
		indexwriter.addDocuments(pageCollection);
		closeWriter();
		close();
	}

	public synchronized void deleteDocument(DocumentWrap docwrap, boolean closeOptimize ) throws IOException {
		setIndexWriter();
    indexwriter.deleteDocuments( new Term(DocumentWrap.ID, docwrap.getId()) );
    
    if ( closeOptimize ){
    	closeWriter();
    }
	}
	
	
	public synchronized void closeWriter() throws CorruptIndexException, IOException{
		indexwriter.commit();
		indexwriter.close();
		indexwriter	= null;
	}
	
	
	public synchronized void deleteAll() throws IOException {
		setIndexWriter();
		indexwriter.deleteAll();
		closeWriter();
		close();		
	}
}