package com.sagacloud.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sagacloud.dto.OrderExeResp;
import com.sagacloud.dto.OrderSendResp;
import com.sagacloud.dto.OrderSetDTO;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;


/**
 * Created by wo on 9/13/17.
 */
public class UdpMsgUtil {
    private static final Logger log = LoggerFactory.getLogger(UdpMsgUtil.class);

    private static int TIME_OUT;
    private static int TIME_DELAY = 1;
    private static int TIME_DELAY_HALF = 1;
//    private static final String HOST = "101.201.234.108";
    // 下发批量指令，最大条数限制为30
    private static int batchSize = 30;
    private static  String HOST;
    private static int PORT;
    private static int seq = 0;
    private static final int baseSeq = 100000;
    private static DatagramChannel channel;
    private static Map<Integer, BlockingQueue<String>> seqToQueueMap = new ConcurrentHashMap<>();
    private static InetSocketAddress inetSocketAddress;

    public static void init() {
        HOST = OssUtils.getProperty("collect.service.host");
        PORT = Integer.valueOf(OssUtils.getProperty("collect.service.port").trim());
        TIME_OUT = Integer.valueOf(OssUtils.getProperty("collect.service.timeout").trim());
        String delayStr = OssUtils.getProperty("collect.service.delay.resp");
        if(null != delayStr) {
        	TIME_DELAY = Integer.valueOf(delayStr.trim());
        	if(TIME_DELAY > 1) {
        		TIME_DELAY_HALF = TIME_DELAY / 2;
        	}
        }
        log.info("collect.service.host: " + HOST);
        log.info("collect.service.port: " + PORT);
        log.info("collect.service.timeout: " + TIME_OUT);
        inetSocketAddress = new InetSocketAddress(HOST, PORT);
        try {
            channel = DatagramChannel.open();
//            channel.socket().bind(new InetSocketAddress(8668));
        } catch (IOException e) {
            log.error("DatagramChannel init failed. ", e);
            System.exit(2);
        }

    }

    public static synchronized int incrementAndGetSeq() {
        seq = (seq + 1) % baseSeq;
        return seq;
    }

    public static String setData(String buildingId, String meter, long funcId, Object value) {

        int seqNum = incrementAndGetSeq();

        BlockingQueue<String> queque = new LinkedBlockingQueue<>();
        seqToQueueMap.put(seqNum, queque);

        String sendStr = buildingId + ";1;pointset;;" + seqNum + ";" + meter + ";" + funcId + ";" + value;
        ByteBuffer buf = ByteBuffer.allocate(sendStr.length());
        buf.clear();
        buf.put(sendStr.getBytes());
        buf.flip();
        try {
            log.info(String.join(" ", "Send to", HOST, ":", sendStr) );
            channel.send(buf, inetSocketAddress);
        } catch (IOException e) {
            log.error("Failed to send " + sendStr, e);
        }

        try {
            String receivedStr = queque.poll(TIME_OUT, TimeUnit.SECONDS);
            if (receivedStr != null) {
                String[] arr = receivedStr.split(";");
                seqToQueueMap.remove(seqNum);
                return arr[arr.length - 1];
            }
        } catch (InterruptedException e) {
            log.error(e.getClass().toString() + " for handling " + sendStr);
        }
        seqToQueueMap.remove(seqNum);
        return "failed";
    }
    
    /**
     * 对一个点位下发控制指令
     * @param order
     * @return 指令下发及执行结果
     */
	public static Object setData(OrderSetDTO order) {
		// 下发指令的指令编号
    	int seqNum = incrementAndGetSeq();
    	// 存放指令反馈信息的队列
    	BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    	seqToQueueMap.put(seqNum, queue);
    	// 指令下发逻辑的执行情况
    	OrderSendResp result = new OrderSendResp();
        result.orderSeqNum = System.currentTimeMillis() + "-" + seqNum;
    	
        // 组织下发的指令信息
        String sendStr = getOrderSendStr(order, seqNum);
        // 给采集软件发送指令
        boolean sendOK = sendStrToCollecter(queue, result, seqNum, sendStr);

        if(sendOK) {
        	result.beOKSend();
        }else {
        	result.beErrSend();
        	return result;
        }
        
        String processInfo = analysisClollecterResp(queue, sendStr);
        if(null == processInfo || processInfo.endsWith("rcvd")) {
        	// 未得到反馈信息，或者反馈信息是‘指令收到但未执行完’时，再去查一下指令反馈信息
        	processInfo = analysisClollecterResp(queue, sendStr);
        }
        
        if(null != processInfo) {
        	// 已得到反馈信息
        	result.beOK();
        	result.exeResult = processInfo;
        }
        
        return result;
	}
	
	/**
     * 分析控制指令下发后的响应信息(最多延迟等待TIME_DELAY_HALF秒)
     * @param queue 存放指令执行的反馈信息的队列
     * @param sendStr 所下发的控制指令
     * @return null采集器未反馈信息，success指令执行成功；processing:rcvd采集器已收到指令但并未执行完成
     */
	private static String analysisClollecterResp(BlockingQueue<String> queue, String sendStr) {
		String processInfo = null;
		String receivedStr = null;
		
		try {
            receivedStr = queue.poll(TIME_DELAY_HALF, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error(e.getClass().toString() + " handle received data for " + sendStr, e);
        }
		
		if (receivedStr != null) {
            String[] arr = receivedStr.split(";");
            processInfo = arr[arr.length - 1];
        }
		
		return processInfo;
	}
	
	/**
     * 批量下发控制指令，不关注执行结果，只关注是否下发成功
     * @param orders
     * @return
     */
	public static boolean setDataBatch(List<OrderSetDTO> orders) {
		int seqNum = baseSeq + 1;
        
        int splitStart = 0, splitEnd = 0, orderNum = orders.size();
        String sendStr = null;
        
        boolean sendResult = false;
        try {
	        while(splitStart < orders.size()) {
	        	splitEnd = splitStart + batchSize;
	        	if(splitEnd > orderNum) {
	        		splitEnd = orderNum;
	        	}
	        	
	        	List<OrderSetDTO> subOrders = orders.subList(splitStart, splitEnd);
	        	
	        	sendStr = getOrderSendStr(subOrders, seqNum);
	            ByteBuffer buf = ByteBuffer.allocate(sendStr.length());
	            buf.clear();
	            buf.put(sendStr.getBytes());
	            buf.flip();
	            
                channel.send(buf, inetSocketAddress);
                seqNum++;
	        	
	        	splitStart = splitEnd;
	        }
	        
	        log.info("batchSend success");
	        sendResult = true;
        } catch (IOException e) {
        	log.error("Failed to batchSend.  sendStr=" + sendStr + " seqNum=" + seqNum, e);
        	e.printStackTrace();
        }
        
        return sendResult;
	}
	
	/**
	 * 查询指令执行的反馈信息
	 * @param timeStp_seqNum 下发指令编号，格式：时间戳-顺序号，示例：1616748061045-67
	 * @return
	 */
	public static Object checkExe(String timeStp_seqNum) {
		OrderExeResp result = new OrderExeResp();
    	result.orderSeqNum = timeStp_seqNum;
    	
    	Integer seqNum = null;
    	Long timeStp = null;
    	try {
    		timeStp = Long.valueOf(timeStp_seqNum.substring(0, timeStp_seqNum.indexOf("-")));
    		seqNum = Integer.valueOf(timeStp_seqNum.substring(1+timeStp_seqNum.indexOf("-")));
    	}catch (Exception e) {
		}
    	
    	if(null == seqNum || null == timeStp) {
    		// 请求码错误
    		result.beErrParam();
    		return result;
    	}else if(Math.abs(System.currentTimeMillis()-timeStp) > 60 * 1000){
    		// 指令编号过期
    		result.beTimeInvalid();
    		return result;
    	}
    	
    	if(seqToQueueMap.containsKey(seqNum)) {
    		// 指令已经下发
    		
    		BlockingQueue<String> queue = seqToQueueMap.get(seqNum);
    		
    		// 获取指令反馈信息
    		String receivedStr = queue.poll();
    		String receivedStrTemp = null;
    		
    		while(true) {
    			// 获取到队列中的最新的反馈数据
    			receivedStrTemp = queue.poll();
    			
    			if(null == receivedStrTemp) {
    				break;
    			}else {
    				receivedStr = receivedStrTemp;
    			}
    		}
    		
    		if (receivedStr != null) {
    			try {
    				// 已得到了指令执行的反馈
    				String[] arr = receivedStr.split(";");
    				seqToQueueMap.remove(seqNum);
    				result.beOK();
    				result.exeResult = arr[arr.length - 1];
    			}catch (Exception e) {
					log.error("received data analysis err! data=" + receivedStr);
				}
			}else {
    			// 指令已发出去，但未得到反馈信息
    			result.beNoResp();
    		}
    		
    	}else {
    		// 指令已过期
    		result.beErrInvalid();
    	}
    	
    	return result;
	}
	
	/**
     * 给采集软件发送指令
     * @param queue 存放指令反馈信息的队列
     * @param result 指令下发逻辑的执行情况
     * @param seqNum 下发指令的指令编号
     * @param sendStr 指令内容
     * @return true指令已发出去，false指令未发出去
     */
	private static boolean sendStrToCollecter(BlockingQueue<String> queue, OrderSendResp result,
			int seqNum, String sendStr) {
		
		boolean sendOK = false;
		ByteBuffer buf = ByteBuffer.allocate(sendStr.length());
        buf.clear();
        buf.put(sendStr.getBytes());
        buf.flip();
        
        try {
            log.info(String.join(" ", "new Send to", HOST, ":", PORT+"", sendStr) );
            
            int totalBytes = sendStr.getBytes().length;
            
            int sendBytes = channel.send(buf, inetSocketAddress);
            
            
            result.sendResult = "sendBytes:" + sendBytes + "/" + totalBytes;
            
            seqToQueueMap.put(seqNum, queue);
            
            sendOK = true;
        } catch (IOException e) {
            log.error("new sendStrToCollecter failed sendStr:" + sendStr, e);
        }
		return sendOK;
	}
	
	/**
     * 组织下发控制指令的字符串
     * @param order 控制指令DTO对象
     * @param seqNum 指令编号
     * @return 示例：1101070037;1;pointset;;123;1001;11;3.1
     */
	private static String getOrderSendStr(OrderSetDTO order, int seqNum) {
		return order.getProject() + ";1;pointset;;" + seqNum + ";" + order.getMeter() + ";" + order.getFuncId() + ";" + order.getValue();
	}
	
	/**
     * 组织批量下发控制指令的字符串
     * @param orders
     * @param seqNum
     * @return 示例：1101070037;1;pointset;;123;1001;11;3.1&1101070037;1;pointset;;123;1002;11;3.1
     */
    private static String getOrderSendStr(List<OrderSetDTO> orders, int seqNum) {
		StringBuffer sb = new StringBuffer(getOrderSendStr(orders.get(0), seqNum));
		
		for(int i=1; i<orders.size(); i++) {
			sb.append("&");
			sb.append(getOrderSendStr(orders.get(i), seqNum));
		}
		
		return sb.toString();
	}
    
    public static void receive() {

        while (true) {
            try {
                ByteBuffer readBuf = ByteBuffer.allocate(128);
                readBuf.clear();
                channel.receive(readBuf);
                String receivedStr = new String(readBuf.array()).trim();
                log.info("Receive : " + receivedStr );
                String[] strs = receivedStr.split(";");
                if (strs.length < 5) {
                    continue;
                }
                BlockingQueue<String> queue = seqToQueueMap.get(Integer.valueOf(receivedStr.split(";")[4]));
                if (queue != null) {
                    queue.add(receivedStr);
                }
            } catch (IOException e) {
                log.error("Receive mesage error. ", e);
            }
        }
    }
    public static void main(String[] args){
//        init();
//        setData();
    }

}
