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

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.uas.search.util.CollectionUtils;
import com.uas.search.util.StringUtils;
import org.wltea.analyzer.lucene.IKAnalyzer;

import com.uas.search.b2b.exception.SearchException;
import com.uas.search.b2b.service.SearchService.Table_name;
import com.uas.search.console.b2b.LuceneProperties;
import com.uas.search.console.b2b.support.IndexSearcherManager;

/**
 * 搜索相关的工具类
 * 
 * @author sunyj
 * @since 2016年10月17日 下午1:56:16
 */
public class SearchUtils {

	private static LuceneProperties luceneProperties = ContextUtils.getBean(LuceneProperties.class);

	private static Logger logger = LoggerFactory.getLogger(SearchUtils.class);

	/**
	 * 默认搜索的最大的记录条数
	 */
	public static final int TOP_NUM = 1024 * 1024 * 1024;

	/**
	 * IndexSearcher的管理器
	 */
	private static IndexSearcherManager searcherManager = IndexSearcherManager.getInstance();

	/**
	 * 判断搜索词是否为无效的（比如只包含特殊字符，不含有任何字母、数字、汉字等有意义的字符）
	 * 
	 * @param keyword
	 * @return 搜索词是否无效
	 */
	public static boolean isKeywordInvalid(String keyword) {
		if (!StringUtils.isEmpty(keyword)) {
			// 将特殊字符剔除
			keyword = keyword.replaceAll("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~！@#￥……&*（）——|{}【】‘；：”“'。，、？ ]+", "");
			return StringUtils.isEmpty(keyword);
		}
		return true;
	}

	/**
	 * 对搜索词进行分词后组合得到BooleanQuery（内部条件为PrefixQuery）
	 * 
	 * @param field
	 *            搜索的域名
	 * @param keyword
	 *            搜索关键词
	 * @return
	 */
	public static BooleanQuery booleanQuery(String field, String keyword) {
		if (StringUtils.isEmpty(field) || StringUtils.isEmpty(keyword)) {
			return null;
		}
		BooleanQuery booleanQuery = new BooleanQuery();
		Analyzer analyzer = new IKAnalyzer(true);
		try {
			TokenStream tokenStream = analyzer.tokenStream(field, keyword);
			tokenStream.reset();
			CharTermAttribute cta = tokenStream.addAttribute(CharTermAttribute.class);
			while (tokenStream.incrementToken()) {
				booleanQuery.add(new PrefixQuery(new Term(field, cta.toString())), BooleanClause.Occur.MUST);
			}
			tokenStream.close();
			analyzer.close();
		} catch (IOException e) {
			logger.error("", e);
		}
		return booleanQuery;
	}

	/**
	 * 对搜索词进行分词后组合得到RegexpQuery
	 * 
	 * @param field
	 *            搜索的域名
	 * @param keyword
	 *            搜索关键词
	 * @param tokenized
	 *            可为空，模糊搜索时，是否对关键词进行分词，为空时，不分词
	 * @return
	 */
	// TODO 如果用正则表达式进行搜索，建索引时就不需要再分词（分词的字段可能会导致边界问题） <- 可以分词
	public static Query regexpQuery(String field, String keyword, Boolean tokenized) {
		if (StringUtils.isEmpty(field)) {
			return null;
		}
		if (field == null) {
			field = "";
		}
		// 关键词为空或不分词，直接返回单个RegexpQuery
		if (StringUtils.isEmpty(keyword) || tokenized == null || !tokenized.booleanValue()) {
			return new RegexpQuery(new Term(field, ".*" + keyword + ".*"));
		}
		BooleanQuery booleanQuery = new BooleanQuery();
		Analyzer analyzer = new IKAnalyzer(true);
		try {
			TokenStream tokenStream = analyzer.tokenStream(field, keyword);
			tokenStream.reset();
			CharTermAttribute cta = tokenStream.addAttribute(CharTermAttribute.class);
			while (tokenStream.incrementToken()) {
				booleanQuery.add(new RegexpQuery(new Term(field, ".*" + cta.toString() + ".*")),
						BooleanClause.Occur.MUST);
			}
			tokenStream.close();
			analyzer.close();
		} catch (IOException e) {
			logger.error("", e);
		}
		return booleanQuery;
	}

	/**
	 * 获取指定表的IndexSearcher对象，若为空，抛出异常
	 * 
	 * @param tableName
	 *            表名
	 * @return IndexSearcher对象
	 */
	public static IndexSearcher getIndexSearcher(Table_name tableName) {
		IndexSearcher indexSearcher = searcherManager.get(tableName);
		if (indexSearcher == null) {
			throw new SearchException("获取索引文件失败");
		}
		return indexSearcher;
	}

	/**
	 * 释放indexSearcher
	 * 
	 * @param indexSearcher
	 */
	public static void releaseIndexSearcher(IndexSearcher indexSearcher) {
		searcherManager.release(indexSearcher);
	}

	/**
	 * 根据域和搜索词获取指定表的Document（每个id最多只能对应一条数据）
	 * 
	 * @param tableName
	 *            表名
	 * @param field
	 *            搜索域
	 * @param keyword
	 *            搜索词
	 * @return Document
	 */
	public static Document getDocumentById(Table_name tableName, String field, Long id) throws SearchException {
		if (id == null) {
			throw new SearchException("输入无效：" + id);
		}
		List<Document> documents = getDocuments(tableName, field, Long.toString(id));
		if (CollectionUtils.isEmpty(documents)) {
			return null;
		} else if (documents.size() > 1) {
			throw new SearchException("索引中存在不止一个对象：" + field + "=" + id);
		}
		return documents.get(0);
	}

	/**
	 * 根据域和搜索词获取指定表的Document列表
	 * 
	 * @param tableName
	 *            表名
	 * @param field
	 *            搜索域
	 * @param keyword
	 *            搜索词
	 * @return Document列表
	 */
	public static List<Document> getDocuments(Table_name tableName, String field, String keyword) {
		return getDocuments(tableName, field, keyword, TOP_NUM);
	}

	/**
	 * 根据域和搜索词获取指定表的指定数目的Document列表
	 * 
	 * @param tableName
	 *            表名
	 * @param field
	 *            搜索域
	 * @param keyword
	 *            搜索词
	 * @param topNum
	 *            指定的数目（数目最多为该数目）
	 * @return
	 */
	public static List<Document> getDocuments(Table_name tableName, String field, String keyword, int topNum) {
		if (StringUtils.isEmpty(field) || StringUtils.isEmpty(keyword)) {
			throw new SearchException("搜索的域和搜索词不能为空");
		}
		TermQuery termQuery = new TermQuery(new Term(field, keyword));
		return getDocuments(tableName, termQuery, topNum);
	}

	/**
	 * 根据查询条件获取指定表的Document列表
	 * 
	 * @param tableName
	 *            表名
	 * @param query
	 *            查询条件
	 * @return
	 */
	public static List<Document> getDocuments(Table_name tableName, Query query) {
		return getDocuments(tableName, query, TOP_NUM);
	}

	/**
	 * 根据查询条件获取指定表的指定数目的Document列表
	 * 
	 * @param tableName
	 *            表名
	 * @param query
	 *            查询条件
	 * @param topNum
	 *            指定的数目（数目最多为该数目）
	 * @return
	 */
	public static List<Document> getDocuments(Table_name tableName, Query query, int topNum) {
		if (query == null) {
			throw new SearchException("query不能为null");
		}
		if (topNum < 1) {
			throw new SearchException("查询的数目topNum不能小于1");
		}
		IndexSearcher indexSearcher = getIndexSearcher(tableName);
		List<Document> documents = new ArrayList<>();
		try {
			TopDocs topDocs = indexSearcher.search(query, topNum);
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			for (ScoreDoc scoreDoc : scoreDocs) {
				documents.add(indexSearcher.doc(scoreDoc.doc));
			}
		} catch (IOException e) {
			throw new SearchException(e).setDetailedMessage(e);
		} finally {
			releaseIndexSearcher(indexSearcher);
		}
		return documents;
	}

	/**
	 * 获取指定表的索引路径
	 * 
	 * @param tableName
	 *            指定表
	 * @return 索引路径
	 */
	public static String getIndexPath(Table_name tableName) {
		if (tableName == null) {
			return null;
		}
		return luceneProperties.getIndexesDir() + "/" + tableName.value();
	}

	/**
	 * 递归删除文件（夹）
	 * 
	 * @param file
	 *            文件（夹）
	 */
	public static void deleteDir(File file) {
		if (file == null) {
			return;
		}
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			for (File f : files) {
				deleteDir(f);
			}
		}
		file.delete();
		logger.info("Deleted... " + file.getPath());
	}

	/**
	 * 获取指定单据类型在本地的数据存储路径
	 * 
	 * @param tableName
	 *            单据类型
	 * @return 数据存储路径
	 */
	public static String getDataPath(Table_name tableName) {
		return luceneProperties.getDataDir() + "/" + tableName.value();
	}
}
