package com.mogo.telematic.client;

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 static com.mogo.telematic.MogoProtocolMsg.IDENTITY_REGIST;

import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;

import com.mogo.telematic.MogoLengthFrameDecoder;
import com.mogo.telematic.MogoMessageEncoder;
import com.mogo.telematic.MogoProtocolMsg;
import com.mogo.telematic.client.handler.HeartbeatHandler;
import com.mogo.telematic.client.handler.NettyClientHandler;
import com.mogo.telematic.client.handler.ReconnectHandler;
import com.mogo.telematic.client.listener.MessageStateListener;
import com.mogo.telematic.client.listener.NettyClientListener;
import com.mogo.telematic.client.status.ConnectState;

import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
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.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * TCP 客户端
 */
public class NettyTcpClient {
    private static final String TAG = "NettyTcpClient";
    /**
     *
     */
    public volatile static String sSERVER_SN = "";

    private Bootstrap bootstrap;

    private EventLoopGroup group;

    private NettyClientListener listener;

    private Channel channel;

    private volatile boolean canReconnect = true;
    private volatile boolean isConnected = false;

    /**
     * 最大重连次数
     */
    private int MAX_CONNECT_TIMES = Integer.MAX_VALUE;

    private int reconnectNum = MAX_CONNECT_TIMES;

    private boolean isNeedReconnect = true;
    private volatile boolean isConnecting = false;

    private long reconnectIntervalTime = 5000;

    private String host;
    private int tcp_port;
    private String mSign;
    /**
     * 心跳间隔时间
     */
    private long heartBeatInterval = 10;//单位秒

    /**
     * 是否发送心跳
     */
    private boolean isSendheartBeat = false;

    /**
     * 心跳数据，可以是String类型，也可以是byte[].
     */
    private Object heartBeatData;

    private NettyTcpClient(String host, int tcp_port, String sign) {
        this.host = host;
        this.tcp_port = tcp_port;
        this.mSign = sign;
    }

    public int getMaxConnectTimes() {
        return MAX_CONNECT_TIMES;
    }

    public long getReconnectIntervalTime() {
        return reconnectIntervalTime;
    }

    public String getHost() {
        return host;
    }

    public int getTcp_port() {
        return tcp_port;
    }

    public String getSign() {
        return mSign;
    }

    public void setSign(String sign) {
        this.mSign = sign;
    }

    public long getHeartBeatInterval() {
        return heartBeatInterval;
    }

    public boolean isSendheartBeat() {
        return isSendheartBeat;
    }

    public void connect() {
        if (isConnecting) {
            Log.d(TAG, "正在连接中，connect return");
            return;
        }
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            Thread clientThread = new Thread("client-Netty") {
                @Override
                public void run() {
                    super.run();
                    isNeedReconnect = true;
                    reconnectNum = MAX_CONNECT_TIMES;
                    connectServer();
                }
            };
            clientThread.start();
        } else {
            isNeedReconnect = true;
            reconnectNum = MAX_CONNECT_TIMES;
            connectServer();
        }
    }

    private void initBootstrap() {
        try {
            bootstrap = new Bootstrap().group(group)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            if (isSendheartBeat) {
                                ch.pipeline().addLast(new IdleStateHandler(heartBeatInterval, heartBeatInterval, 0, TimeUnit.SECONDS));
                                ch.pipeline().addLast(new HeartbeatHandler(mProxyListener));
                            }
                            // 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 NettyClientHandler(mProxyListener, mSign));
                            ch.pipeline().addLast(new ReconnectHandler(mProxyListener));
                        }
                    });
        } catch (Exception e) {
            Log.w(TAG, "初始化出错的原因为:" + e.getMessage());
        }
    }

    private void connectServer() {
        Log.d(TAG, "connectServer fun is called.");
        synchronized (NettyTcpClient.this) {
            if (!isConnected) {
                isConnecting = true;
                if (group == null || group.isShutdown()) {
                    group = new NioEventLoopGroup();
                }
                initBootstrap();
                doConnect();
            } else {
                Log.w(TAG, "socket正在连接中，无需重连");
            }
        }
    }

    private void doConnect() {
        Log.d(TAG, "开始连接Server端");
        if (channel != null && channel.isOpen() && channel.isActive()) {
            Log.w(TAG, "Channel is active, no need to call start.");
            return;
        }
        if (!canReconnect) {
            Log.d(TAG, "重连已被关闭则直接return.");
            return;
        }
        ChannelFuture channelFuture;
        try {
            if (bootstrap != null) {
                if (group == null || group.isShutdown()) {
                    group = new NioEventLoopGroup();
                    bootstrap.group(group);
                }
                channelFuture = bootstrap.connect(host, tcp_port);
                channel = channelFuture.channel();
                channelFuture.addListener(mFutureListener);

                channel.closeFuture().addListener((ChannelFutureListener) future -> {
//                    // 确保连接不再被使用时使用，否则重连时task会被拒绝
//                    if (future.channel() != null) {
//                        future.channel().eventLoop().shutdownGracefully();
//                    }
                    channel = null;
                    Log.e(TAG, "连接已经结束!");
                });
            }
        } catch (Exception e) {
            Log.e(TAG, "连接Server ip:" + host + ",port:" + tcp_port + ",失败信息:" + e.getMessage());
            isConnected = false;
        }
    }

    public void reconnectServer() {
        if (!TextUtils.isEmpty(host)) {
            doConnect();
        }
    }

    private NettyClientListener mProxyListener = new NettyClientListener() {
        @Override
        public void onMessageResponseClient(Object msg, String sign, Channel channel) {
            if (listener != null) {
                listener.onMessageResponseClient(msg, sign, channel);
            }
        }

        @Override
        public void onClientStatusConnectChanged(int statusCode, String sign, Channel channel) {
            if (listener != null) {
                listener.onClientStatusConnectChanged(statusCode, sign, channel);
            }
            if (statusCode == ConnectState.STATUS_CONNECT_SUCCESS && !TextUtils.isEmpty(mSign)) {
                byte[] signByteArr = mSign.getBytes();
                MogoProtocolMsg msg = new MogoProtocolMsg(IDENTITY_REGIST, signByteArr.length, signByteArr);
                sendMsgToServer(msg, isSuccess -> {
                    Log.d(TAG, "身份注册成功：" + isSuccess);
                });
            }
        }
    };

    private ChannelFutureListener mFutureListener = channelFuture -> {
        if (channelFuture.isDone()) {
            if (channelFuture.isSuccess()) {
                Log.d(TAG, "连接成功");
                reconnectNum = MAX_CONNECT_TIMES;
                isConnected = true;
            } else {
                if (channelFuture.isCancelled()) {
                    Log.w(TAG, "Connection attempt cancelled by user.");
                } else {
                    Log.e(TAG, "连接失败原因为：" + channelFuture.cause().getMessage());
                }
                isConnected = false;
                channelFuture.channel().pipeline().fireChannelInactive();
            }
            isConnecting = false;
        } else {
            Log.w(TAG, "Connection isn't done.");
            isConnected = false;
        }
    };

    public void disconnect() {
        Log.e(TAG, "断开连接!");
        isNeedReconnect = false;
        isConnecting = false;
        isConnected = false;
        if (group != null) {
            group.shutdownGracefully();
            group = null;
        }
        bootstrap = null;
    }

    public boolean sendMsgToServer(MogoProtocolMsg protocolMsg, final MessageStateListener listener) {
        boolean flag = channel != null && isConnected;
        if (flag) {
            channel.writeAndFlush(protocolMsg).addListener((ChannelFutureListener) channelFuture -> {
                if (listener != null) {
                    listener.isSendSuccess(channelFuture.isSuccess());
                }
            });
        }
        return flag;
    }

    /**
     * 获取TCP连接状态
     *
     * @return 获取TCP连接状态
     */
    public boolean getConnectStatus() {
        return channel != null && channel.isOpen() && channel.isActive();
    }

    public boolean isConnecting() {
        return isConnecting;
    }

    public void setConnectStatus(boolean status) {
        this.isConnected = status;
    }

    public boolean isConnected() {
        return this.isConnected;
    }

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

    /**
     * 构建者，创建NettyTcpClient
     */
    public static class Builder {

        /**
         * 最大重连次数
         */
        private int MAX_CONNECT_TIMES = Integer.MAX_VALUE;

        /**
         * 重连间隔
         */
        private long reconnectIntervalTime = 5000;
        /**
         * 服务器地址
         */
        private String host;
        /**
         * 服务器端口
         */
        private int tcp_port;
        /**
         * 客户端标识，(因为可能存在多个连接)
         */
        private String mSign;

        /**
         * 是否发送心跳
         */
        private boolean isSendheartBeat;
        /**
         * 心跳时间间隔
         */
        private long heartBeatInterval = 5;

        /**
         * 心跳数据，可以是String类型，也可以是byte[].
         */
        private Object heartBeatData;

        public Builder() {
        }

        public Builder setMaxReconnectTimes(int reConnectTimes) {
            this.MAX_CONNECT_TIMES = reConnectTimes;
            return this;
        }


        public Builder setReconnectIntervalTime(long reconnectIntervalTime) {
            this.reconnectIntervalTime = reconnectIntervalTime;
            return this;
        }


        public Builder setHost(String host) {
            this.host = host;
            return this;
        }

        public Builder setTcpPort(int tcp_port) {
            this.tcp_port = tcp_port;
            return this;
        }

        public Builder setSign(String sign) {
            this.mSign = sign;
            return this;
        }

        public Builder setHeartBeatInterval(long intervalTime) {
            this.heartBeatInterval = intervalTime;
            return this;
        }

        public Builder setSendHeartBeat(boolean isSendheartBeat) {
            this.isSendheartBeat = isSendheartBeat;
            return this;
        }

        public Builder setHeartBeatData(Object heartBeatData) {
            this.heartBeatData = heartBeatData;
            return this;
        }

        public NettyTcpClient build() {
            NettyTcpClient nettyTcpClient = new NettyTcpClient(host, tcp_port, mSign);
            nettyTcpClient.MAX_CONNECT_TIMES = this.MAX_CONNECT_TIMES;
            nettyTcpClient.reconnectIntervalTime = this.reconnectIntervalTime;
            nettyTcpClient.heartBeatInterval = this.heartBeatInterval;
            nettyTcpClient.isSendheartBeat = this.isSendheartBeat;
            nettyTcpClient.heartBeatData = this.heartBeatData;
            return nettyTcpClient;
        }
    }
}
