package com.uas.search.console.util;

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.Occur;
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 org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.wltea.analyzer.lucene.IKAnalyzer;

import com.uas.search.console.LuceneProperties;
import com.uas.search.console.core.util.ContextUtils;
import com.uas.search.console.model.BrandSimpleInfo;
import com.uas.search.console.model.ComponentSimpleInfo;
import com.uas.search.console.model.KindSimpleInfo;
import com.uas.search.console.model.OrderInvoiceSimpleInfo;
import com.uas.search.console.model.OrderSimpleInfo;
import com.uas.search.console.model.PurchaseInvoiceSimpleInfo;
import com.uas.search.console.model.PurchaseSimpleInfo;
import com.uas.search.console.support.IndexSearcherManager;
import com.uas.search.exception.SearchException;

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

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

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

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

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

	/**
	 * 对搜索词进行分词后组合得到BooleanQuery
	 * 
	 * @param field
	 *            搜索的域名
	 * @param keyword
	 *            搜索关键词
	 * @return
	 */
	public static BooleanQuery getBooleanQuery(String field, String keyword) {
		return getBooleanQuery(field, keyword, Occur.MUST);
	}

	/**
	 * 对搜索词进行分词后组合得到BooleanQuery
	 * 
	 * @param field
	 *            搜索的域名
	 * @param keyword
	 *            搜索关键词
	 * @param occur
	 *            多个Query之间的关系
	 * @return
	 */
	public static BooleanQuery getBooleanQuery(String field, String keyword, Occur occur) {
		if (StringUtils.isEmpty(field) || StringUtils.isEmpty(keyword)) {
			return null;
		}
		BooleanQuery booleanQuery = new BooleanQuery();
		Analyzer analyzer = new IKAnalyzer();
		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())), occur);
			}
			tokenStream.close();
			analyzer.close();
		} catch (IOException e) {
			logger.error("", e);
		}
		return booleanQuery;
	}

	/**
	 * 对搜索词进行分词后组合得到RegexpQuery
	 * 
	 * @param field
	 *            搜索的域名
	 * @param keyword
	 *            搜索关键词
	 * @return
	 */
	// 如果用正则表达式进行搜索，就不需要再分词（分词的字段可能会导致边界问题）
	public static RegexpQuery getRegexpQuery(String field, String keyword) {
		if (StringUtils.isEmpty(field)) {
			return null;
		}
		// TODO 针对的是分词了的字段（未分词的字段可能搜索不准确）
		return new RegexpQuery(new Term(field, ".*" + keyword.toLowerCase() + ".*"));
	}

	/**
	 * 获取指定表的IndexSearcher对象，若为空，抛出异常
	 * 
	 * @param tableName
	 *            表名
	 * @return IndexSearcher对象
	 */
	public static IndexSearcher getIndexSearcher(String 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(String tableName, String field, Long id) {
		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(String tableName, String field, String keyword) {
		return getDocuments(tableName, field, keyword, SearchConstants.TOP_NUM);
	}

	/**
	 * 根据域和搜索词获取指定数目的指定表的Document列表
	 * 
	 * @param tableName
	 *            表名
	 * @param field
	 *            搜索域
	 * @param keyword
	 *            搜索词
	 * @param topNum
	 *            指定的数目（数目最多为该数目）
	 * @return
	 */
	public static List<Document> getDocuments(String 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(String tableName, Query query) {
		return getDocuments(tableName, query, SearchConstants.TOP_NUM);
	}

	/**
	 * 根据查询条件获取指定数目的指定表的Document列表
	 * 
	 * @param tableName
	 *            表名
	 * @param query
	 *            查询条件
	 * @param topNum
	 *            指定的数目（数目最多为该数目）
	 * @return
	 */
	public static List<Document> getDocuments(String 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;
	}

	/**
	 * 获取所有需要建立索引的表名
	 * 
	 * @return
	 */
	public static List<String> getTableNames() {
		List<String> tableNames = new ArrayList<>();
		tableNames.add(SearchConstants.KIND_TABLE_NAME);
		tableNames.add(SearchConstants.BRAND_TABLE_NAME);
		tableNames.add(SearchConstants.COMPONENT_TABLE_NAME);
		tableNames.add(SearchConstants.ORDER_TABLE_NAME);
		tableNames.add(SearchConstants.ORDER_INVOICE_TABLE_NAME);
		tableNames.add(SearchConstants.PURCHASE_TABLE_NAME);
		tableNames.add(SearchConstants.PURCHASE_INVOICE_TABLE_NAME);
		return tableNames;
	}

	/**
	 * 获取指定的实体类对应的需要建立索引的表名
	 * 
	 * @param clazz
	 * @return
	 */
	public static String getTableName(Class<?> clazz) {
		if (clazz == null) {
			return null;
		}
		if (clazz == KindSimpleInfo.class) {
			return SearchConstants.KIND_TABLE_NAME;
		} else if (clazz == BrandSimpleInfo.class) {
			return SearchConstants.BRAND_TABLE_NAME;
		} else if (clazz == ComponentSimpleInfo.class) {
			return SearchConstants.COMPONENT_TABLE_NAME;
		} else if (clazz == OrderSimpleInfo.class) {
			return SearchConstants.ORDER_TABLE_NAME;
		} else if (clazz == OrderInvoiceSimpleInfo.class) {
			return SearchConstants.ORDER_INVOICE_TABLE_NAME;
		} else if (clazz == PurchaseSimpleInfo.class) {
			return SearchConstants.PURCHASE_TABLE_NAME;
		} else if (clazz == PurchaseInvoiceSimpleInfo.class) {
			return SearchConstants.PURCHASE_INVOICE_TABLE_NAME;
		} else {
			throw new SearchException("该实体类没有对应的需要建立索引的表：" + clazz);
		}
	}

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