package org.framework.lazy.cloud.network.heartbeat.client.netty.udp.socket;


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.framework.lazy.cloud.network.heartbeat.client.netty.NettyClientSocket;
import org.framework.lazy.cloud.network.heartbeat.client.netty.event.ClientChangeEvent;
import org.framework.lazy.cloud.network.heartbeat.client.netty.udp.filter.NettyUdpClientFilter;
import org.framework.lazy.cloud.network.heartbeat.common.constant.UdpMessageType;
import org.framework.lazy.cloud.network.heartbeat.common.NettyProxyMsg;
import org.framework.lazy.cloud.network.heartbeat.common.NettyServerContext;
import org.framework.lazy.cloud.network.heartbeat.common.adapter.ChannelTypeAdapter;
import org.framework.lazy.cloud.network.heartbeat.common.advanced.HandleChannelTypeAdvanced;
import org.framework.lazy.cloud.network.heartbeat.common.utils.ChannelAttributeKeyUtils;

import java.net.InetAddress;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 客户端连接服务端
 */
@Slf4j
public class NettyUdpClientSocket implements NettyClientSocket {
    private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    /**
     * 服务端host
     */
    private final String inetHost;
    /**
     * 服务端端口
     */
    private final int inetPort;
    /**
     * 当前客户端id
     */
    @Getter
    private final String clientId;

    /**
     * 当前连接的服务端ID
     */
    private final String serverId;
    private final String appKey;
    private final String appSecret;
    /**
     * 客户端状态变更事件
     */
    @Getter
    private final ClientChangeEvent clientChangeEvent;
    private final List<HandleChannelTypeAdvanced> handleChannelTypeAdvancedList; // 处理服务端发送过来的数据类型

    public NettyUdpClientSocket(String inetHost,
                                int inetPort,
                                String clientId,
                                String serverId,
                                String appKey,
                                String appSecret,
                                ClientChangeEvent clientChangeEvent,
                                List<HandleChannelTypeAdvanced> handleChannelTypeAdvancedList) {
        this.inetHost = inetHost;
        this.inetPort = inetPort;
        this.clientId = clientId;
        this.serverId = serverId;
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.clientChangeEvent = clientChangeEvent;
        this.handleChannelTypeAdvancedList = handleChannelTypeAdvancedList;
    }

    public void newConnect2Server() throws InterruptedException {
        newConnect2Server(inetHost, inetPort, clientId, serverId, clientChangeEvent);
    }

    protected void newConnect2Server(String inetHost, int inetPort, String clientId, String serverId, ClientChangeEvent clientChangeEvent) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
                .channel(NioDatagramChannel.class)
                .option(ChannelOption.SO_RCVBUF, 2048 * 1024)
                // 设置写缓冲区为1M
                .option(ChannelOption.SO_SNDBUF, 1024 * 1024)

                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.SO_BROADCAST, true)
//                    .childOption(ChannelOption.UDP_NODELAY, false)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000 * 60)//连接超时时间设置为 60 秒
//                    .childOption(ChannelOption.RCVBUF_ALLOCATOR, new NettyRecvByteBufAllocator(1024 * 1024))//用于Channel分配接受Buffer的分配器 默认AdaptiveRecvByteBufAllocator.DEFAULT
                .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(1024 * 1024, 1024 * 1024 * 2))
                .handler(new NettyUdpClientFilter(new ChannelTypeAdapter(handleChannelTypeAdvancedList), this))
        ;
        log.info("use clientId:{} connect to server IP:{},server port :{}", clientId, inetHost, inetPort);
        ChannelFuture future = bootstrap.connect(inetHost, inetPort);
        // 客户端连接服务端的channel
        Channel serviceChannel = future.channel();

        future.addListener((ChannelFutureListener) futureListener -> {
            if (futureListener.isSuccess()) {

                log.info("clientId:{},connect to server IP:{},server port :{}  udp isSuccess ", clientId, inetHost, inetPort);
                // 告诉服务端这条连接是client的连接
                NettyProxyMsg nettyMsg = new NettyProxyMsg();
                nettyMsg.setType(UdpMessageType.UDP_REPORT_CLIENT_CONNECT_SUCCESS);
                nettyMsg.setClientId(clientId);
                String hostAddress = InetAddress.getLocalHost().getHostAddress();
                nettyMsg.setOriginalIpString(hostAddress);
                nettyMsg.setData((clientId).getBytes());
                nettyMsg.setAppKeyString(appKey);
                nettyMsg.setAppSecretString(appSecret);
                ChannelAttributeKeyUtils.buildClientId(serviceChannel, clientId);
                serviceChannel.writeAndFlush(nettyMsg);

                NettyServerContext.pushServerEndpointChannel(serverId, clientId, serviceChannel);
                // 在线 客户端注册服务端成功
                clientChangeEvent.clientOnLine(inetHost, inetPort,serverId, clientId);
            } else {
                log.warn("Reconnect every 2 seconds....");
                // 离线
                NettyServerContext.removeServerEndpointChannels(serverId, clientId);
                clientChangeEvent.clientOffLine(inetHost, inetPort,serverId, clientId);
                eventLoopGroup.schedule(() -> {
                    try {
                        newConnect2Server(inetHost, inetPort, clientId, serverId, clientChangeEvent);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }, 2, TimeUnit.SECONDS);
            }
        });
    }

    /**
     * 关闭连接
     */

    public void shutdown() {
        if ((eventLoopGroup != null) && (!eventLoopGroup.isShutdown())) {
            eventLoopGroup.shutdownGracefully();
        }
    }

}