package org.voovan.network;

import org.voovan.tools.ByteBufferChannel;
import org.voovan.tools.TByteBuffer;
import org.voovan.tools.TEnv;
import org.voovan.tools.log.Logger;

import java.nio.ByteBuffer;
import java.time.temporal.IsoFields;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * 类文字命名
 *
 * @author: helyho
 * Voovan Framework.
 * WebSite: https://github.com/helyho/Voovan
 * Licence: Apache v2 License
 */
public class HeartBeat {
    private ByteBuffer ping;
    private ByteBuffer pong;
    private boolean isFirstBeat = true;
    private LinkedBlockingDeque<Integer> queue;
    private int fieldCount = 0;


    /**
     * 构造方法
     * @param session   会话
     * @param connectModel  模式,指定发送第一个 PING 的模式,服务端先发,或者客户端先发
     * @param ping  ping 消息
     * @param pong  pong 消息
     * @return 心跳消息对象
     */
    private HeartBeat(IoSession session, ConnectModel connectModel, String ping, String pong){
        this.ping = ByteBuffer.wrap(ping.getBytes());
        this.pong = ByteBuffer.wrap(pong.getBytes());
        queue = new LinkedBlockingDeque<Integer>();

        if(session.socketContext().getConnectModel() == connectModel){
            session.send(ByteBuffer.wrap(ping.getBytes()));
        }
    }

    public LinkedBlockingDeque<Integer> getQueue() {
        return queue;
    }

    public ByteBuffer getPing() {
        return ping;
    }

    public ByteBuffer getPong() {
        return pong;
    }

    public int getFieldCount() {
        return fieldCount;
    }

//    /**
//     * 判断会话中是否这个作为消息开始
//     * @param msg 消息
//     * @return true: 是, false: 否
//     */
//    private boolean isHeadOfMsg(IoSession session, ByteBuffer msg){
//        ByteBufferChannel byteBufferChannel = session.getByteBufferChannel();
//
//        if( byteBufferChannel.size() >= msg.capacity()){
//            if(byteBufferChannel.indexOf(msg.array()) == 0) {
//                //收缩通道内的数据
//                byteBufferChannel.shrink(msg.capacity());
//                try {
//                    return true;
//                }finally {
//                    if(byteBufferChannel.size()!=0) {
//                        while (isHeadOfMsg(session, msg)) {
//                            continue;
//                        }
//                    }
//                }
//            }
//        }
//
//        return false;
//    }
//
//    /**
//     * 检测是否是 Pong 消息
//     * @return true: 是, false: 否
//     */
//    public boolean isPing(IoSession session){
//        return isHeadOfMsg(session, ping);
//    }
//
//    /**
//     * 检测是否是 Pong 消息
//     * @return true: 是, false: 否
//     */
//    public boolean isPong(IoSession session){
//        return isHeadOfMsg(session, pong);
//    }
//
//    /**
//     * 一次心跳动作
//     */
//    public static boolean beat_old(IoSession session){
//
//        HeartBeat heartBeat = session.getHeartBeat();
//
//        if(heartBeat.isFirstBeat){
//            heartBeat.isFirstBeat=false;
//            return true;
//        }
//        //收到 PING 发送 PONG
//        if(heartBeat.isPing(session)){
//            session.getMessageLoader().reset();
//            session.send(heartBeat.pong);
//            return true;
//        } else if(heartBeat.isPong(session)){
//            session.getMessageLoader().reset();
//            session.send(heartBeat.ping);
//            return true;
//        } else {
//            return false;
//        }
//    }

    public static void interceptHeartBeat(IoSession session, ByteBuffer byteBuffer){
        HeartBeat heartBeat = session.getHeartBeat();
        String mms = TByteBuffer.toString(byteBuffer);
        if(heartBeat!=null) {
            if (byteBuffer.hasRemaining()) {
                //心跳处理
                if (heartBeat != null) {
                    if (TByteBuffer.indexOf(byteBuffer, heartBeat.getPing().array()) == 0) {
                        if(byteBuffer.remaining() != heartBeat.getPing().limit()) {
                            TByteBuffer.moveData(byteBuffer, heartBeat.getPing().limit());
                        }
                        heartBeat.getQueue().addLast(1);
                    }
                    if (TByteBuffer.indexOf(byteBuffer, heartBeat.getPong().array()) == 0) {
                        if(byteBuffer.remaining() != heartBeat.getPing().limit()) {
                            TByteBuffer.moveData(byteBuffer, heartBeat.getPong().limit());
                        }
                        heartBeat.getQueue().addLast(2);
                    }
                }
            }
        }
    }

    public static boolean beat(IoSession session){

        HeartBeat heartBeat = session.getHeartBeat();

        //收个心跳返回成功
        if(heartBeat.isFirstBeat){
            heartBeat.isFirstBeat=false;
            return true;
        }

        if(heartBeat.getQueue().size() > 0) {
            int beatType = heartBeat.getQueue().pollFirst();

            if (beatType == 1) {
                session.getMessageLoader().reset();
                session.send(heartBeat.pong);
                heartBeat.fieldCount = 0;
                return true;
            } else if (beatType == 2) {
                session.getMessageLoader().reset();
                session.send(heartBeat.ping);
                heartBeat.fieldCount = 0;
                return true;
            } else {
                heartBeat.fieldCount++;
                return false;
            }
        }

        heartBeat.fieldCount++;
        return false;
    }
    /**
     * 将心跳绑定到 Session
     * @param session   会话
     * @param connectModel  模式,指定发送第一个 PING 的模式,服务端先发,或者客户端先发
     * @param ping  ping 消息
     * @param pong  pong 消息
     * @return 心跳消息对象
     */
    public static HeartBeat attachSession(IoSession session, ConnectModel connectModel, String ping, String pong){
        HeartBeat heartBeat = null;
        if(session.getHeartBeat()==null) {
            heartBeat = new HeartBeat(session, connectModel, ping, pong);
            session.setHeartBeat(heartBeat);
        } else{
            heartBeat = session.getHeartBeat();
        }
        return heartBeat;

    }

    /**
     * 将心跳绑定到 Session
     *      默认使用 PING, PONG 作为心跳消息
     * @param session   会话
     * @param connectModel  模式,指定发送第一个 PING 的模式,服务端先发,或者客户端先发
     * @return 心跳消息对象
     */
    public static HeartBeat attachSession(IoSession session, ConnectModel connectModel){
        HeartBeat heartBeat = null;
        if(session.getHeartBeat()==null) {
            heartBeat = new HeartBeat(session, connectModel, "PING", "PONG");
            session.setHeartBeat(heartBeat);
        }else{
            heartBeat = session.getHeartBeat();
        }

        return heartBeat;
    }
}

@@ -3550,106 +3550,129 @@
     
-HeartBeat heartBeat = session.getHeartBeat();%0A        String mms = TByteBuffer.toString(byteBuffer
+if(session==null %7C%7C byteBuffer==null)%7B%0A            return;%0A        %7D%0A%0A        HeartBeat heartBeat = session.getHeartBeat(
 );%0A 

修复 NIO 下不停触发 IDLE 事件的问题