package com.uas.search.console.b2b.support;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.NIOFSDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.uas.search.util.StringUtils;

import com.uas.search.b2b.service.SearchService.Table_name;
import com.uas.search.console.b2b.util.ClassAndTableNameUtils;
import com.uas.search.console.b2b.util.SearchUtils;

/**
 * 将索引加入缓存，对IndexSearcher进行管理
 * 
 * @author sunyj
 * @since 2016年7月7日 下午8:50:55
 */
public class IndexSearcherManager {

	private static volatile IndexSearcherManager instance;

	/**
	 * 用于存放指定表的索引IndexSearcher
	 */
	private Map<Table_name, IndexSearcher> indexSearchers = new ConcurrentHashMap<>();

	/**
	 * 标识指定表的IndexSearcher是否正在重新打开
	 */
	private Map<Table_name, Boolean> reopening = new ConcurrentHashMap<>();

	/**
	 * 标识在后台是否存在线程在等待刷新缓存中的索引
	 */
	private Map<Table_name, Boolean> waitingFlushCache = new ConcurrentHashMap<>();

	public static final Long DEFAULT_DELAY = 1000L;

	private Logger logger = LoggerFactory.getLogger(getClass());

	private IndexSearcherManager() {
		List<Table_name> tableNames = ClassAndTableNameUtils.getTableNames();
		for (Table_name tableName : tableNames) {
			reopening.put(tableName, false);
		}
	}

	public static IndexSearcherManager getInstance() {
		if (instance == null) {
			synchronized (IndexSearcherManager.class) {
				if (instance == null) {
					instance = new IndexSearcherManager();
				}
			}
		}
		return instance;
	}

	/**
	 * 对指定表的IndexSearcher进行初始化操作
	 * 
	 * @param tableName
	 *            表名
	 * @return 指定表的FSDirectory
	 */
	private synchronized void warm(Table_name tableName) {
		IndexSearcher indexSearcher = indexSearchers.get(tableName);
		// 不为空的话说明索引已成功加载
		if (indexSearcher == null) {
			String indexPath = SearchUtils.getIndexPath(tableName);
			FSDirectory directory = null;
			// 本来NIOFSDirectory速度更快，但因为jre的bug，在Windows平台上，其性能很差
			try {
				if (System.getProperty("os.name").toLowerCase().startsWith("win")) {
					directory = FSDirectory.open(Paths.get(indexPath));
				} else {
					directory = NIOFSDirectory.open(Paths.get(indexPath));
				}

				File[] files = directory.getDirectory().toFile().listFiles();
				// 路径不为空文件夹
				if (files.length != 0) {
					try {
						indexSearcher = new IndexSearcher(DirectoryReader.open(directory));
						indexSearchers.put(tableName, indexSearcher);
					} catch (IOException e) {
						logger.error("", e);
					}
				} else {
					logger.error("索引文件不存在：" + tableName.value());
				}
			} catch (IOException e) {
				logger.error("索引路径打开失败：" + indexPath, e);
			}
		}
	}

	/**
	 * 内存中指定表的IndexReader（可能需要）更新，根据版本号确定是否更新
	 * 
	 * @param tableName
	 *            表名
	 */
	public void maybeReopen(Table_name tableName) {
		if (tableName == null) {
			return;
		}
		startReopen(tableName);

		try {
			IndexSearcher currentSearcher = indexSearchers.get(tableName);
			if (currentSearcher == null) {
				return;
			}
			IndexSearcher searcher = get(tableName);
			try {
				// 本地索引有更改，更新IndexReader
				DirectoryReader newReader = DirectoryReader.openIfChanged((DirectoryReader) searcher.getIndexReader());
				if (newReader != null) {
					IndexSearcher newSearcher = new IndexSearcher(newReader);
					updateIndexSearcher(tableName, newSearcher);
				}
			} catch (IOException e) {
				logger.error("", e);
			}
			release(searcher);
		} finally {
			doneReopen(tableName);
		}

	}

	private synchronized void startReopen(Table_name tableName) {
		while (reopening.get(tableName)) {
			try {
				wait();
			} catch (InterruptedException e) {
				logger.error("", e);
			}
		}
		reopening.put(tableName, true);
	}

	private synchronized void doneReopen(Table_name tableName) {
		reopening.put(tableName, false);
		notifyAll();
	}

	/**
	 * 得到指定表的IndexSearcher
	 * 
	 * @param tableName
	 *            表名
	 * @return
	 */
	public synchronized IndexSearcher get(Table_name tableName) {
		if (tableName == null) {
			return null;
		}
		warm(tableName);
		IndexSearcher currentSearcher = indexSearchers.get(tableName);
		if (currentSearcher != null) {
			currentSearcher.getIndexReader().incRef();
		}
		return currentSearcher;
	}

	/**
	 * 释放indexSearcher
	 * 
	 * @param indexSearcher
	 */
	public synchronized void release(IndexSearcher indexSearcher) {
		if (indexSearcher != null) {
			try {
				// 对indexReader的引用减1，为0时成为垃圾
				indexSearcher.getIndexReader().decRef();
			} catch (IOException e) {
				logger.error("", e);
			}
		}
	}

	/**
	 * 更新指定表的IndexSearcher
	 * 
	 * @param tableName
	 *            表名
	 * @param newIndexSearcher
	 *            新IndexSearcher
	 */
	private synchronized void updateIndexSearcher(Table_name tableName, IndexSearcher newIndexSearcher) {
		IndexSearcher currentSearcher = indexSearchers.get(tableName);
		release(currentSearcher);
		indexSearchers.put(tableName, newIndexSearcher);
	}

	/**
	 * 刷新缓存中的索引
	 * 
	 * @param tableName
	 *            表名
	 * @param indexWriter
	 * @param delay
	 *            延迟一定时间刷新/ms，可为空，默认延迟3000ms
	 */
	public void flushCache(final Table_name tableName, final IndexWriter indexWriter, final Long delay) {
		if (StringUtils.isEmpty(tableName)) {
			return;
		}
		Boolean waiting = waitingFlushCache.get(tableName);
		// 有等待的线程，则不增加线程
		if (waiting == null || !waiting.booleanValue()) {
			final long delayCopy = (delay == null || delay.longValue() < 0) ? DEFAULT_DELAY : delay.longValue();
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						// 延迟一定时间再提交更改（提高更新索引的速度）并刷新缓存（提高搜索速度）
						Thread.sleep(delayCopy);
						// 提交更改后再刷新
						indexWriter.commit();
						maybeReopen(tableName);
					} catch (InterruptedException | IOException e) {
						logger.error("", e);
					} finally {
						// 线程执行完毕，无论是否成功，均将状态改为false
						waitingFlushCache.put(tableName, false);
					}
				}
			}).start();
			// 将状态改为true
			waitingFlushCache.put(tableName, true);
		}
	}
}
