package io.gitlab.leibnizhu.sbnetty.core;

import com.google.common.base.StandardSystemProperty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
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.HttpServerCodec;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;

import java.net.InetSocketAddress;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Netty servle容器
 * 处理请求,返回响应
 * 目前不支持JSP,考虑到SpringBoot多用于REST+前后端分离,也不会去实现JSP
 *
 * @author Leibniz
 */
public class NettyContainer implements EmbeddedServletContainer {
    private final Log log = LogFactory.getLog(getClass());

    private final InetSocketAddress address; //监听端口地址
    private final NettyContext servletContext; //Context

    //Netty所需的线程池,分别用于接收/监听请求以及处理请求读写
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private DefaultEventExecutorGroup servletExecutor;

    public NettyContainer(InetSocketAddress address, NettyContext servletContext) {
        this.address = address;
        this.servletContext = servletContext;
    }

    @Override
    public void start() throws EmbeddedServletContainerException {
        servletContext.setInitialised(false);

        ServerBootstrap sb = new ServerBootstrap();
        //根据不同系统初始化对应的EventLoopGroup
        if ("Linux".equals(StandardSystemProperty.OS_NAME.value())) {
            bossGroup = new EpollEventLoopGroup(1);
            workerGroup = new EpollEventLoopGroup();//不带参数,线程数传入0,实际解析为 Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
            sb.channel(EpollServerSocketChannel.class)
                    .group(bossGroup, workerGroup)
                    .option(EpollChannelOption.TCP_CORK, true);
        } else {
            bossGroup = new NioEventLoopGroup(1);
            workerGroup = new NioEventLoopGroup();
            sb.channel(NioServerSocketChannel.class)
                    .group(bossGroup, workerGroup);
        }
        sb.option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_REUSEADDR, true)
                .option(ChannelOption.SO_BACKLOG, 100);
        log.info("Bootstrap configuration: " + sb.toString());

        servletExecutor = new DefaultEventExecutorGroup(50);
        sb.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                p.addLast("codec", new HttpServerCodec(4096, 8192, 8192, false)); //HTTP编码解码Handler
                p.addLast("servletInput", new ServletContentHandler(servletContext)); //处理请求,读入数据,生成Request和Response对象
                p.addLast(checkNotNull(servletExecutor), "filterChain", new RequestDispatcherHandler(servletContext)); //获取请求分发器,让对应的Servlet处理请求,同时处理404情况
            }
        });

        servletContext.setInitialised(true);

        ChannelFuture future = sb.bind(address).awaitUninterruptibly();
        Throwable cause = future.cause();
        if (null != cause) {
            throw new EmbeddedServletContainerException("Could not start Netty server", cause);
        }
        log.info(servletContext.getServerInfo() + " started on port: " + getPort());
    }

    /**
     * 优雅地关闭各种资源
     * @throws EmbeddedServletContainerException
     */
    @Override
    public void stop() throws EmbeddedServletContainerException {
        log.info("Embedded Netty Servlet Container(by Leibniz.Hu) is now shuting down.");
        try {
            if (null != bossGroup) {
                bossGroup.shutdownGracefully().await();
            }
            if (null != workerGroup) {
                workerGroup.shutdownGracefully().await();
            }
            if (null != servletExecutor) {
                servletExecutor.shutdownGracefully().await();
            }
        } catch (InterruptedException e) {
            throw new EmbeddedServletContainerException("Container stop interrupted", e);
        }
    }

    @Override
    public int getPort() {
        return address.getPort();
    }
}