package com.mogo.telematic.server.netty;

import static com.mogo.telematic.MogoLengthFrameDecoder.LENGTH_FIELD_OFFSET;
import static com.mogo.telematic.MogoLengthFrameDecoder.LENGTH_FIELD_SIZE;
import static com.mogo.telematic.MogoLengthFrameDecoder.MAX_FRAME_LENGTH;

import android.util.Log;

import com.mogo.telematic.MogoLengthFrameDecoder;
import com.mogo.telematic.MogoMessageEncoder;
import com.mogo.telematic.MogoProtocolMsg;

import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelId;
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;


/**
 * TCP 服务端
 * 目前服务端支持连接多个客户端
 */
public class NettyTcpServer {

    private static final String TAG = "NettyTcpServer";
    // client端用来过滤的
    public static final String SERVER_NAME = "NSD_SERVER";
    public static final int SERVER_PORT = 1088;
    public volatile static String sSERVER_SN = "";

    private Channel selectChannel;
    private CopyOnWriteArrayList<Channel> channelList = new CopyOnWriteArrayList<>();
    /**
     * channelId和业务标识(sn)绑定(场景一是断开连接时知道是哪个设备断开)
     */
    private ConcurrentMap<ChannelId, String> chlIdSnMap = new ConcurrentHashMap<>();

    private static NettyTcpServer instance = null;
    private NettyServerListener mListener;
    //    private boolean connectStatus;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private volatile boolean isServerStart;

    public static NettyTcpServer getInstance() {
        if (instance == null) {
            synchronized (NettyTcpServer.class) {
                if (instance == null) {
                    instance = new NettyTcpServer();
                }
            }
        }
        return instance;
    }

    private NettyTcpServer() {
    }

    public void start() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                startNettyServer();
            }
        }.start();
    }

    public void startNettyServer() {
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(SERVER_PORT))
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.SO_REUSEADDR, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            // decoder
                            ch.pipeline().addLast(new MogoLengthFrameDecoder(MAX_FRAME_LENGTH,
                                    LENGTH_FIELD_OFFSET,
                                    LENGTH_FIELD_SIZE));
                            // encoder
                            ch.pipeline().addLast(new MogoMessageEncoder());
                            ch.pipeline().addLast(new MogoProtocolServerHandler(mListener));
                        }
                    });

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind().sync();
            Log.e(TAG, NettyTcpServer.class.getName() + " started and listen on " + f.channel().localAddress());
            isServerStart = true;
            if (mListener != null) {
                mListener.onStartServer();
            }
            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            Log.e(TAG, e.getLocalizedMessage());
            e.printStackTrace();
        } finally {
            isServerStart = false;
            if (mListener != null) {
                mListener.onStopServer();
            }
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public void disconnect() {
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

    public void setListener(NettyServerListener listener) {
        this.mListener = listener;
    }

    public boolean isServerStart() {
        return isServerStart;
    }

    public boolean sendMsgToClient(MogoProtocolMsg mogoProtocolMsg, ChannelFutureListener listener) {
        boolean flag = selectChannel != null && selectChannel.isActive();
        if (flag) {
            selectChannel.writeAndFlush(mogoProtocolMsg).addListener(listener);
        }
        return flag;
    }

    public boolean sendMsgToSpecifiedClient(MogoProtocolMsg mogoProtocolMsg, Channel channel, ChannelFutureListener listener) {
        boolean flag = channel != null && channel.isActive();
        if (flag) {
            ChannelFuture channelFuture = channel.writeAndFlush(mogoProtocolMsg);
            if (listener != null) channelFuture.addListener(listener);
        }
        return flag;
    }

    public void sendMsgToAllClients(MogoProtocolMsg mogoProtocolMsg) {
        for (Channel chl : channelList) {
            if (chl != null && chl.isActive()) {
                chl.writeAndFlush(mogoProtocolMsg);
            }
        }
    }

    private final ChannelFutureListener mProxyListener = channelFuture -> {
        if (channelFuture.isSuccess()) {
            Log.d(TAG, "SendMsgToAllClients result was successful");
        } else {
            Log.d(TAG, "SendMsgToAllClients result was unsuccessful");
        }
    };

    /**
     * 切换通道(单通道通信)
     * 设置服务端，与哪个客户端通信
     *
     * @param channel
     */
    public void selectorChannel(Channel channel) {
        this.selectChannel = channel;
    }

    /**
     *
     * @param channel: Socket连接成功时保存下
     */
    public void addChannel(Channel channel) {
        channelList.add(channel);
    }

    public void removeChannel(Channel channel) {
        channelList.remove(channel);
    }

    public void putChannelSN(ChannelId channelId, String sn) {
        chlIdSnMap.put(channelId, sn);
    }

    public void removeChannelSN(ChannelId channelId) {
        chlIdSnMap.remove(channelId);
    }

    /**
     *
     * @param channelId 每次socket连接成功时分配的channelId，断开重连后会改变
     * @return
     */
    public String getSNByChlId(String channelId) {
        String sn = "";
        if (chlIdSnMap != null) {
            sn = chlIdSnMap.get(channelId);
        }
        return sn;
    }
}
