package com.example.repurchase.calculation;

import com.alibaba.fastjson.JSONObject;
import com.example.repurchase.utils.BaiDuMapUnit;
import com.example.repurchase.utils.DateUtils;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 根据用户的地理位置，调用百度接口计算经纬度
 */
@Component
public class CalculateLatLng {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 并行执行计算经纬度
     * @param threadNum 并行数
     * @param step 每次并行计算的用户数
     * @throws IOException
     */
    public void runGetLatLngSingleThread(Integer threadNum, Integer step) throws IOException {
        getLatLng(threadNum, step);
    }

    /**
     *
     * @param threadNum
     * @param step
     * @throws InterruptedException
     */
    @Deprecated
    public void runGetLatLngMultiThread(Integer threadNum, Integer step) throws InterruptedException {
        Integer threadNumber = threadNum;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(threadNumber);
        CountDownLatch countDownLatch = new CountDownLatch(threadNumber);
        ExecutorService executorService = Executors.newFixedThreadPool(threadNumber);
        for (int i = 0; i < threadNumber; i++) {
            Integer threadNumTemp = i;
            executorService.submit(new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("Thread:" + threadNumTemp + "准备完毕,time:" + System.currentTimeMillis() + "， 开始爬取数据");
                        cyclicBarrier.await();
                        getLatLng(threadNumTemp, step);
                        System.out.println("Thread:" + threadNumTemp + "爬取数据完毕,time:" + System.currentTimeMillis());
                    } catch (InterruptedException | BrokenBarrierException | IOException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            }));
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("执行结束");
    }

    private void getLatLng(Integer threadNum, Integer step) throws IOException {
        System.out.println("threadNum: " + threadNum + ", step: " + step);
        ArrayList<Map> dataNeedToLatLng = getDataNeedToLatLng(threadNum, step);
        System.out.println(dataNeedToLatLng);
        ArrayList<JSONObject> LatLng = new ArrayList<>();
        long t1 = System.currentTimeMillis();
        for (Map map : dataNeedToLatLng) {
            try {
                HashMap<String, Double> baiduResult = BaiDuMapUnit.getLatLng(map.get("ADeliveryAddress").toString(), threadNum);
                Thread.sleep((long) (Math.random() * 1000));
                if (baiduResult.keySet().size() == 0) {
                    return;
                }
                JSONObject jsonpResult = new JSONObject();
                jsonpResult.put("id", map.get("id").toString());
                jsonpResult.put("lat", baiduResult.get("lat"));
                jsonpResult.put("lng", baiduResult.get("lng"));
                LatLng.add(jsonpResult);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
        long t2 = System.currentTimeMillis();
        System.out.println("爬取了" + LatLng.size() + "条记录耗时：" + (t2 - t1));
        System.out.println(LatLng);
        if (LatLng.size() > 0) {
            BulkRequest bulkRequest = new BulkRequest();
            //写入完成立即刷新
            bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
            for (JSONObject jsonpResult : LatLng) {
                UpdateRequest updateRequest = new UpdateRequest("user_lat_lng", jsonpResult.getString("id"))
                        .doc(XContentFactory.jsonBuilder()
                                .startObject()
                                .field("Lat", jsonpResult.getDoubleValue("Lat"))
                                .field("Lng", jsonpResult.getDoubleValue("Lng"))
                                .field("LatLngTime", DateUtils.getCurrentTime())
                                .endObject());
                bulkRequest.add(updateRequest);
            }
            long t3 = System.currentTimeMillis();
            BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            long t4 = System.currentTimeMillis();
            System.out.println("写入es: " + LatLng.size() + "条数据，耗时： " + (t4 - t3));
            System.out.println(bulkResponse);
        }
    }

    // get data from elasticsearch that need to get Lat and Lng
    private ArrayList<Map> getDataNeedToLatLng(Integer threadNum, Integer step) throws IOException {

        QueryBuilder qb2 = QueryBuilders.boolQuery().must(QueryBuilders.rangeQuery("Lat").lt(1));

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(qb2);

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(qb2).from(threadNum * step).size(step).timeout(TimeValue.timeValueSeconds(30));

        SearchRequest searchRequest = new SearchRequest("user_lat_lng").source(searchSourceBuilder);
        SearchResponse myresponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hits = myresponse.getHits();
        ArrayList<Map> resultMaps = new ArrayList<>();

        for (int i = 0; i < hits.getHits().length; i++) {
            Map<String, Object> sourceAsMap = hits.getHits()[i].getSourceAsMap();
            resultMaps.add(sourceAsMap);
        }
        return resultMaps;
    }
}
