package com.lingniu.ingest.protocol.jt808.inbound;

import com.lingniu.ingest.core.dispatcher.Dispatcher;
import com.lingniu.ingest.identity.VehicleIdentityRegistry;
import com.lingniu.ingest.identity.VehicleIdentityResolver;
import com.lingniu.ingest.protocol.jt808.codec.Jt808FrameDecoder;
import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder;
import com.lingniu.ingest.protocol.jt808.session.Jt808ChannelRegistry;
import com.lingniu.ingest.protocol.jt808.session.Jt808PendingRequests;
import com.lingniu.ingest.session.SessionStore;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import java.util.concurrent.ThreadFactory;

public class Jt808NettyServer implements InitializingBean, DisposableBean {

    private static final Logger log = LoggerFactory.getLogger(Jt808NettyServer.class);

    private final int port;
    private final int workerThreads;
    private final Jt808MessageDecoder messageDecoder;
    private final Dispatcher dispatcher;
    private final SessionStore sessions;
    private final VehicleIdentityRegistry identityRegistry;
    private final VehicleIdentityResolver identityResolver;
    private final Jt808ChannelRegistry channelRegistry;
    private final Jt808PendingRequests pendingRequests;

    private EventLoopGroup boss;
    private EventLoopGroup workers;
    private Channel serverChannel;

    public Jt808NettyServer(int port, int workerThreads,
                            Jt808MessageDecoder messageDecoder, Dispatcher dispatcher,
                            SessionStore sessions,
                            VehicleIdentityRegistry identityRegistry,
                            VehicleIdentityResolver identityResolver,
                            Jt808ChannelRegistry channelRegistry,
                            Jt808PendingRequests pendingRequests) {
        this.port = port;
        this.workerThreads = workerThreads;
        this.messageDecoder = messageDecoder;
        this.dispatcher = dispatcher;
        this.sessions = sessions;
        this.identityRegistry = identityRegistry;
        this.identityResolver = identityResolver;
        this.channelRegistry = channelRegistry;
        this.pendingRequests = pendingRequests;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        ThreadFactory bossTf = r -> new Thread(r, "jt808-boss");
        ThreadFactory wkTf = r -> new Thread(r, "jt808-worker");
        this.boss = new NioEventLoopGroup(1, bossTf);
        this.workers = new NioEventLoopGroup(
                workerThreads == 0 ? Runtime.getRuntime().availableProcessors() * 2 : workerThreads,
                wkTf);

        ServerBootstrap b = new ServerBootstrap();
        b.group(boss, workers)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline()
                                .addLast("frame", new Jt808FrameDecoder())
                                .addLast("dispatch", new Jt808ChannelHandler(
                                        messageDecoder, dispatcher, sessions,
                                        identityRegistry, identityResolver,
                                        channelRegistry, pendingRequests));
                    }
                });
        this.serverChannel = b.bind(port).sync().channel();
        log.info("[jt808] Netty server listening on :{}", port);
    }

    @Override
    public void destroy() {
        try {
            if (serverChannel != null) serverChannel.close().sync();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (boss != null) boss.shutdownGracefully();
            if (workers != null) workers.shutdownGracefully();
            log.info("[jt808] Netty server stopped");
        }
    }
}
