package com.uas.search.console.b2b.service.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.lucene.document.Document;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.uas.search.b2b.exception.SearchException;
import com.uas.search.b2b.model.MultiValue;
import com.uas.search.b2b.model.PageParams;
import com.uas.search.b2b.model.SPage;
import com.uas.search.b2b.service.SearchService;
import com.uas.search.console.b2b.service.InnerSearchService;
import com.uas.search.console.b2b.util.ClassAndTableNameUtils;
import com.uas.search.console.b2b.util.DocumentToObjectUtils;
import com.uas.search.console.b2b.util.SearchUtils;

/**
 * 搜索实现
 * 
 * @author sunyj
 * @since 2016年11月9日 下午4:47:09
 */
@Service
public class SearchServiceImpl implements SearchService, InnerSearchService {

	/**
	 * 默认的页码
	 */
	private static final int PAGE_INDEX = 1;

	/**
	 * 默认每页的大小
	 */
	private static final int PAGE_SIZE = 20;

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

	@Override
	public SPage<Long> searchIds(String keyword, Table_name tableName, PageParams pageParams) throws SearchException {
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName);
		// 获取单据的id
		List<Long> content = new ArrayList<>();
		try {
			SPage<ScoreDoc> scoreDocPage = search(indexSearcher, keyword, tableName, pageParams);
			SPage<Long> sPage = convertSPage(scoreDocPage, Long.class);
			for (ScoreDoc scoreDoc : scoreDocPage.getContent()) {
				Document document = indexSearcher.doc(scoreDoc.doc);
				content.add(Long.valueOf(document.get(ClassAndTableNameUtils.getIdField(tableName))));
			}
			sPage.setContent(content);
			return sPage;
		} catch (NumberFormatException | IOException e) {
			throw new SearchException(e).setDetailedMessage(e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
	}

	/**
	 * 转换SPage
	 * 
	 * @param scoreDocPage
	 * @param clazz
	 * @return
	 */
	private <T> SPage<T> convertSPage(SPage<ScoreDoc> scoreDocPage, Class<T> clazz) {
		return new SPage<T>(scoreDocPage.getTotalPage(), scoreDocPage.getTotalElement(), scoreDocPage.getPage(),
				scoreDocPage.getSize(), scoreDocPage.isFirst(), scoreDocPage.isLast());
	}

	/**
	 * 根据关键词、单据类型、状态码搜索单据
	 * 
	 * @param indexSearcher
	 * 
	 * @param keyword
	 *            不为空，模糊搜索关键词，可以是：单据编号、供应商uu（采购）、供应商名称（采购）、客户uu（销售）、客户名称（销售）、
	 *            物料编号、 物料名称、物料规格
	 * @param tableName
	 *            不为空，单据类型
	 * @param pageParams
	 *            可为空，可能含有翻页信息，filters中可能有过滤信息，包括：1.状态、所属企业uu、其他状态（如已采纳、未采纳等），
	 *            该部分参数的键为数据库表中相应的字段名称，值为字段对应的值，若值有多个，则使用com.uas.search.b2b.
	 *            model.MultiValue，则；2.开始时间（Long）、截止时间（Long），
	 *            这两个参数用于对时间范围进行筛选（包含开始和截止时间），键为com.uas.search.b2b.util.
	 *            SearchConstants中的常量；3.排序方式（ArrayList(Sort)），键为com.uas.search.
	 *            b2b.util.SearchConstants中的常量，值为List(com.uas.search.b2b.model.
	 *            Sort)
	 * @return 单据数据
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	private SPage<ScoreDoc> search(IndexSearcher indexSearcher, String keyword, Table_name tableName,
			PageParams pageParams) throws IOException {

		// 获取该表keyword可以搜索的域
		List<String> keywordFields = ClassAndTableNameUtils.getKeywordFields(tableName);

		SPage<ScoreDoc> sPage = new SPage<>();
		BooleanQuery booleanQuery = new BooleanQuery();
		Sort sort = null;

		if (keyword == null) {
			keyword = "";
		}
		// 关键词无效，即搜索所有的数据
		// 关键词带空格，进行与操作
		String[] strs = keyword.split(" ");
		for (String str : strs) {
			// keyword可能是哪些域，域之间进行或操作
			BooleanQuery booleanQuery2 = new BooleanQuery();
			for (String keywordField : keywordFields) {
				booleanQuery2.add(SearchUtils.getRegexpQuery(keywordField, str), BooleanClause.Occur.SHOULD);
			}
			booleanQuery.add(booleanQuery2, BooleanClause.Occur.MUST);
		}

		// 分页信息
		if (pageParams == null) {
			sPage.setPage(PAGE_INDEX);
			sPage.setSize(PAGE_SIZE);
			sPage.setFirst(true);
		} else {
			int page = pageParams.getPage();
			if (page > 0) {
				sPage.setPage(page);
			} else {
				sPage.setPage(PAGE_INDEX);
				sPage.setFirst(true);
			}
			int size = pageParams.getSize();
			if (size > 0) {
				sPage.setSize(size);
			} else {
				sPage.setSize(PAGE_SIZE);
			}

			Map<String, Object> filters = pageParams.getFilters();
			if (!CollectionUtils.isEmpty(filters)) {
				// 如果filter中开始时间和截止时间至少传了一个参数，说明需要对时间范围进行限定
				if (!StringUtils.isEmpty(filters.get(com.uas.search.b2b.util.SearchConstants.FROM_DATE_KEY))
						|| !StringUtils.isEmpty(filters.get(com.uas.search.b2b.util.SearchConstants.END_DATE_KEY))) {
					// 开始时间初始化为1990.1.1
					Calendar calendar = Calendar.getInstance();
					calendar.set(1990, 0, 1);
					// 截止时间初始化为现在
					long fromTime = calendar.getTimeInMillis();
					long endTime = new Date().getTime();
					if (!StringUtils.isEmpty(filters.get(com.uas.search.b2b.util.SearchConstants.FROM_DATE_KEY))) {
						fromTime = Long.parseLong(
								filters.remove(com.uas.search.b2b.util.SearchConstants.FROM_DATE_KEY).toString());
					}
					if (!StringUtils.isEmpty(filters.get(com.uas.search.b2b.util.SearchConstants.END_DATE_KEY))) {
						endTime = Long.parseLong(
								filters.remove(com.uas.search.b2b.util.SearchConstants.END_DATE_KEY).toString());
					}
					booleanQuery.add(NumericRangeQuery.newLongRange(ClassAndTableNameUtils.getDateField(tableName),
							fromTime, endTime, true, true), BooleanClause.Occur.MUST);
				}

				// 如果需要排序
				if (!StringUtils.isEmpty(filters.get(com.uas.search.b2b.util.SearchConstants.SORT_KEY))) {
					Object sortObject = filters.remove(com.uas.search.b2b.util.SearchConstants.SORT_KEY);
					List<com.uas.search.b2b.model.Sort> sortList = null;
					if (sortObject instanceof ArrayList) {
						sortList = (List<com.uas.search.b2b.model.Sort>) sortObject;
					}
					if (!CollectionUtils.isEmpty(sortList)) {
						SortField[] sortFields = new SortField[sortList.size()];
						for (int i = 0; i < sortList.size(); i++) {
							com.uas.search.b2b.model.Sort s = sortList.get(i);
							SortField sortField = new SortField(
									ClassAndTableNameUtils.combineField(tableName, s.getField()), getType(s),
									!s.isReverse());
							if (s.getMissingValue() != null) {
								sortField.setMissingValue(s.getMissingValue());
							}
							sortFields[i] = sortField;
						}
						sort = new Sort(sortFields);
					}
				}

				// 其他过滤条件，键即为相应的数据库字段名（field名）
				Set<Entry<String, Object>> entrySet = filters.entrySet();
				for (Entry<String, Object> entry : entrySet) {
					String key = entry.getKey();
					Object value = entry.getValue();
					// 拼接索引域名
					String field = ClassAndTableNameUtils.combineField(tableName, key);
					/*
					 * 对于明细、企业等所关联的表的数据，是以json格式存储的，搜索时可能会对明细等表中多个字段进行过滤，
					 * 但是键都是明细等表在主表中所对应的字段名称，为了实现这样的过滤功能，需要将多个值放在com.uas.search.
					 * b2b.model.MultiValue中， 作为filters中key-value的value传递过来
					 */
					if (value instanceof MultiValue) {
						MultiValue multiValue = (MultiValue) value;
						List<Object> values = multiValue.getValues();
						Occur occur = multiValue.isOr() ? Occur.SHOULD : Occur.MUST;
						BooleanQuery booleanQuery2 = new BooleanQuery();
						for (Object object : values) {
							booleanQuery2.add(SearchUtils.getRegexpQuery(field, String.valueOf(object)), occur);
						}
						booleanQuery.add(booleanQuery2, Occur.MUST);
					} else {
						if (value != null) {
							booleanQuery.add(SearchUtils.getRegexpQuery(field, String.valueOf(value)), Occur.MUST);
						}
					}

				}
			}
		}
		logger.info(booleanQuery.toString());

		TopDocs topDocs;
		// 如果页码不为1
		if (sPage.getPage() > 1) {
			TopDocs previousTopDocs = null;
			if (sort != null) {
				previousTopDocs = indexSearcher.search(booleanQuery, (sPage.getPage() - 1) * sPage.getSize(), sort);
			} else {
				previousTopDocs = indexSearcher.search(booleanQuery, (sPage.getPage() - 1) * sPage.getSize());
			}
			int totalHits = previousTopDocs.totalHits;
			ScoreDoc[] previousScoreDocs = previousTopDocs.scoreDocs;
			if ((sPage.getPage() - 1) * sPage.getSize() >= totalHits) {
				throw new SearchException("页码过大：元素总数量为" + totalHits);
			}
			if (sort != null) {
				topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], booleanQuery,
						sPage.getSize(), sort);
			} else {
				topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], booleanQuery,
						sPage.getSize());
			}
		} else {
			sPage.setFirst(true);
			if (sort != null) {
				topDocs = indexSearcher.search(booleanQuery, sPage.getSize(), sort);
			} else {
				topDocs = indexSearcher.search(booleanQuery, sPage.getSize());
			}
		}

		int totalHits = topDocs.totalHits;
		// 设置总元素个数、页数等信息
		sPage.setTotalElement(totalHits);
		int totalPage = (int) Math.ceil(totalHits / (1.0 * sPage.getSize()));
		sPage.setTotalPage(totalPage);
		if (totalPage == sPage.getPage()) {
			sPage.setLast(true);
		}

		sPage.setContent(Arrays.asList(topDocs.scoreDocs));
		return sPage;
	}

	@Override
	public <T> T getObjectById(Long id, Class<T> clazz) {
		if (id == null) {
			return null;
		}
		try {
			Document document = SearchUtils.getDocumentById(ClassAndTableNameUtils.toTableName(clazz),
					ClassAndTableNameUtils.getIdField(clazz), id);
			T element = DocumentToObjectUtils.toObject(document, clazz);
			return element;
		} catch (SearchException e) {
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public <T> SPage<T> getAllObjects(Class<T> clazz, int page, int size) {
		Table_name tableName = ClassAndTableNameUtils.toTableName(clazz);
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName);
		// 获取单据的id
		List<T> content = new ArrayList<>();
		try {
			PageParams pageParams = new PageParams();
			pageParams.setPage(page);
			pageParams.setSize(size);
			SPage<ScoreDoc> scoreDocPage = search(indexSearcher, null, tableName, pageParams);
			SPage<T> sPage = convertSPage(scoreDocPage, clazz);
			for (ScoreDoc scoreDoc : scoreDocPage.getContent()) {
				Document document = indexSearcher.doc(scoreDoc.doc);
				content.add(DocumentToObjectUtils.toObject(document, clazz));
			}
			sPage.setContent(content);
			return sPage;
		} catch (NumberFormatException | IOException e) {
			throw new SearchException(e).setDetailedMessage(e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
	}

	/**
	 * 获取排序字段的类型
	 * 
	 * @param s
	 * @return
	 */
	private SortField.Type getType(com.uas.search.b2b.model.Sort s) {
		if (s == null) {
			return null;
		}
		com.uas.search.b2b.model.Sort.Type type = s.getType();
		if (type == com.uas.search.b2b.model.Sort.Type.STRING) {
			return SortField.Type.STRING;
		} else if (type == com.uas.search.b2b.model.Sort.Type.INT) {
			return SortField.Type.INT;
		} else if (type == com.uas.search.b2b.model.Sort.Type.FLOAT) {
			return SortField.Type.FLOAT;
		} else if (type == com.uas.search.b2b.model.Sort.Type.LONG) {
			return SortField.Type.LONG;
		} else if (type == com.uas.search.b2b.model.Sort.Type.DOUBLE) {
			return SortField.Type.DOUBLE;
		} else {
			throw new SearchException("排序参数错误：" + type);
		}
	}

}
