package com.uas.search.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.uas.search.annotation.NotEmpty;
import com.uas.search.constant.SearchConstants;
import com.uas.search.constant.model.CollectField;
import com.uas.search.constant.model.PageParams;
import com.uas.search.constant.model.PageParams.FilterField;
import com.uas.search.constant.model.SPage;
import com.uas.search.exception.SearchException;
import com.uas.search.grouping.DistinctGroupCollector;
import com.uas.search.grouping.GoodsGroupCollector;
import com.uas.search.model.*;
import com.uas.search.service.SearchService;
import com.uas.search.sort.StringFieldComparatorSource;
import com.uas.search.util.CollectionUtils;
import com.uas.search.util.DocumentToObjectUtils;
import com.uas.search.util.SearchUtils;
import com.uas.search.util.StringUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.SortField.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.net.URLDecoder;
import java.util.*;

/**
 * 搜索索引
 * 
 * @author sunyj
 * @since 2016年8月5日 下午2:21:26
 */
@Service
public class SearchServiceImpl implements SearchService {

	/**
	 * 获取联想词时返回的最大数目
	 */
	private static final int SIMILAR_NUM = 20;

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

	@Override
	public SPage<Long> getKindIds(String keyword, Integer page, Integer size) {
		List<Long> ids = new ArrayList<>();
		SPage<Document> documents = getKindDocuments(keyword, page, size);;
		SPage<Long> sPage = new SPage<>(documents.getTotalPage(), documents.getTotalElement(), documents.getPage(),
                documents.getSize(), documents.isFirst(), documents.isLast());
		for (Document document : documents.getContent()) {
			ids.add(Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
		}
		sPage.setContent(ids);
		return sPage;
	}

	@Override
	public SPage<Map<String, Object>> getKinds(String keyword, Integer page, Integer size) {
		List<Map<String, Object>> kinds = new ArrayList<>();
		SPage<Document> documents = getKindDocuments(keyword, page, size);
		SPage<Map<String, Object>> sPage = new SPage<>(documents.getTotalPage(),
				documents.getTotalElement(), documents.getPage(), documents.getSize(), documents.isFirst(),
				documents.isLast());
		for (Document document : documents.getContent()) {
			Map<String, Object> kind = new HashMap<>();
			kind.put("id", Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
			kind.put("nameCn", document.get(SearchConstants.KIND_NAMECN_FIELD));
			kinds.add(kind);
		}
		sPage.setContent(kinds);
		return sPage;
	}

	private SPage<Document> getKindDocuments(String keyword, Integer page, Integer size){
		if (SearchUtils.isKeywordInvalid(keyword)) {
			throw new SearchException("搜索关键词无效：" + keyword);
		}
		BooleanQuery booleanQuery = SearchUtils.getBooleanQuery(SearchConstants.KIND_NAMECN_FIELD, keyword);
		logger.info(booleanQuery.toString());
		return SearchUtils.getDocuments(SearchConstants.KIND_TABLE_NAME, booleanQuery, new Sort(sortKind(keyword)), page, size);
	}

    /**
     * @return 类目排序规则
     */
    private SortField[] sortKind(String keyword) {
        // 分数 > 访问量 > 搜索次数
        return new SortField[]{
                sortField(SearchConstants.KIND_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.KIND_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
				new SortField(SearchConstants.KIND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword))
        };
    }

    /**
     * 构造 SortField
     *
     * @param field        Name of field to sort by.  Can be <code>null</code> if
     *                     <code>type</code> is SCORE or DOC.
     * @param type         Type of values in the terms.
     * @param reverse      True if natural order should be reversed.
     * @param missingValue Used for 'sortMissingFirst/Last'
     * @return SortField
     */
    private SortField sortField(String field, Type type, boolean reverse, Object missingValue) {
        SortField sortField = new SortField(field, type, reverse);
        sortField.setMissingValue(missingValue);
        return sortField;
    }

	@Override
	public SPage<Long> getBrandIds(String keyword, Integer page, Integer size) {
		List<Long> ids = new ArrayList<>();
		SPage<Document> documents = getBrandDocuments(keyword, page, size);
		SPage<Long> sPage = new SPage<>(documents.getTotalPage(), documents.getTotalElement(), documents.getPage(),
                documents.getSize(), documents.isFirst(), documents.isLast());
		for (Document document : documents.getContent()) {
			ids.add(Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
		}
		sPage.setContent(ids);
		return sPage;
	}

	@Override
	public SPage<Map<String, Object>> getBrands(String keyword, Integer page, Integer size) {
		List<Map<String, Object>> brands = new ArrayList<>();
		SPage<Document> documents = getBrandDocuments(keyword, page, size);
		SPage<Map<String, Object>> sPage = new SPage<Map<String, Object>>(documents.getTotalPage(),
				documents.getTotalElement(), documents.getPage(), documents.getSize(), documents.isFirst(),
				documents.isLast());
		for (Document document : documents.getContent()) {
			Map<String, Object> brand = new HashMap<String, Object>();
			brand.put("id", Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
			brand.put("uuid", document.get(SearchConstants.BRAND_UUID_FIELD));
			brand.put("nameCn", document.get(SearchConstants.BRAND_NAMECN_FIELD));
			brand.put("nameEn", document.get(SearchConstants.BRAND_NAMEEN_FIELD));
			brands.add(brand);
		}
		sPage.setContent(brands);
		return sPage;
	}

	private SPage<Document> getBrandDocuments(String keyword, Integer page, Integer size){
		if (SearchUtils.isKeywordInvalid(keyword)) {
			throw new SearchException("搜索关键词无效：" + keyword);
		}
		BooleanQuery booleanQuery = new BooleanQuery();
		booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMECN_FIELD, keyword),
				BooleanClause.Occur.SHOULD);
		booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMEEN_FIELD, keyword),
				BooleanClause.Occur.SHOULD);
		logger.info(booleanQuery.toString());
		return SearchUtils.getDocuments(SearchConstants.BRAND_TABLE_NAME, booleanQuery, new Sort(sortBrand(keyword)), page,
				size);
	}

	/**
	 * @return 品牌排序规则
	 */
	private SortField[] sortBrand(String keyword) {
		// 自定义排序 > 权重 > 访问量 > 搜索次数 > 分数
        // 分数排序放在最后，是因为有的中英文名称相同，分数翻倍，但实际匹配度并不高
        return new SortField[]{
                sortField(SearchConstants.BRAND_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
                sortField(SearchConstants.BRAND_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.BRAND_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
				new SortField(SearchConstants.BRAND_NAMEEN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
				new SortField(SearchConstants.BRAND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword))
        };
	}

	@Override
	public Map<String, Object> getComponentIds(String keyword, PageParams pageParams) {
		Map<String, Object> searchComponentIds = getComponentIds(keyword, pageParams, null, null);
		return searchComponentIds;
		// TODO 对品牌、类目甚至拼音混合搜索（待完善）
		// int total = (int) searchComponentIds.get("total");
		// if (total != 0) {
		// return searchComponentIds;
		// }
		// List<Long> kindIds = getKindIds(keyword, Occur.SHOULD);
		// List<Long> brandIds = getBrandIds(keyword, Occur.SHOULD);
		// return getComponentIds(null, pageParams, kindIds, brandIds);
	}

	/**
	 * 根据关键词搜索产品
	 * 
	 * @param keyword
	 * @param pageParams
	 * @param kindIds
	 * @param brandIds
	 * @return
	 * @throws SearchException
	 */
	private Map<String, Object> getComponentIds(String keyword, PageParams pageParams, List<Long> kindIds,
			List<Long> brandIds) throws SearchException {
		// 因为器件、属性值的数据量远比类目、品牌大得多，而且器件搜索可能还需进行分页，
		// 所以涉及器件、属性值的搜索，大都不能像类目和品牌一样直接利用SearchUtils.getDocuments方法
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);

		if (pageParams == null) {
			pageParams = new PageParams();
		}
		if (pageParams.getPage() <= 0)
			pageParams.setPage(1);
		if (pageParams.getSize() <= 0)
			pageParams.setSize(20);

		Map<String, Object> map = new HashMap<>();
		List<Long> ids = new ArrayList<>();
		try {
			BooleanQuery booleanQuery = new BooleanQuery();
			if (!SearchUtils.isKeywordInvalid(keyword)) {
				booleanQuery.add(setBoost(keyword), BooleanClause.Occur.MUST);
			}

			Map<FilterField, Object> filters = pageParams.getFilters();
			if (!CollectionUtils.isEmpty(filters)) {
				// 筛选类目
				if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_KINDID))) {
					String kindId = filters.get(FilterField.COMPONENT_KINDID).toString();
					TermQuery kindQuery = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
					booleanQuery.add(kindQuery, BooleanClause.Occur.MUST);
				}

				// 筛选品牌
				if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_BRANDID))) {
					String brandId = filters.get(FilterField.COMPONENT_BRANDID).toString();
					TermQuery brandQuery = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
					booleanQuery.add(brandQuery, BooleanClause.Occur.MUST);
				}

				// 库存不为0
				if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_RESERVE))) {
					Boolean isReserveNotEmpty = (Boolean) filters.get(FilterField.COMPONENT_HAS_RESERVE);
					if (isReserveNotEmpty) {
						booleanQuery.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_RESERVE_FIELD, 0.0,
								Double.MAX_VALUE, false, true), BooleanClause.Occur.MUST);
					}
				}

				// 现货、呆滞库存、样品数量不为0，取或的关系
				if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_SAMPLE))
						|| !StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_ORIGINAL))
						|| !StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_INACTION_STOCK))) {
					BooleanQuery booleanQuery2 = new BooleanQuery();
					if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_SAMPLE))) {
						booleanQuery2.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_SAMPLE_QTY_FIELD,
								0.0, Double.MAX_VALUE, false, true), BooleanClause.Occur.SHOULD);
					}
					if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_ORIGINAL))) {
						booleanQuery2.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_ORIGINAL_QTY_FIELD,
								0.0, Double.MAX_VALUE, false, true), BooleanClause.Occur.SHOULD);
					}
					if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_INACTION_STOCK))) {
						booleanQuery2.add(
								NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_INACTION_STOCK_QTY_FIELD,
										0.0, Double.MAX_VALUE, false, true),
								BooleanClause.Occur.SHOULD);
					}
					booleanQuery.add(booleanQuery2, Occur.MUST);
				}

				// 属性过滤
				if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_PROPERTIES))) {
					JSONObject proJSON = JSONObject
							.parseObject(String.valueOf(filters.get(FilterField.COMPONENT_PROPERTIES)));
					for (String key : proJSON.keySet()) {
						String value = String.valueOf(proJSON.get(key));
						if (!StringUtils.isEmpty(value)) {
							if (!key.startsWith(SearchConstants.COMPONENT_PROPERTY_PREFIX)) {
								key = SearchConstants.COMPONENT_PROPERTY_PREFIX + key;
							}
							TermQuery propertyQuery = new TermQuery(new Term(key, value));
							booleanQuery.add(propertyQuery, BooleanClause.Occur.MUST);
						}
					}
				}
			}
			if (!CollectionUtils.isEmpty(kindIds)) {
				BooleanQuery booleanQuery2 = new BooleanQuery();
				for (Long id : kindIds) {
					booleanQuery2.add(new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, id.toString())),
							Occur.SHOULD);
				}
				booleanQuery.add(booleanQuery2, Occur.MUST);
			}
			if (!CollectionUtils.isEmpty(brandIds)) {
				BooleanQuery booleanQuery2 = new BooleanQuery();
				for (Long id : brandIds) {
					booleanQuery2.add(new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, id.toString())),
							Occur.SHOULD);
				}
				booleanQuery.add(booleanQuery2, Occur.MUST);
			}
			logger.info(booleanQuery.toString());

			Sort sort = new Sort(sortComponent(keyword));
			TopDocs hits;
			if (pageParams.getPage() > 1) {// 不是第一页
				TopDocs previousHits = indexSearcher.search(booleanQuery,
						(pageParams.getPage() - 1) * pageParams.getSize(), sort, true, false);
				ScoreDoc[] previousScoreDocs = previousHits.scoreDocs;
				ScoreDoc after = previousScoreDocs[previousScoreDocs.length - 1];
				hits = indexSearcher.searchAfter(after, booleanQuery, pageParams.getSize(), sort, true, false);
			} else {
				hits = indexSearcher.search(booleanQuery, pageParams.getSize(), sort, true, false);
			}

			ScoreDoc[] scoreDocs = hits.scoreDocs;
			for (ScoreDoc scoreDoc : scoreDocs) {
				// 数据量太大，需要指定将获取的数据（以免载入不必要的数据，降低速度）
				Set<String> fieldsToLoad = new HashSet<>();
				fieldsToLoad.add(SearchConstants.COMPONENT_ID_FIELD);
				Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
				String componentId = document.get(SearchConstants.COMPONENT_ID_FIELD);
				ids.add(Long.parseLong(componentId));
				// System.out.println(componentId + "\t" + scoreDoc.score);
			}
			map.put("componentIds", ids);
			map.put("page", pageParams.getPage());
			map.put("size", pageParams.getSize());
			map.put("total", hits.totalHits);
		} catch (IOException e) {
			logger.error("", e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
		return map;
	}

	/**
	 * 同时搜索器件、类目、品牌，并设置boost
	 * 
	 * @param keyword
	 * @return
	 */
	private Query setBoost(String keyword) {
		BooleanQuery booleanQuery = new BooleanQuery();
		PrefixQuery prefixQuery = new PrefixQuery(
				new Term(SearchConstants.COMPONENT_CODE_FIELD, keyword.toLowerCase()));
		prefixQuery.setBoost(100);
		booleanQuery.add(prefixQuery, BooleanClause.Occur.SHOULD);
		booleanQuery.add(createQuery(SearchConstants.COMPONENT_BR_NAMECN_FIELD, keyword, 10), BooleanClause.Occur.SHOULD);
		booleanQuery.add(createQuery(SearchConstants.COMPONENT_BR_NAMEEN_FIELD, keyword, 10), BooleanClause.Occur.SHOULD);
		booleanQuery.add(createQuery(SearchConstants.COMPONENT_KI_NAME_FIELD, keyword, 1), BooleanClause.Occur.SHOULD);
		return booleanQuery;
	}

    /**
     * @return 器件排序规则
     */
    private SortField[] sortComponent(String keyword) {
        // 分数 > 器件（访问量 > 搜索次数） > 品牌（权重 > 访问量 > 搜索次数） > 类目（访问量 > 搜索次数）
        return new SortField[]{
                sortField(SearchConstants.COMPONENT_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.COMPONENT_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.COMPONENT_BR_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
                sortField(SearchConstants.COMPONENT_BR_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.COMPONENT_BR_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.COMPONENT_KI_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.COMPONENT_KI_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
				new SortField(SearchConstants.COMPONENT_CODE_FIELD, new StringFieldComparatorSource(keyword, true)),
				new SortField(SearchConstants.COMPONENT_BR_NAMEEN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
				new SortField(SearchConstants.COMPONENT_BR_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
				new SortField(SearchConstants.COMPONENT_KI_NAME_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword))
        };
    }

	@Override
	public Set<Long> getKindIdsBySearchComponent(String keyword, String brandId) {
		Query filter = null;
		// 筛选品牌
		if (!StringUtils.isEmpty(brandId)) {
			filter = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
		}
		return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_KI_ID_FIELD).getValues();
	}

	@Override
	public Set<Map<String, Object>> getKindsBySearchComponent(String keyword, String brandId) {
		Query filter = null;
		// 筛选品牌
		if (!StringUtils.isEmpty(brandId)) {
			filter = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
		}
		return collectBySearchComponent(keyword, filter, SearchConstants.KIND_ID_FIELD,
				new DistinctGroupCollector.CollectField(SearchConstants.KIND_ID_FIELD, "id"),
				new DistinctGroupCollector.CollectField(SearchConstants.KIND_NAMECN_FIELD, "nameCn"))
				.getCollectValues();
	}

	@Override
	public Set<Long> getBrandIdsBySearchComponent(String keyword, String kindId) {
		Query filter = null;
		// 筛选类目
		if (!StringUtils.isEmpty(kindId)) {
			filter = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
		}
		return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_BR_ID_FIELD).getValues();
	}

	@Override
	public Set<Map<String, Object>> getBrandsBySearchComponent(String keyword, String kindId) {
		Query filter = null;
		// 筛选类目
		if (!StringUtils.isEmpty(kindId)) {
			filter = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
		}
		return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_BR_ID_FIELD,
				new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_ID_FIELD, "id"),
				new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_UUID_FIELD, "uuid"),
				new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_NAMECN_FIELD, "nameCn"))
				.getCollectValues();
	}

    /**
     * 搜索器件时统计指定信息
     *
     * @param keyword       关键词
     * @param filter        过滤条件
     * @param groupField    统计的单个字段（多为 id）
     * @param collectFields 统计的多个字段（详细信息），可为空
     * @return 统计信息
     */
    private DistinctGroupCollector collectBySearchComponent(String keyword, Query filter, String groupField, DistinctGroupCollector.CollectField... collectFields){
		if (SearchUtils.isKeywordInvalid(keyword)) {
			throw new SearchException("搜索关键词无效：" + keyword);
		}
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
		try {
			BooleanQuery booleanQuery = new BooleanQuery();
			keyword = URLDecoder.decode(keyword, "UTF-8");
			booleanQuery.add(setBoost(keyword), BooleanClause.Occur.MUST);

			if(filter != null){
				booleanQuery.add(filter, Occur.FILTER);
			}
			logger.info(booleanQuery.toString());

			DistinctGroupCollector collector = new DistinctGroupCollector(groupField, collectFields);
			indexSearcher.search(booleanQuery, collector);
			return collector;
		} catch (IOException e) {
			throw new IllegalStateException("统计失败", e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
	}

	@Override
	public List<String> getSimilarKeywords(String keyword, Integer size) {
		size = size == null || size < 1 ? SIMILAR_NUM : size;
		List<String> result = new ArrayList<>();

		// 相似的器件原厂型号数量足够，直接返回
		List<String> componentCodes = getSimilarComponentCodes(keyword, size);
		result.addAll(componentCodes);
		removeDuplicate(result);
		if (result.size() == size) {
			return result;
		}

		// 获取相似类目
		List<String> kindNames = getSimilarKindNames(keyword, size);
		if (!CollectionUtils.isEmpty(kindNames)) {
			result.addAll(kindNames);
			removeDuplicate(result);
			// 如果总的数量超出SIMILAR_NUM，去除多余的元素
			if (result.size() > size) {
				removeElements(result, size);
				return result;
			}
		}

		// 获取相似品牌
		List<String> brandNames = getSimilarBrandNames(keyword, size);
		if (!CollectionUtils.isEmpty(brandNames)) {
			result.addAll(brandNames);
			removeDuplicate(result);
			if (result.size() > size) {
				removeElements(result, size);
				return result;
			}
		}

		return result;
	}

	@Override
	public List<Map<String, Object>> getSimilarComponents(String componentCode, Integer size) {
		size = size == null || size < 1 ? SIMILAR_NUM : size;
		if (SearchUtils.isKeywordInvalid(componentCode)) {
			throw new SearchException("输入无效：" + componentCode);
		}
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);

		List<Map<String, Object>> components = new ArrayList<>();
		try {
			PrefixQuery prefixQuery = new PrefixQuery(
					new Term(SearchConstants.COMPONENT_CODE_FIELD, componentCode.toLowerCase()));
			logger.info(prefixQuery.toString());

            Sort sort = new Sort(new SortField(SearchConstants.COMPONENT_CODE_FIELD, new StringFieldComparatorSource(componentCode)));
			TopDocs hits = indexSearcher.search(prefixQuery, size, sort);
			ScoreDoc[] scoreDocs = hits.scoreDocs;
			for (ScoreDoc scoreDoc : scoreDocs) {
				Set<String> fieldsToLoad = new HashSet<>();
				fieldsToLoad.add(SearchConstants.COMPONENT_ID_FIELD);
				fieldsToLoad.add(SearchConstants.COMPONENT_UUID_FIELD);
				fieldsToLoad.add(SearchConstants.COMPONENT_CODE_FIELD);
				Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
				Map<String, Object> map = new HashMap<>();
				map.put("id", Long.parseLong(document.get(SearchConstants.COMPONENT_ID_FIELD)));
				map.put("uuid", document.get(SearchConstants.COMPONENT_UUID_FIELD));
				map.put("code", document.get(SearchConstants.COMPONENT_CODE_FIELD));
				components.add(map);
			}
		} catch (IOException e) {
			logger.error("", e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
		return components;
	}

	@Override
	public List<Map<String, Object>> getSimilarBrands(String brandName, Integer size) {
		size = size == null || size < 1 ? SIMILAR_NUM : size;
		if (SearchUtils.isKeywordInvalid(brandName)) {
			throw new SearchException("输入无效：" + brandName);
		}
		List<Map<String, Object>> brands = new ArrayList<Map<String, Object>>();
		// 品牌名称带有空格，并且中英文名并无一定顺序，因此对nameCn、nameEn均要搜索
		BooleanQuery booleanQuery = new BooleanQuery();
		// 搜索nameCn
		booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMECN_FIELD, brandName),
				BooleanClause.Occur.SHOULD);
		// 搜索nameEn
		booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMEEN_FIELD, brandName),
				BooleanClause.Occur.SHOULD);
		logger.info(booleanQuery.toString());

        Sort sort = new Sort(new SortField(SearchConstants.BRAND_NAMEEN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(brandName)),
                new SortField(SearchConstants.BRAND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(brandName)));
		List<Document> documents = SearchUtils.getDocuments(SearchConstants.BRAND_TABLE_NAME, booleanQuery, sort, null, size)
				.getContent();
		for (Document document : documents) {
			Map<String, Object> brand = new HashMap<>();
			brand.put("id", Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
			brand.put("uuid", document.get(SearchConstants.BRAND_UUID_FIELD));
			brand.put("nameCn", document.get(SearchConstants.BRAND_NAMECN_FIELD));
			brand.put("nameEn", document.get(SearchConstants.BRAND_NAMEEN_FIELD));
			brands.add(brand);
		}
		return brands;
	}

	@Override
	public List<Map<String, Object>> getSimilarKinds(String kindName, Integer size) {
		size = size == null || size < 1 ? SIMILAR_NUM : size;
		return getSimilarKinds(kindName, null, null, size);
	}

	@Override
	public List<Map<String, Object>> getSimilarLeafKinds(String kindName, Integer size) {
		size = size == null || size < 1 ? SIMILAR_NUM : size;
		return getSimilarKinds(kindName, (short) 1, null, size);
	}

	@Override
	public List<Map<String, Object>> getSimilarKindsByLevel(String kindName, Short level, Integer size) {
		size = size == null || size < 1 ? SIMILAR_NUM : size;
		return getSimilarKinds(kindName, null, level, size);
	}

	/**
	 * 根据输入的类目名获取联想词
	 * 
	 * @param kindName
	 *            类目名
	 * @param isLeaf
	 *            是否只获取末级类目
	 * @param level
	 *            指定的类目级别
	 * @param size 指定的联想词数目
	 * @return
	 */
	private List<Map<String, Object>> getSimilarKinds(String kindName, Short isLeaf, Short level, Integer size) {
		size = size == null || size < 1 ? SIMILAR_NUM : size;
		if (SearchUtils.isKeywordInvalid(kindName)) {
			throw new SearchException("输入无效：" + kindName);
		}
		List<Map<String, Object>> kinds = new ArrayList<>();
		BooleanQuery booleanQuery = new BooleanQuery();
		booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.KIND_NAMECN_FIELD, kindName),
				BooleanClause.Occur.MUST);

		if (isLeaf != null && isLeaf == 1) {
			booleanQuery.add(new TermQuery(new Term(SearchConstants.KIND_ISLEAF_FIELD, String.valueOf(isLeaf))),
					BooleanClause.Occur.MUST);
		} else {
			if (level != null && level > 0) {
				booleanQuery.add(new TermQuery(new Term(SearchConstants.KIND_LEVEL_FIELD, String.valueOf(level))),
						BooleanClause.Occur.MUST);
			}
		}
		logger.info(booleanQuery.toString());

        Sort sort = new Sort(new SortField(SearchConstants.KIND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(kindName)));
		List<Document> documents = SearchUtils.getDocuments(SearchConstants.KIND_TABLE_NAME, booleanQuery, sort, null, size).getContent();
		for (Document document : documents) {
			Map<String, Object> map = new HashMap<>();
			map.put("id", Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
			map.put("nameCn", document.get(SearchConstants.KIND_NAMECN_FIELD));
			map.put("isLeaf", Short.parseShort(document.get(SearchConstants.KIND_ISLEAF_FIELD)));
			map.put("level", Short.parseShort(document.get(SearchConstants.KIND_LEVEL_FIELD)));
			kinds.add(map);
		}
		return kinds;
	}

	@Override
	public List<Map<String, String>> getSimilarPropertyValues(Long kindId, Long propertyId, String keyword,
			Long topNum) {
		if (kindId == null || propertyId == null) {
			throw new SearchException("类目id和属性id不能为空");
		}
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);

		String propertyIdString = String.valueOf(propertyId);
		if (!propertyIdString.startsWith(SearchConstants.COMPONENT_PROPERTY_PREFIX)) {
			propertyIdString = SearchConstants.COMPONENT_PROPERTY_PREFIX + propertyIdString;
		}
		propertyIdString = propertyIdString + SearchConstants.COMPONENT_PROPERTY_TOKENIZED_SUFFIX;
		if (keyword == null) {
			keyword = "";
		}
		if (topNum == null || topNum < 1) {
			topNum = (long) SIMILAR_NUM;
		}

		List<String> propertyValues = new ArrayList<>();
		try {
			BooleanQuery booleanQuery = new BooleanQuery();
			booleanQuery.add(new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, String.valueOf(kindId))),
					BooleanClause.Occur.MUST);
			booleanQuery.add(new PrefixQuery(new Term(propertyIdString, keyword.toLowerCase())),
					BooleanClause.Occur.MUST);
			logger.info(booleanQuery.toString());
			// 如果只搜索topNum个结果，去除重复的属性值后，数目很可能是不够的
			TopDocs topDocs = indexSearcher.search(booleanQuery, SearchConstants.TOP_NUM);
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			for (ScoreDoc scoreDoc : scoreDocs) {
				Set<String> fieldsToLoad = new HashSet<>();
				fieldsToLoad.add(propertyIdString);
				Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
				String propertyValue = document.get(propertyIdString);
				if (!StringUtils.isEmpty(propertyValue) && !propertyValues.contains(propertyValue)) {
					propertyValues.add(propertyValue);
				}
				if (propertyValues.size() >= topNum) {
					break;
				}
			}
		} catch (IOException e) {
			logger.error("", e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}

		List<Map<String, String>> result = new ArrayList<>();
		for (String propertyValue : propertyValues) {
			Map<String, String> map = new HashMap<>();
			map.put("propertyValue", propertyValue);
			result.add(map);
		}
		return result;

	}

	/**
	 * 根据输入获取相似的器件原厂型号
	 * 
	 * @param componentCode
	 * @param size 指定的联想词数目
	 * @return
	 */
	private List<String> getSimilarComponentCodes(String componentCode, Integer size) {
		return getSimilarValues(SearchConstants.COMPONENT_TABLE_NAME, SearchConstants.COMPONENT_CODE_FIELD,
                SearchConstants.COMPONENT_CODE_FIELD, componentCode.toLowerCase(), size, true);
	}

	/**
	 * 根据输入获取相似的品牌名称
	 * 
	 * @param brandName
	 * @param size 指定的联想词数目
	 * @return
	 */
	private List<String> getSimilarBrandNames(String brandName, Integer size) {
		// 获取相似的中文品牌
		List<String> nameCns = getSimilarValues(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_NAMECN_FIELD,
                SearchConstants.BRAND_NAMECN_UNTOKENIZED_FIELD, brandName, size, false);
		// 相似的中文品牌数量足够，直接返回
		if (nameCns != null && nameCns.size() == SIMILAR_NUM) {
			return nameCns;
		}

		List<String> names = nameCns;
		// 获取相似的英文品牌
		List<String> nameEns = getSimilarValues(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_NAMEEN_FIELD,
                SearchConstants.BRAND_NAMEEN_UNTOKENIZED_FIELD, brandName, size, false);
		names.addAll(nameEns);
		return names;
	}

	/**
	 * 根据输入获取相似的类目名称
	 * 
	 * @param kindName
	 * @param size 指定的联想词数目
	 * @return
	 */
	private List<String> getSimilarKindNames(String kindName, Integer size) {
		return getSimilarValues(SearchConstants.KIND_TABLE_NAME, SearchConstants.KIND_NAMECN_FIELD,
                SearchConstants.KIND_NAMECN_UNTOKENIZED_FIELD, kindName, size, false);
	}

    /**
     * 根据输入值获取该域相似的值
     *
     * @param tableName      表名
     * @param field          联想字段
     * @param sortField      排序字段
     * @param keyword        关键词
     * @param size           联想词数目
     * @param usePrefixQuery 是否使用 PrefixQuery
     * @return 联想词
     */
    private List<String> getSimilarValues(String tableName, String field, String sortField, String keyword, Integer size, boolean usePrefixQuery) {
		if (SearchUtils.isKeywordInvalid(keyword)) {
			throw new SearchException("输入无效：" + keyword);
		}
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName);

		List<String> result = new ArrayList<>();
		try {
			Query query;
            if(usePrefixQuery){
                query = new PrefixQuery(new Term(field, keyword.toLowerCase()));
            }else{
                query = SearchUtils.getBooleanQuery(field, keyword);
            }
			logger.info(query.toString());
			Sort sort = new Sort(new SortField(sortField, new StringFieldComparatorSource(keyword)));
			TopDocs hits = indexSearcher.search(query, size, sort);
			ScoreDoc[] scoreDocs = hits.scoreDocs;
			for (ScoreDoc scoreDoc : scoreDocs) {
				Set<String> fieldsToLoad = new HashSet<>();
				fieldsToLoad.add(field);
				Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
				result.add(document.get(field));
				// System.out.println(document.get(field) + "\t" +
				// scoreDoc.score);
			}
		} catch (IOException e) {
			logger.error("", e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
		return result;
	}

	/**
	 * 移除集合中重复的元素
	 * 
	 * @param list
	 * @return
	 */
	private void removeDuplicate(List<String> list) {
		if (list == null) {
			return;
		}
		List<String> result = new ArrayList<>();
		for (String str : list) {
			if (!result.contains(str)) {
				result.add(str);
			}
		}
		list.removeAll(list);
		list.addAll(result);
	}

	/**
	 * 删除集合内 startIndex（含）后的元素
	 * 
	 * @param list
	 * @param startIndex
	 */
	private void removeElements(List<? extends String> list, int startIndex) {
		if (CollectionUtils.isEmpty(list)) {
			return;
		}
		int listsSize = list.size();
		for (int i = listsSize - 1; i >= startIndex; i--) {
			list.remove(i);
		}
	}

	@Override
	public Map<String, Object> getGoodsIds(String keyword, PageParams pageParams) throws SearchException {
		List<String> keywordFields = new ArrayList<>();
		// 先根据品牌搜索，品牌不存在再搜索型号等
		keywordFields.add(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD);
		keywordFields.add(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD);
		Map<String, Object> goodsIds = getGoodsIds(keyword, keywordFields, false, pageParams, false);
		if (CollectionUtils.isEmpty(goodsIds) || goodsIds.get("componentIds") == null
				|| JSONObject.parseArray(goodsIds.get("componentIds").toString()).isEmpty()) {
			goodsIds = getGoodsIds(keyword, null, true, pageParams, true);
		}
		return goodsIds;
	}

	/**
	 * @param keyword
	 * @param keywordFields
	 *            要查询的字段
	 * @param tokenized
	 *            是否分词
	 * @param pageParams
	 * @param recursivelyGet 是否递归获取（逐步降低精度，直到只匹配一个字符）
	 * @return
	 * @throws SearchException
	 */
	private Map<String, Object> getGoodsIds(String keyword, List<String> keywordFields, Boolean tokenized,
			PageParams pageParams, Boolean recursivelyGet) throws SearchException {
		// 因为器件、属性值的数据量远比类目、品牌大得多，而且器件搜索可能还需进行分页，
		// 所以涉及器件、属性值的搜索，大都不能像类目和品牌一样直接利用SearchUtils.getDocuments方法
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.GOODS_TABLE_NAME);

		if (pageParams == null) {
			pageParams = new PageParams();
		}
		if (pageParams.getPage() <= 0)
			pageParams.setPage(1);
		if (pageParams.getSize() <= 0)
			pageParams.setSize(20);

		Map<String, Object> map = new HashMap<String, Object>();
		List<Long> cmpIds = new ArrayList<>();
		List<Long> goIds = new ArrayList<>();
		try {
			BooleanQuery booleanQuery = queryGoods(keyword, keywordFields, tokenized);
			setGoodsFilter(pageParams.getFilters(), booleanQuery);
			logger.info(booleanQuery.toString());

			// 排序
            // 自定义排序字段 > 批次（访问量） > 器件（访问量 > 搜索次数） > 品牌（权重 > 访问量 > 搜索次数） > 类目（访问量 > 搜索次数） > 分数
            SortField[] customSortFields = sortGoods(keyword);
            List<SortField> sortFieldList = new ArrayList<>();
            sortFieldList.add(new SortField(SearchConstants.GOODS_CMP_CODE_FIELD, new StringFieldComparatorSource(keyword, true)));
            sortFieldList.add(new SortField(SearchConstants.GOODS_PR_PCMPCODE_FIELD, new StringFieldComparatorSource(keyword, true)));
			List<com.uas.search.constant.model.Sort> sorts = pageParams.getSort();
			if (sorts != null && !CollectionUtils.isEmpty(sorts)) {
				for (com.uas.search.constant.model.Sort s : sorts) {
					if (s.getField() == null) {
						throw new SearchException("排序字段不可为空：" + s);
					}
					switch (s.getField()) {
					// 库存
					case GO_RESERVE:
						sortFieldList.add(new SortField(SearchConstants.GOODS_GO_RESERVE_FIELD, Type.DOUBLE,
								s.isReverse()));
						break;
					// 人民币价格
					case GO_MINPRICERMB:
						sortFieldList.add(new SortField(SearchConstants.GOODS_GO_MINPRICERMB_FIELD, Type.DOUBLE,
								s.isReverse()));
						break;
					// 美元价格
					case GO_MINPRICEUSD:
						sortFieldList.add(new SortField(SearchConstants.GOODS_GO_MINPRICEUSD_FIELD, Type.DOUBLE,
								s.isReverse()));
						break;
					// 打分
					case GO_SEARCH:
                        sortFieldList.addAll(Arrays.asList(customSortFields));
						break;
					default:
						throw new SearchException("不支持该排序方式：" + s.getField());
					}
				}
			} else{
                sortFieldList.addAll(Arrays.asList(customSortFields));
            }
            SortField[] sortFields = new SortField[sortFieldList.size()];
            sortFieldList.toArray(sortFields);
            Sort sort = new Sort(sortFields);

			TopDocs hits;
			if (pageParams.getPage() > 1) {// 不是第一页
				TopDocs previousHits = indexSearcher.search(booleanQuery,
						(pageParams.getPage() - 1) * pageParams.getSize(), sort, true, false);
				int totalHits = previousHits.totalHits;
				if ((pageParams.getPage() - 1) * pageParams.getSize() >= totalHits) {
                    // 如果没有结果，则降低精度，直至 keyword 长度为 1
                    if(recursivelyGet && totalHits < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1){
                        return getGoodsIds(keyword.substring(0, keyword.length() - 1), keywordFields, tokenized, pageParams, recursivelyGet);
                    }
					return map;
				}
				ScoreDoc[] previousScoreDocs = previousHits.scoreDocs;
				ScoreDoc after = previousScoreDocs[previousScoreDocs.length - 1];
				hits = indexSearcher.searchAfter(after, booleanQuery, pageParams.getSize(), sort, true, false);
			} else {
				hits = indexSearcher.search(booleanQuery, pageParams.getSize(), sort, true, false);
			}

			// 如果没有结果，则降低精度，直至 keyword 长度为 1
			if(recursivelyGet && hits.totalHits < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1){
				return getGoodsIds(keyword.substring(0, keyword.length() - 1), keywordFields, tokenized, pageParams, recursivelyGet);
			}
			// 数据量太大，需要指定将获取的数据（以免载入不必要的数据，降低速度）
			Set<String> fieldsToLoad = new HashSet<>();
			fieldsToLoad.add(SearchConstants.GOODS_CMP_ID_FIELD);
			fieldsToLoad.add(SearchConstants.GOODS_GO_ID_FIELD);
			ScoreDoc[] scoreDocs = hits.scoreDocs;
			for (ScoreDoc scoreDoc : scoreDocs) {
				Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
				String cmpId = document.get(SearchConstants.GOODS_CMP_ID_FIELD);
				cmpIds.add(StringUtils.isEmpty(cmpId) ? null : Long.valueOf(cmpId));
				String goId = document.get(SearchConstants.GOODS_GO_ID_FIELD);
				goIds.add(StringUtils.isEmpty(goId) ? null : Long.valueOf(goId));
				// System.out.println(cmpId + "\t" + goId + "\t" +
				// scoreDoc.score);
				// System.out.println(indexSearcher.explain(booleanQuery,
				// scoreDoc.doc).toString());
			}
			map.put("componentIds", cmpIds);
			map.put("goodsIds", goIds);
			map.put("page", pageParams.getPage());
			map.put("size", pageParams.getSize());
			map.put("total", hits.totalHits);
		} catch (IOException e) {
			logger.error("", e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
		return map;
	}

    /**
     * 设置批次过滤条件
     *
     * @param filters 指定的过滤条件
     * @param query   原查询
     */
    private void setGoodsFilter(Map<FilterField, Object> filters, BooleanQuery query) {
        Object status;
        // 筛选状态
        if (!CollectionUtils.isEmpty(filters) && !StringUtils.isEmpty(filters.get(FilterField.GOODS_STATUS))) {
            // 如果明确指定了状态，则直接过滤批次（结果中不包括没有批次的器件）
            status = filters.get(FilterField.GOODS_STATUS);
            filter(status, SearchConstants.GOODS_GO_STATUS_FIELD, query);
        } else {
            // 如果未明确指定状态，则使用默认状态分情况进行过滤（结果中包括没有批次的器件）
            status = Arrays.asList(TradeGoods.VALID_STATUS);
            // 批次 id 不为空时，对状态过滤
            Query goIdNotNullQuery = SearchUtils.getNotNullQuery(SearchConstants.GOODS_GO_ID_FIELD);
            BooleanQuery q1 = new BooleanQuery();
            q1.add(goIdNotNullQuery, Occur.MUST);
            filter(status, SearchConstants.GOODS_GO_STATUS_FIELD, q1);
            // 或者批次 id 为空（此时是器件）
            BooleanQuery q2 = new BooleanQuery();
            q2.add(SearchUtils.getNotNullQuery(SearchConstants.GOODS_CMP_ID_FIELD), Occur.SHOULD);
            q2.add(goIdNotNullQuery, Occur.MUST_NOT);

            BooleanQuery booleanQuery = new BooleanQuery();
            booleanQuery.add(q1, Occur.SHOULD);
            booleanQuery.add(q2, Occur.SHOULD);
            query.add(booleanQuery, Occur.FILTER);
        }

        if (CollectionUtils.isEmpty(filters)) {
            return;
        }
        // 筛选类目
        if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_KINDID))) {
            filter(filters.get(FilterField.GOODS_KINDID), SearchConstants.GOODS_KI_ID_FIELD, query);
        }

        // 筛选品牌
        if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_BRANDID))) {
            filter(filters.get(FilterField.GOODS_BRANDID), SearchConstants.GOODS_BR_ID_FIELD, query);
        }

        // 筛选货源
        if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_STORE_TYPE))) {
            filter(filters.get(FilterField.GOODS_STORE_TYPE), SearchConstants.GOODS_ST_TYPE_FIELD, query);
        }

        // 筛选货币
        if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_CRNAME))) {
            filter(filters.get(FilterField.GOODS_CRNAME), SearchConstants.GOODS_CRNAME_FIELD, query);
        }

        // 价格筛选
        Object minPriceRmb = filters.get(FilterField.GOODS_MINPRICERMB);
        Object maxPriceRmb = filters.get(FilterField.GOODS_MAXPRICERMB);
        Object minPriceUsd = filters.get(FilterField.GOODS_MINPRICEUSD);
        Object maxPriceUsd = filters.get(FilterField.GOODS_MAXPRICEUSD);
        // 筛选人民币价格
        if (!StringUtils.isEmpty(minPriceRmb) || !StringUtils.isEmpty(maxPriceRmb)) {
            Double minPrice = null;
            Double maxPrice = null;
            if (!StringUtils.isEmpty(minPriceRmb)) {
                minPrice = Double.valueOf(minPriceRmb.toString());
            }
            if (!StringUtils.isEmpty(maxPriceRmb)) {
                maxPrice = Double.valueOf(maxPriceRmb.toString());
            }
            query.add(NumericRangeQuery.newDoubleRange(SearchConstants.GOODS_GO_MINPRICERMB_FIELD,
                    minPrice, maxPrice, true, true), BooleanClause.Occur.FILTER);
        }
        // 筛选美元价格
        if (!StringUtils.isEmpty(minPriceUsd) || !StringUtils.isEmpty(maxPriceUsd)) {
            Double minPrice = null;
            Double maxPrice = null;
            if (!StringUtils.isEmpty(minPriceUsd)) {
                minPrice = Double.valueOf(minPriceUsd.toString());
            }
            if (!StringUtils.isEmpty(maxPriceUsd)) {
                maxPrice = Double.valueOf(maxPriceUsd.toString());
            }
            query.add(NumericRangeQuery.newDoubleRange(SearchConstants.GOODS_GO_MINPRICEUSD_FIELD,
                    minPrice, maxPrice, true, true), BooleanClause.Occur.FILTER);
        }
    }

    /**
     * @return 批次排序规则
     */
    private SortField[] sortGoods(String keyword) {
        // 器件、非标型号自定义排序 > 批次（访问量） > 器件（自定义排序 > 访问量 > 搜索次数） > 品牌（自定义排序 > 权重 > 访问量 > 搜索次数） > 类目（访问量 > 搜索次数）
        return new SortField[]{
                sortField(SearchConstants.GOODS_GO_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.GOODS_CMP_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.GOODS_CMP_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.GOODS_BR_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
                sortField(SearchConstants.GOODS_BR_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.GOODS_BR_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.GOODS_KI_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                sortField(SearchConstants.GOODS_KI_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
                new SortField(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
                new SortField(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
                new SortField(SearchConstants.GOODS_KI_NAME_CN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword))
        };
    }

	@Override
	public List<Map<String, Object>> collectBySearchGoods(String keyword, CollectField collectedField,
			Map<FilterField, Object> filters) {
		List<String> keywordFields = new ArrayList<>();
		// 先根据品牌搜索，品牌不存在再搜索型号等
		keywordFields.add(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD);
		keywordFields.add(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD);
		List<Map<String, Object>> result = collectBySearchGoods(keyword, keywordFields, false, collectedField, filters, false);
		if (CollectionUtils.isEmpty(result)) {
			result = collectBySearchGoods(keyword, null, true, collectedField, filters, true);
		}
		return result;

	}

	/**
	 * @param keyword
	 * @param keywordFields
	 *            要查询的字段
	 * @param tokenized
	 *            是否分词
	 * @param collectedField
	 * @param filters
	 * @param recursivelyGet 是否递归获取（逐步降低精度，直到只匹配一个字符）
	 * @return
	 */
	private List<Map<String, Object>> collectBySearchGoods(String keyword, List<String> keywordFields,
			Boolean tokenized, CollectField collectedField, Map<FilterField, Object> filters, Boolean recursivelyGet) {
		if (collectedField == null && CollectionUtils.isEmpty(filters)) {
			throw new SearchException("参数不合法：collectedField=" + collectedField + ", filter=" + filters);
		}
		// 与批次搜索的搜索词保持一致，最终有结果的 keyword 是相同的
		if(recursivelyGet != null && recursivelyGet){
			PageParams pageParams = new PageParams();
			pageParams.setFilters(filters);
			Map<String, Object> goodsIds = getGoodsIds(keyword, keywordFields, tokenized, pageParams, false);
			if(Integer.parseInt(goodsIds.get("total").toString()) < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1){
				return collectBySearchGoods(keyword.substring(0, keyword.length() - 1), keywordFields, tokenized, collectedField, filters, true);
			}
		}
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.GOODS_TABLE_NAME);

		List<Map<String, Object>> result = new ArrayList<>();
		try {
			BooleanQuery booleanQuery = queryGoods(keyword, keywordFields, tokenized);
			setGoodsFilter(filters, booleanQuery);
			logger.info(booleanQuery.toString());

			// 统计
			String uniqueField;
			Set<String> fieldsToLoad = new HashSet<>();
			switch (collectedField) {
			case GOODS_KIND:
				uniqueField = SearchConstants.GOODS_KI_ID_FIELD;
				fieldsToLoad.add(SearchConstants.GOODS_KI_ID_FIELD);
				fieldsToLoad.add(SearchConstants.GOODS_KI_NAME_CN_FIELD);
				break;
			case GOODS_BRAND:
				uniqueField = SearchConstants.GOODS_BR_ID_FIELD;
				fieldsToLoad.add(SearchConstants.GOODS_BR_ID_FIELD);
				fieldsToLoad.add(SearchConstants.GOODS_BR_UUID_FIELD);
				fieldsToLoad.add(SearchConstants.GOODS_BR_NAME_CN_FIELD);
				fieldsToLoad.add(SearchConstants.GOODS_BR_NAME_EN_FIELD);
				break;
			case GOODS_STORE_TYPE:
				uniqueField = SearchConstants.GOODS_ST_TYPE_FIELD;
				fieldsToLoad.add(SearchConstants.GOODS_ST_TYPE_FIELD);
				break;
			case GOODS_CRNAME:
				uniqueField = SearchConstants.GOODS_CRNAME_FIELD;
				fieldsToLoad.add(SearchConstants.GOODS_CRNAME_FIELD);
				break;
			default:
				throw new SearchException("不支持该统计字段：" + collectedField);
			}
			GoodsGroupCollector collector = new GoodsGroupCollector(uniqueField, fieldsToLoad);
			indexSearcher.search(booleanQuery, collector);
			result = collector.getValues();
		} catch (IOException e) {
			logger.error("", e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
		return result;
	}

	/**
	 * 获取查询批次的query
	 * 
	 * @param keyword
	 * @param keywordFields
	 * @param tokenized
	 * @return
	 */
	private BooleanQuery queryGoods(String keyword, List<String> keywordFields, Boolean tokenized) {
		BooleanQuery booleanQuery = new BooleanQuery();
		if (!SearchUtils.isKeywordInvalid(keyword)) {
			// 未指定搜索的字段，则采用默认搜索逻辑
			if (CollectionUtils.isEmpty(keywordFields)) {
				booleanQuery.add(setGoodsBoost(keyword), BooleanClause.Occur.MUST);
			} else {
				BooleanQuery booleanQuery2 = new BooleanQuery();
				for (String keywordField : keywordFields) {
					// 是否分词
					if (tokenized == null || !tokenized.booleanValue()) {
						booleanQuery2.add(new TermQuery(new Term(keywordField, keyword.toLowerCase())), BooleanClause.Occur.SHOULD);
					} else {
						booleanQuery2.add(SearchUtils.getBooleanQuery(keywordField, keyword), BooleanClause.Occur.SHOULD);
					}
				}
				booleanQuery.add(booleanQuery2, BooleanClause.Occur.MUST);
			}
		}
		return booleanQuery;
	}

	/**
	 * 同时搜索器件、类目、品牌等，并设置boost
	 */
	private Query setGoodsBoost(String keyword) {
		BooleanQuery booleanQuery = new BooleanQuery();
        // 前缀搜索（字段并未分词，进行分词搜索时，会有边界问题，如搜索 'BC807-40,215'）
        booleanQuery.add(new PrefixQuery(new Term(SearchConstants.GOODS_CMP_CODE_FIELD, keyword.toLowerCase())), BooleanClause.Occur.SHOULD);
        booleanQuery.add(new PrefixQuery(new Term(SearchConstants.GOODS_PR_PCMPCODE_FIELD, keyword.toLowerCase())), BooleanClause.Occur.SHOULD);
		// 原厂型号
		booleanQuery.add(createQuery(SearchConstants.GOODS_CMP_CODE_FIELD, keyword, 100), BooleanClause.Occur.SHOULD);
		// 非标
		booleanQuery.add(createQuery(SearchConstants.GOODS_PR_PCMPCODE_FIELD, keyword, 100), Occur.SHOULD);
		// 品牌
		booleanQuery.add(createQuery(SearchConstants.GOODS_BR_NAME_CN_FIELD, keyword, 10), BooleanClause.Occur.SHOULD);
		booleanQuery.add(createQuery(SearchConstants.GOODS_BR_NAME_EN_FIELD, keyword, 10), BooleanClause.Occur.SHOULD);
		// 类目
		booleanQuery.add(createQuery(SearchConstants.GOODS_KI_NAME_CN_FIELD, keyword, 1), BooleanClause.Occur.SHOULD);
		// 属性值
		booleanQuery.add(createQuery(SearchConstants.GOODS_CMP_DESCRIPTION_FIELD, keyword, 1), BooleanClause.Occur.SHOULD);
		return booleanQuery;
	}

    private BooleanQuery createQuery(String field, String keyword, float boost){
       return createQuery(field, keyword, false, boost);
    }


    private BooleanQuery createQuery(String field, String keyword, boolean useRegexpQuery, float boost){
        BooleanQuery booleanQuery = new BooleanQuery();
        if (StringUtils.isEmpty(field) || StringUtils.isEmpty(keyword)) {
            return booleanQuery;
        }
        // 根据空格分隔关键词，分隔的词取或的关系
        String[] array = keyword.split(" ");
        for(String str : array){
            if(!StringUtils.isEmpty(str)){
                booleanQuery.add(SearchUtils.getBooleanQuery(field, str, useRegexpQuery), Occur.SHOULD);
            }
        }
        booleanQuery.setBoost(boost);
        return booleanQuery;
    }

	/**
	 * 过滤
	 * 
	 * @param list
	 *            过滤值列表
	 * @param field
	 *            过滤的字段
	 * @param booleanQuery
	 *            查询条件
	 */
	@SuppressWarnings("unchecked")
	private void filter(Object list, String field, BooleanQuery booleanQuery) {
		List<Object> values;
		if (list instanceof List) {
			values = (List<Object>) list;
		}else{
			values = new ArrayList<>();
			values.add(list);
		}
		BooleanQuery booleanQuery2 = new BooleanQuery();
		for (Object value : values) {
			TermQuery query = new TermQuery(new Term(field, value.toString().toLowerCase()));
			booleanQuery2.add(query, BooleanClause.Occur.SHOULD);
		}
		booleanQuery.add(booleanQuery2, BooleanClause.Occur.FILTER);
	}

	@Override
	public Kind getKind(Long id) {
		return DocumentToObjectUtils.toKind(
				SearchUtils.getDocumentById(SearchConstants.KIND_TABLE_NAME, SearchConstants.KIND_ID_FIELD, id));
	}

	@Override
	public Brand getBrand(Long id) {
		return DocumentToObjectUtils.toBrand(
				SearchUtils.getDocumentById(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_ID_FIELD, id));
	}

	@Override
	public Component getComponent(Long id) {
		return DocumentToObjectUtils.toComponent(SearchUtils.getDocumentById(SearchConstants.COMPONENT_TABLE_NAME,
				SearchConstants.COMPONENT_ID_FIELD, id));
	}

	@Override
	public Goods getGoods(String id) {
		return DocumentToObjectUtils.toGoods(
				SearchUtils.getDocumentById(SearchConstants.GOODS_TABLE_NAME, SearchConstants.GOODS_GO_ID_FIELD, id));
	}

	@Override
	public SPage<Object> getObjects(String tableName, String keyword, String field, Boolean tokenized, @NotEmpty("page") Integer page, @NotEmpty("size") Integer size) {
		if (keyword == null) {
			keyword = "";
		}
		if (field == null) {
			field = SearchUtils.getIdField(tableName);
		}
		if (tokenized == null) {
			tokenized = false;
		}
		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName);
		SPage<Object> sPage = new SPage<>();
		try {
			Query query;
			if (tokenized) {
				query = SearchUtils.getBooleanQuery(field, keyword);
			} else {
				query = SearchUtils.getRegexpQuery(field, keyword);
			}
			// 分页信息
			if (page > 0) {
				sPage.setPage(page);
			} else {
				sPage.setPage(1);
				sPage.setFirst(true);
			}
			if (size > 0) {
				sPage.setSize(size);
			} else {
				sPage.setSize(20);
			}

			TopDocs topDocs;
			// 如果页码不为1
			if (sPage.getPage() > 1) {
				TopDocs previousTopDocs = indexSearcher.search(query, (sPage.getPage() - 1) * sPage.getSize());
				int totalHits = previousTopDocs.totalHits;
				ScoreDoc[] previousScoreDocs = previousTopDocs.scoreDocs;
				if ((sPage.getPage() - 1) * sPage.getSize() >= totalHits) {
					throw new SearchException("页码过大：元素总数量为" + totalHits);
				}
				topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], query,
						sPage.getSize());
			} else {
				sPage.setFirst(true);
				topDocs = indexSearcher.search(query, 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);
			}
			List<Object> content = new ArrayList<>();
			for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
				Document document = indexSearcher.doc(scoreDoc.doc);
				content.add(DocumentToObjectUtils.toObject(document, tableName));
			}
			sPage.setContent(content);
		} catch (IOException e) {
			throw new SearchException(e).setDetailedMessage(e);
		} finally {
			SearchUtils.releaseIndexSearcher(indexSearcher);
		}
		return sPage;
	}

}
