package com.its.dsrc.xnettcp.dsrc;

import com.its.app.utils.NettyUtils;
import com.its.app.utils.OS;
import com.its.dsrc.config.RunningConfig;
import com.its.dsrc.global.AppRepository;
import com.its.dsrc.xnettcp.dsrc.codec.DsrcTcpServerEncoder;
import com.its.dsrc.xnettcp.dsrc.handler.DsrcAsn1ServerPacketInboundHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PreDestroy;

@Slf4j
@RequiredArgsConstructor
@Service
public class DsrcTcpCommServerService {

    private final RunningConfig runningConfig;
    private final AppRepository appRepository;
    private final DsrcTcpServerEncoder dsrcTcpServerEncoder;
    private final DsrcAsn1ServerPacketInboundHandler dsrcAsn1ServerPacketInboundHandler;

    private EventLoopGroup acceptGroups = null;
    private EventLoopGroup workerGroups = null;
    private ChannelFuture channelFuture = null;

    public void run() {
        if (!OS.isWindows()) {
            if (!Epoll.isAvailable()) {
                //Epoll.unavailabilityCause().printStackTrace();
                log.warn("{}", Epoll.unavailabilityCause().getMessage());
            }
        }
        if (NettyUtils.isEpollAvailable()) {
            log.info("서버가 리눅스 EPOLL 모드에서 실행됩니다.");
        }
        else {
            log.info("서버가 윈도우 NIO 모드에서 실행됩니다.");
        }

        ServerBootstrap serverBootstrap = null;
        try {
            serverBootstrap = createBootstrap();
            this.acceptGroups = serverBootstrap.config().group();
            this.workerGroups = serverBootstrap.config().childGroup();
        } catch (Exception e) {
            log.error("DsrcTcpCommServerService createBootstrap exception: {}", e.getMessage());
            log.error("DsrcTcpCommServerService cannot be started. It will be terminated. *************************************");
            return;
        }

        log.info("*********************************************************************************");
        log.info("**                    DSRC Communication Server Information                    **");
        log.info("**     bindAddress: {}", this.runningConfig.getCommBindingAddr());
        log.info("**      listenPort: {}", this.runningConfig.getCommBindingPort());
        log.info("**         backlog: {}", this.runningConfig.getCommBacklog());
        log.info("**   acceptThreads: {}", this.runningConfig.getCommAcceptThreads());
        log.info("**   workerThreads: {}", this.runningConfig.getCommWorkerThreads());
        log.info("*********************************************************************************");

        try {
            if (this.runningConfig.getCommBindingAddr().equals("0.0.0.0")) {
                this.channelFuture = serverBootstrap.bind(this.runningConfig.getCommBindingPort());
            }
            else {
                this.channelFuture = serverBootstrap.bind(this.runningConfig.getCommBindingAddr(), this.runningConfig.getCommBindingPort());
            }
        }
        catch (Exception e) {
            log.error("DsrcTcpCommServerService bind exception: {}", e.getMessage());
            if (this.acceptGroups != null) this.acceptGroups.shutdownGracefully();
            if (this.workerGroups != null) this.workerGroups.shutdownGracefully();
        }
    }

    public void stop() {
        try {
            if (this.acceptGroups != null) {
                this.acceptGroups.shutdownGracefully().sync();
            }
            if (this.workerGroups != null) {
                this.workerGroups.shutdownGracefully().sync();
            }
//            if (this.channelFuture != null && this.channelFuture.channel() != null) {
//                this.channelFuture.channel().closeFuture().sync();
//            }
        }
        catch (InterruptedException e) {
            log.error("DsrcTcpCommServerService stop interrupt: {}", e.getMessage());
            Thread.currentThread().interrupt();
        }
    }

    @PreDestroy
    public void destroyService() {
        log.error("DsrcTcpCommServerService.destroy. system terminated.......");
        stop();
    }

    public ServerBootstrap createBootstrap() {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        EventLoopGroup acceptGroups;
        EventLoopGroup workerGroups;

        acceptGroups = NettyUtils.newEventLoopGroup(this.runningConfig.getCommAcceptThreads(), "Accept");
        workerGroups = NettyUtils.newEventLoopGroup(this.runningConfig.getCommWorkerThreads(), "Worker");
        serverBootstrap.channel(NettyUtils.getServerSocketChannel());
        serverBootstrap.group(acceptGroups, workerGroups);

        serverBootstrap.option(ChannelOption.AUTO_READ, true);
        serverBootstrap.option(ChannelOption.SO_BACKLOG, this.runningConfig.getCommBacklog());
        serverBootstrap.option(ChannelOption.SO_RCVBUF, this.runningConfig.getCommRcvBuf());
        serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
        serverBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.runningConfig.getCommConnectTimeoutSeconds()*1000);

        serverBootstrap.childOption(ChannelOption.SO_LINGER, 0);           // 4way-handshake 비활성
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, false);    // KEEPALIVE 비활성(활성: true)
        serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);     // 소켓 재사용
        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);      // Nagle 알고리즘 비활성화

        DsrcTcpCommServerInitializer dsrcTcpCommServerInitializer = new DsrcTcpCommServerInitializer(
                this.runningConfig,
                this.appRepository,
                this.dsrcAsn1ServerPacketInboundHandler,
                this.dsrcTcpServerEncoder
        );
        serverBootstrap.childHandler(dsrcTcpCommServerInitializer);

        return serverBootstrap;
    }

}
