package com.yonge.netty.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

import java.net.InetSocketAddress;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import com.yonge.netty.server.handler.NettyServerHandler;
import com.yonge.netty.server.handler.message.BinaryWebSocketFrameHandler;
import com.yonge.netty.server.handler.message.TextWebSocketHandler;

@Configuration
public class NettyServer {

	private static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class);

	/**
	 * webSocket协议名
	 */
	private static final String WEBSOCKET_PROTOCOL = "WebSocket";

	/**
	 * 端口号
	 */
	@Value("${netty.server.port}")
	private int port;

	/**
	 * webSocket路径
	 */
	private String webSocketPath = "/audioAnalysis";

	private EventLoopGroup bossGroup = new NioEventLoopGroup(1);

	private EventLoopGroup workGroup = new NioEventLoopGroup(5);

	@Autowired
	private NettyServerHandler nettyServerHandler;

	@Autowired
	private BinaryWebSocketFrameHandler binaryWebSocketFrameHandler;

	@Autowired
	private TextWebSocketHandler textWebSocketHandler;

	/**
	 * 启动
	 * @throws InterruptedException
	 */
	private void start() throws InterruptedException {
		ServerBootstrap bootstrap = new ServerBootstrap();

		// bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
		bootstrap.group(bossGroup, workGroup);
		// 设置NIO类型的channel
		bootstrap.channel(NioServerSocketChannel.class);
		// 设置监听端口
		bootstrap.localAddress(new InetSocketAddress(port));
		// 服务端 accept 队列的大小
		//bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
		//bootstrap.option(ChannelOption.SO_RCVBUF, 1024*4);
		// 允许较小的数据包的发送，降低延迟
		bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
		// 连接到达时会创建一个通道
		bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

			@Override
			protected void initChannel(SocketChannel ch) throws Exception {
				// 获得 Channel 对应的 ChannelPipeline
				ChannelPipeline channelPipeline = ch.pipeline();

				// 流水线管理通道中的处理程序（Handler），用来处理业务
				// webSocket协议本身是基于http协议的，所以这边也要使用http编解码器
				channelPipeline.addLast(new HttpServerCodec());
				// channelPipeline.addLast(new ObjectEncoder());
				// 分块向客户端写数据，防止发送大文件时导致内存溢出， channel.write(new ChunkedFile(new File("bigFile.mkv")))
				channelPipeline.addLast(new ChunkedWriteHandler());
				/*
				 * 说明： 1、http数据在传输过程中是分段的，HttpObjectAggregator可以将多个段聚合 2、这就是为什么，当浏览器发送大量数据时，就会发送多次http请求
				 */
				channelPipeline.addLast(new HttpObjectAggregator(1024 * 8));
				// webSocket 数据压缩扩展，当添加这个的时候WebSocketServerProtocolHandler的第三个参数需要设置成true
				channelPipeline.addLast(new WebSocketServerCompressionHandler());
				/*
				 * 说明： 1、对应webSocket，它的数据是以帧（frame）的形式传递 2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri 3、核心功能是将http协议升级为ws协议，保持长连接
				 */
				channelPipeline.addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 1000, false, true));

				// 自定义的handler，处理业务逻辑
				channelPipeline.addLast(nettyServerHandler);
				channelPipeline.addLast(binaryWebSocketFrameHandler);
				channelPipeline.addLast(textWebSocketHandler);

			}
		});

		// 配置完成，开始绑定server，通过调用sync同步方法阻塞直到绑定成功
		ChannelFuture channelFuture = bootstrap.bind().sync();

		if (channelFuture.isSuccess()) {
			LOGGER.info("Server started and listen on:{}", channelFuture.channel().localAddress());
		}

		// 对关闭通道进行监听
		channelFuture.channel().closeFuture().sync();
	}

	/**
	 * 释放资源
	 * @throws InterruptedException
	 */
	@PreDestroy
	public void destroy() throws InterruptedException {
		if (bossGroup != null) {
			bossGroup.shutdownGracefully().sync();
		}
		if (workGroup != null) {
			workGroup.shutdownGracefully().sync();
		}
	}

	@PostConstruct()
	public void init() {
		// 需要开启一个新的线程来执行netty server 服务器
		new Thread(() -> {
			try {
				start();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();
	}

}
