package com.everdata.command;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Stack;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.deletebyquery.DeleteByQueryRequestBuilder;
import org.elasticsearch.action.deletebyquery.DeleteByQueryResponse;
import org.elasticsearch.action.deletebyquery.IndexDeleteByQueryResponse;
import org.elasticsearch.action.search.ClearScrollRequestBuilder;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram.Bucket;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram.Interval;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramBuilder;
import org.elasticsearch.search.aggregations.metrics.NumericMetricsAggregator.MultiValue;
import org.elasticsearch.search.aggregations.metrics.NumericMetricsAggregator.SingleValue;
import org.elasticsearch.search.aggregations.metrics.avg.Avg;
import org.elasticsearch.search.aggregations.metrics.cardinality.Cardinality;
import org.elasticsearch.search.aggregations.metrics.max.Max;
import org.elasticsearch.search.aggregations.metrics.min.Min;
import org.elasticsearch.search.aggregations.metrics.sum.Sum;
import org.elasticsearch.search.aggregations.metrics.valuecount.ValueCount;
import org.elasticsearch.search.sort.SortOrder;

import com.everdata.command.JoinQuery.Join;
import com.everdata.parser.AST_Join;
import com.everdata.parser.AST_Search;
import com.everdata.parser.AST_Sort;
import com.everdata.parser.AST_Stats;
import com.everdata.parser.AST_Table;
//import com.everdata.parser.AST_Top;
import com.everdata.parser.CommandParser;
import com.everdata.parser.Node;

public class Search {
	public final static String PREFIX_SEARCH_STRING = "SEARCH";

	public static class QueryResponse {
		public long took, totalHits, failedShards, successfulShards,
				totalShards;
		public ArrayList<Map<String, Object>> searchHits = null;

		public QueryResponse(int capcity) {
			searchHits = new ArrayList<Map<String, Object>>(capcity);
		}
	}

	public static final int QUERY = 0;
	public static final int REPORT = 1;
	public static final int TIMELINE = 2;
	public static final int DELETE = 2;

	ESLogger logger;
	Client client;
	String starttime, endtime;
	public SearchRequestBuilder querySearch, reportSearch, timelineSearch, cardSearch;
	DeleteByQueryRequestBuilder deleteSearch;
	
	public String[] indices, sourceTypes;

	public ArrayList<Join> joinSearchs = new ArrayList<Join>();

	public ArrayList<com.everdata.parser.AST_Stats.Bucket> bucketFields = null;
	public ArrayList<Function> statsFields = new ArrayList<Function>();
	
	public ArrayList<String> tableFieldNames = new ArrayList<String>(); 

	// public Function countField = null;

	public static String[] parseIndices(AST_Search searchTree, Client client) throws CommandException {
		String[] indices = Strings.EMPTY_ARRAY;

		// 过滤不存在的index,不然查询会失败
		// 如果所有指定的index都不存在,那么将在所有的index查询该条件
		String[] originIndices = ((String) searchTree.getOption(Option.INDEX))
				.split(",");
		ArrayList<String> listIndices = new ArrayList<String>();

		for (String index : originIndices) {
			if (client.admin().indices()
					.exists(new IndicesExistsRequest(index)).actionGet()
					.isExists()) {
				listIndices.add(index);
			}
		}

		if (listIndices.size() > 0)
			indices = listIndices.toArray(new String[listIndices.size()]);
		else
			throw new CommandException(String.format("%s index not exsit", searchTree.getOption(Option.INDEX)));
		
		return indices;

	}

	public static String[] parseTypes(AST_Search searchTree) {
		String[] sourceTypes = Strings.EMPTY_ARRAY;

		if (searchTree.getOption(Option.SOURCETYPE) != null)
			sourceTypes = ((String) searchTree.getOption(Option.SOURCETYPE))
					.split(",");

		return sourceTypes;
	}

	// 全命令支持
	public Search(CommandParser parser, Client client, ESLogger logger)
			throws CommandException, IOException {

		this.client = client;
		this.logger = logger;

		ArrayList<Node> searchCommands = parser.getSearchCommandList();

		// search rolling out
		AST_Search searchTree = (AST_Search) searchCommands.get(0);

		indices = parseIndices(searchTree, client);
		sourceTypes = parseTypes(searchTree);
		this.querySearch = client.prepareSearch(indices).setTypes(sourceTypes)
				.setQuery(searchTree.getQueryBuilder());
		this.timelineSearch = client.prepareSearch(indices)
				.setTypes(sourceTypes).setQuery(searchTree.getQueryBuilder());
		this.reportSearch = client.prepareSearch(indices).setTypes(sourceTypes)
				.setQuery(searchTree.getQueryBuilder());
		this.cardSearch = client.prepareSearch(indices).setTypes(sourceTypes)
				.setQuery(searchTree.getQueryBuilder());
		this.deleteSearch = client.prepareDeleteByQuery(indices)
				.setTypes(sourceTypes).setQuery(searchTree.getQueryBuilder());

		for (int i = 1; i < searchCommands.size(); i++) {

			if (searchCommands.get(i) instanceof AST_Sort) {
				AST_Sort sortTree = (AST_Sort) searchCommands.get(i);

				for (AST_Sort.SortField field : sortTree.sortFields) {
					addSortToQuery2(field.field, field.desc);
				}

			} else if (searchCommands.get(i) instanceof AST_Join) {
				AST_Join joinTree = (AST_Join) searchCommands.get(i);
				joinSearchs.add(joinTree.getJoin());
			} else if (searchCommands.get(i) instanceof AST_Table) {
				AST_Table tableTree = (AST_Table) searchCommands.get(i);
				if (tableTree.getTables() != null) {
					querySearch.setFetchSource(tableTree.getTables(), null);
					for(String fieldName: tableTree.getTables())
					tableFieldNames.add(fieldName);
				}
			}
		}

		ArrayList<Node> reportCommands = parser.getReportCommandList();

		if (reportCommands.size() > 0) {

			ArrayList<AbstractAggregationBuilder> stats = new ArrayList<AbstractAggregationBuilder>();

			for (Node child : reportCommands) {
				if (child instanceof AST_Stats) {
					
					for (AbstractAggregationBuilder stat : ((AST_Stats) child).getStats()) {
						stats.add(stat);
					}
					
					bucketFields = ((AST_Stats) child).bucketFields();

					statsFields = ((AST_Stats) child).statsFields();
					
					
					for (int idx = 0; idx < bucketFields.size() ; idx ++) {
						
						AST_Stats.Bucket b = bucketFields.get(idx);
							
						if(b.type == AST_Stats.Bucket.TERMWITHCARD){
							((AST_Stats) child).setBucketLimit(idx, executeCardinary(b.bucketField));
						}
						
					}

				} /*else if (child instanceof AST_Top) {
					stats.add(((AST_Top) child).getTop());

					for (String key : ((AST_Top) child).bucketFields())
						bucketFields.add(key);

					for (String key : ((AST_Top) child).topFields())
						funcFields.add(key);

				} 
				else if (child instanceof AST_Sort) {
					
					for (AST_Sort.SortField field : ((AST_Sort) child).sortFields) {
						addSortToReport(field.field, field.desc);
					}
					
				}*/

			}

			for (AbstractAggregationBuilder stat : stats) {
				reportSearch.addAggregation(stat);
			}

		}

	}
	
	private void addSortToQuery2(String field, boolean desc) {		
		querySearch.addSort(field, desc?SortOrder.DESC:SortOrder.ASC);		
	}

	private void addSortToQuery(String field) {
		boolean desc = true;
		if(field.startsWith("-")){
			desc = true;
			field = field.substring(1);
		}else if(field.startsWith("+")){
			desc = false;
			field = field.substring(1);
		}
		querySearch.addSort(field, desc?SortOrder.DESC:SortOrder.ASC);		
	}
	/*
	private void addSortToReport(String field) {
		
		if (field == null) {
			return;
		} else if (field.startsWith("+")) {
			querySearch.addSort(field.substring(1), SortOrder.ASC);
		} else if (field.startsWith("-")) {
			querySearch.addSort(field.substring(1), SortOrder.DESC);
		} else
			querySearch.addSort(field, SortOrder.ASC);
		
	}
	*/
	public static void buildQuery(int from, XContentBuilder builder,
			SearchResponse response, ESLogger logger, List<String> fieldNames, boolean showMeta) throws IOException {

		logger.info("Query took in millseconds:" + response.getTookInMillis());

		// 格式化结果后输出

		builder.startObject();
		builder.field("took", response.getTookInMillis());
		builder.field("total", response.getHits().getTotalHits());
		builder.field("from", from);

		builder.field("_shard_failed", response.getFailedShards());
		builder.field("_shard_successful", response.getSuccessfulShards());
		builder.field("_shard_total", response.getTotalShards());
		builder.startArray("fields");

		if (response.getHits().getTotalHits() == 0) {
			builder.endArray();
			return;
		}
		
		if(showMeta){
			builder.value("_id");
			builder.value("_index");
			builder.value("_type");
		}
		
		List<String> fields = null;
		
		if(fieldNames != null && fieldNames.size() >0 ){
			fields = fieldNames;
		}else{
			fields = new ArrayList<String>(response.getHits().getAt(0).sourceAsMap().keySet());		
			java.util.Collections.sort(fields);
		}
		
		for (String key : fields) {
			builder.value(key);
		}
		builder.endArray();

		builder.startArray("rows");
		Iterator<SearchHit> iterator = response.getHits().iterator();

		while (iterator.hasNext()) {
			SearchHit _row = iterator.next();

			builder.startArray();
			
			if(showMeta){
				builder.value(_row.getId());
				builder.value(_row.getIndex());
				builder.value(_row.getType());
			}
			
			for (String key : fields) {
				builder.value(_row.sourceAsMap().get(key));
			}

			builder.endArray();

		}
		builder.endArray().endObject();

	}
	public static void buildQuery(int from, XContentBuilder builder,
			QueryResponse response, ESLogger logger, List<String> fieldNames, boolean showMeta) throws IOException {

		logger.info("Query took in millseconds:" + response.took);

		// 格式化结果后输出

		builder.startObject();
		builder.field("took", response.took);
		builder.field("total", response.totalHits);
		builder.field("from", from);

		builder.field("_shard_failed", response.failedShards);
		builder.field("_shard_successful", response.successfulShards);
		builder.field("_shard_total", response.totalShards);
		builder.startArray("fields");

		if (response.searchHits.size() == 0) {
			builder.endArray();
			return;
		}

		List<String> fields = null;
		
		if(fieldNames  != null && fieldNames.size() > 0){
			fields = fieldNames;			
			fields.add(0,"_id");
			fields.add(1,"_index");
			fields.add(2,"_type");
		}else{
			fields = new ArrayList<String>(response.searchHits.get(0)
					.keySet());			
			java.util.Collections.sort(fields);
		}
		
		if(!showMeta){
			fields.remove("_id");
			fields.remove("_index");
			fields.remove("_type");
		}
		
		for (String key : fields) {
			builder.value(key);
		}
		builder.endArray();

		builder.startArray("rows");
		for (Map<String, Object> _row : response.searchHits) {

			builder.startArray();

			for (String key : fields) {
				builder.value(_row.get(key));
			}

			builder.endArray();

		}
		builder.endArray().endObject();

	}

	public void buildDelete(XContentBuilder builder,
			DeleteByQueryResponse response) throws IOException {

		builder.startObject();
		builder.startObject("_indices");
		for (IndexDeleteByQueryResponse indexDeleteByQueryResponse : response
				.getIndices().values()) {
			builder.startObject(indexDeleteByQueryResponse.getIndex(),
					XContentBuilder.FieldCaseConversion.NONE);

			builder.startObject("_shards");
			builder.field("total", indexDeleteByQueryResponse.getTotalShards());
			builder.field("successful",
					indexDeleteByQueryResponse.getSuccessfulShards());
			builder.field("failed",
					indexDeleteByQueryResponse.getFailedShards());
			builder.endObject();

			builder.endObject();
		}
		builder.endObject();
		builder.endObject();
	}

	public static void buildTimeline(XContentBuilder builder,
			SearchResponse response, ESLogger logger) throws IOException {
		logger.info("Report took in millseconds:" + response.getTookInMillis());
		DateHistogram timeline = response.getAggregations().get(
				"data_over_time");

		// 格式化结果后输出

		builder.startObject();
		builder.field("took", response.getTookInMillis());
		builder.field("total", timeline.getBuckets().size());

		builder.startArray("fields");
		builder.value("_bucket_timevalue");
		builder.value("_doc_count");
		builder.endArray();

		builder.startArray("rows");
		for (Bucket bucket : timeline.getBuckets()) {
			builder.startArray();
			builder.value(bucket.getKey());
			builder.value(bucket.getDocCount());
			builder.endArray();
		}
		builder.endArray().endObject();

	}
	
	public static void LeafTraverse(final ArrayList<Function> statsFields, final ArrayList<com.everdata.parser.AST_Stats.Bucket> bucketFields, Queue<List<String>> rows, 
			org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket node, Stack<String> path, int[] totalRows,
			final int from, final int size) throws CommandException{
		
		
		path.push(node.getKey());
		
		MultiBucketsAggregation bucketAgg = node.getAggregations().get("statsWithBy");
		if( bucketAgg != null ){
			Iterator<? extends org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket> iterator = bucketAgg.getBuckets().iterator();
			while (iterator.hasNext()) {
				LeafTraverse(statsFields, bucketFields, rows, iterator.next(), path, totalRows, from, size);
			}
		}else{
			if(size < 0 ||  totalRows[0] < (from + size) ){
				List<String> row = new ArrayList<String>();
				row.addAll(path);
				
				Map<String, Aggregation> map = node.getAggregations().getAsMap();
	
				for (Function f : statsFields) {
					row.add(getValueFromAggregation(map.get(f.statsField), f));	
				}
				
				row.add(String.valueOf(node.getDocCount()));
	
				rows.add(row);
			}
			totalRows[0] += 1;
		}
		
		path.pop();
	}

	public static void buildReport(int from, int size, XContentBuilder builder,
			ReportResponse response, ESLogger logger) throws IOException,
			CommandException {

		int[] total = {0};
		logger.info("Report took in millseconds:"
				+ response.response.getTookInMillis());

		List<String> fields = new ArrayList<String>();
		LinkedList<List<String>> rows = new LinkedList<List<String>>();
		List<List<String>> reportRows = null;
		Aggregations report = response.response.getAggregations();

		// 构建表头
		for(com.everdata.parser.AST_Stats.Bucket b : response.bucketFields){
			fields.add(b.bucketField);
		}
		
		java.util.Collections.reverse(fields);
		for(Function f: response.statsFields)
			fields.add(f.statsField);

		fields.add("_count");

		if (report.get("statsWithBy") != null) {
			
			MultiBucketsAggregation bucketAgg = report.get("statsWithBy");
			
			Iterator<? extends org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket> iterator = bucketAgg.getBuckets().iterator();
			while (iterator.hasNext()) {
				org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket next = iterator.next();
				LeafTraverse(response.statsFields, response.bucketFields, rows, next, new Stack<String>(), total, from, size);
			}

		} else {
			// 0级结构
			List<String> row = new ArrayList<String>();

			for (Function f : response.statsFields) {
				row.add(getValueFromAggregation(report.get(f.statsField), f));				
			}

			row.add(String.valueOf(response.response.getHits().getTotalHits()));

			rows.add(row);
			
			total[0] = 1;
		}

		reportRows = rows.subList(from, rows.size());
		
		builder.startObject();
		builder.field("took", response.response.getTookInMillis());
		builder.field("total", total[0]);
		builder.field("from", from);

		builder.field("_shard_failed", response.response.getFailedShards());
		builder.field("_shard_successful",
				response.response.getSuccessfulShards());
		builder.field("_shard_total", response.response.getTotalShards());

		builder.startArray("fields");
		for (String field : fields) {
			builder.value(field);
		}

		builder.endArray();

		builder.startArray("rows");
		for (List<String> row : reportRows) {
			builder.startArray();
			for (String value : row) {
				builder.value(value);
			}
			builder.endArray();
		}
		builder.endArray().endObject();

	}

	public static String getValueFromAggregation(Aggregation a, Function f){
		
		String value = null;
		switch(f.type){
		case Function.SUM :
			value = String.valueOf(((Sum) a).getValue());
			break;
		case Function.COUNT :
			value = String.valueOf(((ValueCount) a).getValue());
			break;
		case Function.DC :
			value = String.valueOf(((Cardinality) a).getValue());
			break;
		case Function.AVG :
			value = String.valueOf(((Avg) a).getValue());
			break;
		case Function.MAX :
			value = String.valueOf(((Max) a).getValue());
			break;
		case Function.MIN :
			value = String.valueOf(((Min) a).getValue());
			break;
		}
		
		return value;
		
		
	}
	
	public void executeQueryWithNonJoin(ActionListener<SearchResponse> listener,
			int from, final int size, String[] sortFields) {

		// jobHandler,执行期才知道要排序
		for (String field : sortFields) {
			if(field != null)addSortToQuery(field);
		}
		querySearch.setSearchType(SearchType.QUERY_THEN_FETCH).setFrom(from).setSize(size);
		dumpSearchScript(querySearch, logger);
		
		querySearch.execute(listener);
	}
	
	public void executeQuery(final ActionListener<QueryResponse> listener,
			int from, final int size, String[] sortFields) {

		// jobHandler,执行期才知道要排序
		for (String field : sortFields) {
			if(field != null)	addSortToQuery(field);
		}

		querySearch.setSearchType(SearchType.QUERY_THEN_FETCH).setFrom(from)
				.setSize(size);
		dumpSearchScript(querySearch, logger);

		SearchResponse response = querySearch.execute().actionGet();
		logger.info(String.format("query took %d", response.getTookInMillis()));
		long milli = System.currentTimeMillis();

		final QueryResponse queryResponse = new QueryResponse(size);

		queryResponse.totalHits = response.getHits().getTotalHits();
		queryResponse.failedShards = response.getFailedShards();
		queryResponse.successfulShards = response.getSuccessfulShards();
		queryResponse.totalShards = response.getTotalShards();
		Iterator<SearchHit> iterator = response.getHits().iterator();

		while (iterator.hasNext()) {
			SearchHit _hit = iterator.next();
			Map<String, Object> hit = _hit.sourceAsMap();
			hit.put("_id", _hit.id());
			hit.put("_index", _hit.index());
			hit.put("_type", _hit.type());

			queryResponse.searchHits.add(hit);
		}

		for (Join join : joinSearchs) {
			try {
				JoinQuery.executeJoin(join, size, queryResponse.searchHits,
						client, logger);
			} catch (CommandException e) {
				logger.error("executeJoin", e);
				listener.onFailure(e);
				return;
			}
		}

		queryResponse.took = response.getTookInMillis()
				+ (System.currentTimeMillis() - milli);
		listener.onResponse(queryResponse);
	}

	public void executeTimeline(final ActionListener<SearchResponse> listener,
			String interval, String timelineField) {
		timelineSearch.setSearchType(SearchType.COUNT);

		DateHistogramBuilder timeline = AggregationBuilders
				.dateHistogram("data_over_time");
		if (timelineField == null)
			timeline.field("_timestamp");
		else
			timeline.field(timelineField);

		try {
			long intervalAtMilli = Long.parseLong(interval);
			timeline.interval(intervalAtMilli);
		} catch (NumberFormatException e) {
			timeline.interval(new Interval(interval));
		}

		timelineSearch.addAggregation(timeline);

		dumpSearchScript(timelineSearch, logger);
		timelineSearch.execute(listener);

	}

	public void executeReport(final ActionListener<SearchResponse> listener,
			int from, int size) {
		reportSearch.setSearchType(SearchType.COUNT);
		dumpSearchScript(reportSearch, logger);
		reportSearch.execute(listener);
	}
	
	private long executeCardinary(String field) throws IOException{
		
		SearchResponse cardResponse = cardSearch.setAggregations(
				JsonXContent.contentBuilder()
				.startObject()
                	.startObject("LIMIT")
                		.startObject("cardinality")
                			.field("field", field)
                		.endObject()
                	.endObject()
				).get();

		Cardinality limit = cardResponse.getAggregations().get("LIMIT");
		
		return limit.getValue();		
	}

	static void dumpSearchScript(SearchRequestBuilder search, ESLogger logger) {

		try {
			XContentBuilder builder = XContentFactory
					.contentBuilder(XContentType.JSON);
			search.internalBuilder().toXContent(builder,
					ToXContent.EMPTY_PARAMS);
			logger.info(builder.bytes().toUtf8());

		} catch (IOException e) {
			logger.info(e.getMessage());
		}

	}

	public void executeDelete(ActionListener<DeleteByQueryResponse> listener) {
		deleteSearch.execute(listener);
	}

	public void executeDownload(final OutputStream httpStream, final XContent xcontent, boolean download2) {
		
		final SearchResponse head = querySearch.setSearchType(SearchType.SCAN)
				.setSize(200).setScroll(TimeValue.timeValueMinutes(10))
				.execute().actionGet();

		dumpSearchScript(querySearch, logger);
		
		if(download2){
			
			new Thread(new Runnable() {
				@Override
				public void run() {
	
					
					ClearScrollRequestBuilder clear = client.prepareClearScroll();
					try {	
						
						httpStream.write(String.format("{\"total\":%d}\n\n", head.getHits().getTotalHits()).getBytes());					
	
						// start scrolling, until we get not results
	
						String id = head.getScrollId();
						clear.addScrollId(id);
	
						while (true) {
	
							SearchResponse result = client.prepareSearchScroll(id)
									.setScroll(TimeValue.timeValueMinutes(10))
									.execute().actionGet();
	
							if (result.getHits().hits().length == 0) {
								break;
							}
							
	
							for (SearchHit hit : result.getHits()) {
								//hit.toXContent(builder, null);
								httpStream.write(hit.getSourceAsString().getBytes());
								httpStream.write('\n');
							}
							logger.info("executeDownload scrollId: " + id);
							clear.addScrollId(id);
							id = result.getScrollId();
	
						}
						
						httpStream.close();
						clear.get();
	
					} catch (Exception e) {
						logger.error("executeDownload error", e);
					}
				}
	
			}).start();
		}else{
			new Thread(new Runnable() {
				@Override
				public void run() {

					XContentBuilder builder;
					ClearScrollRequestBuilder clear = client.prepareClearScroll();
					try {
						builder = new XContentBuilder(
								xcontent,
								httpStream);

						builder.startObject();
						builder.field("total", head.getHits().getTotalHits());

						builder.startArray("hits");

						// start scrolling, until we get not results

						String id = head.getScrollId();
						clear.addScrollId(id);

						while (true) {

							SearchResponse result = client.prepareSearchScroll(id)
									.setScroll(TimeValue.timeValueMinutes(10))
									.execute().actionGet();

							if (result.getHits().hits().length == 0) {
								break;
							}
							

							for (SearchHit hit : result.getHits()) {
								hit.toXContent(builder, null);								
							}
							logger.info("executeDownload scrollId: " + id);
							clear.addScrollId(id);
							id = result.getScrollId();

						}
						
						
						builder.endArray();
						builder.endObject();
						builder.close();
						
						clear.get();

					} catch (Exception e) {
						logger.error("executeDownload error", e);
					}
				}

			}).start();
		}
		return;

	}



}