package com.pqs9200.utils;

import com.njcn.pojo.advanced.MessageObj;
import com.njcn.utils.WebSocketUtil;
import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import kafka.message.MessageAndMetadata;
import kafka.serializer.StringDecoder;
import kafka.utils.VerifiableProperties;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.EncodeException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class JavaKafkaConsumer implements Runnable {
    /**
     * Kafka数据消费对象
     */
    private ConsumerConnector consumer;
    /*** Kafka Topic名称 */
    private String topic;
    /*** 线程数量，一般就是Topic的分区数量 */
    private int numThreads;
    /*** 线程池 */
    private ExecutorService executorPool;
    /*** Kafka是否打印日志 */
    private static String kafka_log;
    /*** 日志记录 */
    private static final Logger logger = LoggerFactory.getLogger(JavaKafkaConsumer.class);

    /**
     * 构造函数
     *
     * @param topic     Kafka消息Topic主题
     * @param zookeeper Kafka的Zookeeper连接字符串
     * @param groupId   该消费者所属group ID的值
     */
    @SuppressWarnings("static-access")
    public JavaKafkaConsumer(String topic, int numThreads, String zookeeper, String groupId, String kafka_log) {
        // 1. 创建Kafka连接器
        this.consumer = Consumer.createJavaConsumerConnector(createConsumerConfig(zookeeper, groupId));
        // 2. 数据赋值
        this.topic = topic;
        this.numThreads = numThreads;
        this.kafka_log = kafka_log;
    }

    @Override
    public void run() {
        // 1. 指定Topic
        Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
        topicCountMap.put(this.topic, this.numThreads);

        // 2. 指定数据的解码器
        StringDecoder keyDecoder = new StringDecoder(new VerifiableProperties());
        StringDecoder valueDecoder = new StringDecoder(new VerifiableProperties());

        // 3. 获取连接数据的迭代器对象集合
        /**
         * Key: Topic主题 Value: 对应Topic的数据流读取器，大小是topicCountMap中指定的topic大小
         */
        Map<String, List<KafkaStream<String, String>>> consumerMap = this.consumer.createMessageStreams(topicCountMap,
                keyDecoder, valueDecoder);

        // 4. 从返回结果中获取对应topic的数据流处理器
        List<KafkaStream<String, String>> streams = consumerMap.get(this.topic);

        // 5. 创建线程池
        this.executorPool = Executors.newFixedThreadPool(this.numThreads);

        // 6. 构建数据输出对象
        int threadNumber = 0;
        for (final KafkaStream<String, String> stream : streams) {
            this.executorPool.submit(new ConsumerKafkaStreamProcesser(stream, threadNumber));
            threadNumber++;
        }
    }

    public void shutdown() {
        // 1. 关闭和Kafka的连接，这样会导致stream.hashNext返回false
        if (this.consumer != null) {
            this.consumer.shutdown();
        }

        // 2. 关闭线程池，会等待线程的执行完成
        if (this.executorPool != null) {
            // 2.1 关闭线程池
            this.executorPool.shutdown();

            // 2.2. 等待关闭完成, 等待五秒
            try {
                if (!this.executorPool.awaitTermination(5, TimeUnit.SECONDS)) {
                    logger.info("Timed out waiting for consumer threads to shut down, exiting uncleanly!!");
                }
            } catch (InterruptedException e) {
                logger.error("Interrupted during shutdown, exiting uncleanly!!");
            }
        }

    }

    /**
     * 根据传入的zk的连接信息和groupID的值创建对应的ConsumerConfig对象
     *
     * @param zookeeper zk的连接信息，类似于：<br/>
     *                  hadoop-senior01.ibeifeng.com:2181,hadoop-senior02.ibeifeng.com:2181/kafka
     *                  consumer所属的group id的值， group id值一样的kafka consumer会进行负载均衡
     * @return Kafka连接信息
     */
    private ConsumerConfig createConsumerConfig(String zookeeper, String groupId) {
        // 1. 构建属性对象
        Properties prop = new Properties();
        // 2. 添加相关属性
        prop.put("group.id", groupId); // 指定分组id
        prop.put("zookeeper.connect", zookeeper); // 指定zk的连接url
        prop.put("zookeeper.session.timeout.ms", "400"); //
        prop.put("zookeeper.sync.time.ms", "200");
        prop.put("auto.commit.interval.ms", "1000");
        // 3. 构建ConsumerConfig对象
        return new ConsumerConfig(prop);
    }

    /**
     * Kafka消费者数据处理线程
     */
    public static class ConsumerKafkaStreamProcesser implements Runnable {
        // Kafka数据流
        private KafkaStream<String, String> stream;
        // 线程ID编号
        private int threadNumber;
        // 建立socket对象，推送数据
        private WebSocketUtil webSocketUtil = new WebSocketUtil();

        public ConsumerKafkaStreamProcesser(KafkaStream<String, String> stream, int threadNumber) {
            this.stream = stream;
            this.threadNumber = threadNumber;
        }

        @Override
        public void run() {
            // 1. 获取数据迭代器
            ConsumerIterator<String, String> iter = this.stream.iterator();
            // 2. 迭代输出数据
            while (iter.hasNext()) {
                // 2.1 获取数据值
                MessageAndMetadata value = iter.next();

                // 新增去掉传输消息的前后字符串
                String recevData = new String(((String) value.message()).trim());

                /**************************************************************
                 * 加入try...catch处理
                 * 如果非json格式字符串，string转jsonObject对象会异常返回
                 **************************************************************/

                try {
                    translateData(recevData);
                } catch (Exception e) {
                    logger.error("Kafka传输的字符串格式不正确：" + recevData);
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    logger.error(e.getMessage());
                }
                /******* 系统配置是否打印Kafka *******/
                if (kafka_log.equals("true"))
                    logger.error(recevData);
            }
        }

        // 解析kfka数据
        @SuppressWarnings("unchecked")
        public void translateData(String strData) {
            JSONArray jsonArray = JSONArray.fromObject(strData);
            String msgId = jsonArray.getJSONObject(0).getString("msg");

            /**********************************************************
             * add by sw
             * kakfa消息推送中，暂降幅值结果*100
             **********************************************************/
            List<JSONObject> list = (List<JSONObject>) jsonArray.getJSONObject(0).get("data");

            if (msgId.equals("event_data")) {
                for (JSONObject jsonObject : list) {
                    jsonObject.replace("eventvalue", jsonObject.getDouble("eventvalue") * 100);
                }
            }


            postMsg(msgId, list);
        }

        // 消息推送
        public void postMsg(String strMsgId, Object strBody) {
            switch (strMsgId) {
                case "event_data":
                    MessageObj messageObj = new MessageObj();
                    messageObj.setMsgId(messageObj.getRelevance() + 1);
                    messageObj.setMsgBody("insert");
                    messageObj.setMsgbody(strBody);
                    try {
                        webSocketUtil.sendAll(messageObj);
                    } catch (IOException | EncodeException e1) {
                        logger.error(e1.getMessage());
                    }
                    break;
                case "com_data":
                    MessageObj messObj = new MessageObj();
                    messObj.setMsgId(messObj.getRelevance() + 3);
                    messObj.setMsgBody("update");
                    messObj.setMsgbody(strBody);
                    try {
                        webSocketUtil.sendAll(messObj);
                    } catch (IOException | EncodeException e) {
                        logger.error(e.getMessage());
                    }
                    break;
                default:
                    ;
            }
        }
    }
}
