package cn.muchinfo.rma.netcore.socket;

import android.annotation.SuppressLint;
import android.os.Build;

import androidx.annotation.RequiresApi;

import com.blankj.utilcode.util.LogUtils;
import com.google.common.primitives.Bytes;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import cn.muchinfo.rma.netcore.packet.Packet40;
import cn.muchinfo.rma.netcore.packet.Packet50;
import cn.muchinfo.rma.netcore.socket.core.SocketEngine;
import cn.muchinfo.rma.netcore.socket.core.SocketListener;

/**
 * MTP2.0 长链通信类
 * @param <T> Packet40 or Packet50
 */
public class MTP2Socket<T> {
    /** 报文类型，0 - 4.0行情报文，1 - 5.0交易报文 */
    public int packetType;
    /** 服务端IP地址 */
    public String IP = "";
    /** 服务端端口 */
    public String port = "";
    /** 当前连接状态，0 - 未连接，1 - 连接中，2 - 已连接 */
    public int connState = 0;

    /** 回调委托 */
    public MTP2SocketListener<T> listener;

    /** Socket 对象 */
    private SocketEngine socket;
    /** 当前流水号 */
    private int currentSerial = 1;
    /** 默认超时时长（秒） */
    private final int timeOutInterval = 30 * 1000;
    /** 信息发送异步建值对，ConcurrentHashMap会自动处理线程安全问题 */
    private final Map<String, AsyncSocketTask<T>> asyncTaskMap = new ConcurrentHashMap<>();
    /** 段包 */
    private List<Byte> cache = null;
    /** 当前处理的段包报文的原长度 */
    private long cachePackageLength = 0;

    /** 心跳专用定时器 */
    private Timer beatTimer = null;
    /** 最后一次收到心跳的时间 */
    private Date lastRecevieBeatTime = null;

    /** 开始重连 */
    public static final int ReconnectChangeState_BeginReconnect = 0;
    /** 尝试重连失败，将在下一个周期后再次尝试重连 */
    public static final int ReconnectChangeState_FailAndWaitPeriod = 1;
    /** 重连成功，将进行业务操作 */
    public static final int ReconnectChangeState_ReconnectSuccessed = 2;
    /** 重连成功后业务操作失败，由业务模块发起 */
    public static final int ReconnectChangeState_LoginFail = 3;
    /** 重连成功后业务操作成功（包括交易服务的账户登录状态更新或行情服务的行情订阅等） */
    public static final int ReconnectChangeState_Logined = 4;

    /** 外部是否要求停止断网重连操作标志 */
    public boolean isBrokenReconnecting = false;
    /** 当前是否正在进行断网重连 */
    private boolean isReconnecting = false;
    /** 当前是否可进行断网重连；业务与网络分离，而断网重连需要在登录账号后（或行情订阅成功后）才可进行 */
    public boolean canReconnect = false;
    /** 断网重连失败后重试的Timer */
    private Timer reconnectTimer = null;

    public MTP2Socket(int packetType, MTP2SocketListener<T> listener) {
        this.packetType = packetType;
        this.listener = listener;
    }

    /**
     * 手动析构方法
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    public void dealloc() {
        // 停止心跳
        if (null != this.beatTimer) {
            this.beatTimer.cancel();
            this.beatTimer = null;
        }

        // 停止断网重连
        this.canReconnect = false;
        this.isBrokenReconnecting = true;
        if (null != this.reconnectTimer) {
            this.reconnectTimer.cancel();
            this.reconnectTimer = null;
        }

        // 清空回调任务
        if (this.asyncTaskMap.size() > 0) {
            this.asyncTaskMap.forEach((key, asyncTaskMap) -> {
                if (null != asyncTaskMap.timeOut) {
                    asyncTaskMap.timeOut.cancel();
                }
            });
            this.asyncTaskMap.clear();
        }

        // 断开连接
        if (this.connState == 2) {
            this.socket.stopSocket();
        }
        this.socket = null;
    }

    /**
     * 连接服务端
     * @param IP 服务端IP地址
     * @param port 服务端端口号
     */
    public void conn(String IP, String port, Callback<T> callback) {
        if (null != this.socket) {
            this.socket.stopSocket();
            this.socket = null;
        }

        this.connState = 1;
        this.socket = new SocketEngine(IP, port, new SocketListener() {
            @Override
            public void hostError() {

            }

            @Override
            public void initConnectException() {

            }

            @Override
            public void connectSuccess() {
                // 连接成功
                MTP2Socket.this.connState = 2;
                MTP2Socket.this.IP = IP;
                MTP2Socket.this.port = port;
                if (null != callback)
                    callback.onSuccess(null);
                if (null != MTP2Socket.this.listener)
                    MTP2Socket.this.listener.onConnected(MTP2Socket.this);
            }

            @Override
            public void disConnect(int type) {
                // 连接断开
                MTP2Socket.this.connState = 0;
                if (null != callback)
                    callback.onFail(null);
                if (null != MTP2Socket.this.listener)
                    MTP2Socket.this.listener.onClosed(MTP2Socket.this);

                // 判断是否可进行断网重连
                // FIXME: - 由于底层在断网时触发的是disConnect方法，所以目前暂时由心跳超时来触发重连
                MTP2Socket.this.canReconnectThenError();
            }

            @Override
            public void byteMessage(byte[] message) {
                // 接收信息
                MTP2Socket.this.disposeReceive(message);
            }

            @Override
            public void connectOutTime(String host, String port) {

            }

            @Override
            public void connectException() {
                // 发生连接错误
                MTP2Socket.this.connState = 0;
                if (null != callback)
                    callback.onFail(new Error("连接错误"));
                if (null != MTP2Socket.this.listener)
                    MTP2Socket.this.listener.onError(MTP2Socket.this, new Error("连接错误"));

                // 判断是否可进行断网重连
                // FIXME: - 由于底层在断网时触发的是disConnect方法，所以目前暂时由心跳超时来触发重连
//                MTP2Socket.this.canReconnectThenError();
            }
        });
        this.socket.connect();
    }

    /**
     * 断开连接
     */
    public void close() {
        this.connState = 0;
        if (null != this.socket) {
            this.socket.stopSocket();
            this.socket = null;
        }
    }

    /**
     * 启动心跳请求包发送Timer
     */
    public void startBeatTime() {
        this.stopBeatTimer();
        this.lastRecevieBeatTime = new Date();

        // 心跳Timer
        this.beatTimer = new Timer(true);
        TimerTask timerTask = new MyTimerTask() {
            @Override
            public void run() {
                MTP2Socket.this.sendBeat();
            }
        };
        /** 心跳间隔时间，默认为10秒 */
        int beatInterval = 10 * 1000;
        this.beatTimer.schedule(timerTask, 0, beatInterval);   // 会马上执行一次
    }

    /**
     * 停止心跳请求包发送Timer
     */
    public void stopBeatTimer() {
        if (null != this.beatTimer) {
            this.beatTimer.cancel();
            this.beatTimer = null;
        }
    }

    /**
     * 发送心跳
     */
    private void sendBeat() {
        if (this.connState != 2)
            return;

        // 发送心跳
        if (this.packetType == 0) {
            // 4.0
            Packet40 packet = new Packet40((byte)0x12, (short) 0, null);
            this.send40(packet, 0, null);
        } else if (this.packetType == 1) {
            // 5.0
            Packet50 packet = new Packet50(0, null);
            this.send50(packet, 0, null);
        }

        // 如果已经超过或心跳超时时长没有收到心跳回复，则认为网络已经异常，进行断网重连
        // WARNING: - 注意在正常断网时停止心跳逻辑
        Date date = new Date();
        /** 心跳回复超时时间，默认为30秒 */
        int beatTimeoutInterval = 30 * 1000;
        if ((date.getTime() - this.lastRecevieBeatTime.getTime()) > beatTimeoutInterval) {
            if (null != this.listener)
                this.listener.onError(this, new Error("心跳超时"));

            // 判断是否可进行断网重连
            this.canReconnectThenError();
        }
    }

    /**
     * 发送报文
     * @param packet 目标报文, Packet40 or Packet50
     * @param rsp 回复号。P为Package40对象时为回复大类号，同时必传；P为Package50对象为回复功能码，可传零。
     * @param callback 回调
     */
    public void send(T packet, int rsp, Callback<T> callback) {
        if (this.connState != 2) {
            if (null != callback)
                callback.onFail(new Error("连接状态错误"));
            return;
        }

        if (packet instanceof Packet40) {
            // 4.0 报文
            this.send40((Packet40)packet, rsp, callback);
        } else if (packet instanceof Packet50) {
            //  5.0 报文
            this.send50((Packet50)packet, rsp, callback);
        } else {
            if (null != callback)
                callback.onFail(new Error("错误的报文"));
        }
    }

    // 4.0报文发送方法
    private void send40(Packet40 packet, int rspMainClassNumber, Callback<T> callback) {
        // 设置流水号
        packet.serialNumber = this.currentSerial;
        this.currentSerial++;

        // 判断是否需要异步回调
        if (null != callback) {
            @SuppressLint("DefaultLocale") String key =
                    String.format("%s_%d", packet.serialNumber, rspMainClassNumber);
            // 超时
            Timer timer = new Timer(true);
            MyTimerTask timerTask = new MyTimerTask() {
                @Override
                public void run() {
                    // 超时触发
                    AsyncSocketTask<T> asyncSocketTask  =
                            MTP2Socket.this.asyncTaskMap.get(this.key);
                    if (null != asyncSocketTask) {
                        if (null != asyncSocketTask.callback) {
                            asyncSocketTask.callback.onFail(new Error("业务超时"));
                        }
                    }
                    MTP2Socket.this.asyncTaskMap.remove(this.key);
                }
            };
            timerTask.key = key;
            timer.schedule(timerTask, this.timeOutInterval);
            // 报文异步任务
            AsyncSocketTask<T> asyncSocketTask =
                    new AsyncSocketTask<T>(
                            key,
                            rspMainClassNumber,
                            timer,
                            callback
                    );

            this.asyncTaskMap.put(key, asyncSocketTask);
        }

        // 发送信息
        this.socket.send(packet.getBytes());
    }

    // 5.0报文发送方法
    private void send50(Packet50 packet, int rspFunCode , Callback<T> callback) {
        // 设置流水号
        packet.serialNumber = this.currentSerial;
        this.currentSerial++;

        // 判断是否需要异步回调
        if (null != callback) {
            @SuppressLint("DefaultLocale") String key = String.format("%d", packet.serialNumber);
            // 超时
            Timer timer = new Timer(true);
            MyTimerTask timerTask = new MyTimerTask() {
                @Override
                public void run() {
                    // 超时触发
                    AsyncSocketTask<T> asyncSocketTask  =
                            MTP2Socket.this.asyncTaskMap.get(this.key);
                    if (null != asyncSocketTask) {
                        if (null != asyncSocketTask.callback) {
                            asyncSocketTask.callback.onFail(new Error("业务超时"));
                        }
                    }
                    MTP2Socket.this.asyncTaskMap.remove(this.key);
                }
            };
            timerTask.key = key;
            timer.schedule(timerTask, this.timeOutInterval);
            // 报文异步任务
            AsyncSocketTask<T> asyncSocketTask =
                    new AsyncSocketTask<T>(
                            key,
                            rspFunCode,
                            timer,
                            callback
                    );
            this.asyncTaskMap.put(key, asyncSocketTask);
        }

        // 发送信息
        this.socket.send(packet.getBytes());
    }

    // 接收报文处理方法
    private void disposeReceive(byte[] bytes) {
        for (byte b: bytes) {
            if (null == this.cache) {
                // 新报文
                if (b != (byte) 0xFF) {
                    // 接收到首字节不是0xFF的错误数据
                    return;
                }
                this.cache = new ArrayList<>();
                this.cache.add(b);
            } else {
                // 追加数据
                this.cache.add(b);

                // 取报文长度
                if (this.cache.size() == 5) {
                    if (this.packetType == 0) {
                        // 4.0报文
                        this.cachePackageLength =
                                ByteBuffer.wrap(Bytes.toArray(this.cache), 1, 4).
                                        order(ByteOrder.BIG_ENDIAN).getInt();
                    } else {
                        // 5.0 报文
                        this.cachePackageLength =
                                ByteBuffer.wrap(Bytes.toArray(this.cache), 1, 4).
                                        order(ByteOrder.LITTLE_ENDIAN).getInt();
                    }
                    if (this.cachePackageLength > 65535) {
                        // 错误的报文长度
                        this.cache = null;
                        this.cachePackageLength = 0;
                        return;
                    }
                }

                // 判断是否已经到包尾
                if (this.cache.size() == this.cachePackageLength) {
                    if (b != (byte)0x0) {
                        // 接收到尾字节不是0x0的错误数据包
                        this.cache = null;
                        this.cachePackageLength = 0;
                        return;
                    }
                    if (this.packetType == 0) {
                        // 4.0报文
                        this.disposePackage40(Bytes.toArray(this.cache));
                    } else {
                        // 5.0报文
                        this.disposePackage50(Bytes.toArray(this.cache));
                    }
                    this.cache = null;
                    this.cachePackageLength = 0;
                }
            }
        }
    }

    // 接收4.0报文处理方法
    private void disposePackage40(byte[] bytes) {
        Packet40 packet = new Packet40(bytes);
        if (packet.packageLength == 0) {
            // 报文装箱失败
            return;
        }

        if (packet.mainClassNumber == (byte)0x12 || packet.mainClassNumber == (byte)0x41 || packet.mainClassNumber == (byte)0x42) {
            // 推送类报文, 0x12 - 心跳, 0x41 - 实时行情推送, 0x42 - 控制信号
            if (packet.mainClassNumber == (byte)0x12) {
                // 心跳
                this.lastRecevieBeatTime = new Date();
            } else {
                // FIXME: - 这里的泛型强转应该找更好的方法
                if (null != this.listener)
                    this.listener.onReceivePush(this, (T)packet);
            }
        } else {
            // 非推送类报文
            @SuppressLint("DefaultLocale") String key =
                    String.format("%s_%d", packet.serialNumber, packet.mainClassNumber);
            AsyncSocketTask<T> asyncSocketTask = this.asyncTaskMap.get(key);
            if (null != asyncSocketTask) {
                asyncSocketTask.timeOut.cancel();
                asyncSocketTask.timeOut = null;
                if (null != asyncSocketTask.callback)
                    asyncSocketTask.callback.onSuccess((T)packet);
                this.asyncTaskMap.remove(key);
            }
        }
    }

    // 接收5.0报文处理方法
    private void disposePackage50(byte[] bytes) {
        Packet50 packet = new Packet50(bytes);
        if (packet.packageLength == 0) {
            // 报文装箱失败
            return;
        }

        if (packet.funCode == 0 || packet.serialNumber == 0) {
//            LogUtils.eTag("RMA", "++++++++++++++ 接收心跳包,type=1");
            if (packet.funCode == 0) {
                // 心跳
                this.lastRecevieBeatTime = new Date();
            } else {
                // 推送类报文
                if (null != this.listener)
                    this.listener.onReceivePush(this, (T)packet);
            }
        } else {
            // 非推送类报文
            @SuppressLint("DefaultLocale") String key = String.format("%d", packet.serialNumber);
            AsyncSocketTask<T> asyncSocketTask = this.asyncTaskMap.get(key);
            if (null != asyncSocketTask) {
                // 判断是否需要检验回复功能码
                if (asyncSocketTask.rspFunCode != 0 && asyncSocketTask.rspFunCode != packet.funCode)
                    return;

                asyncSocketTask.timeOut.cancel();
                asyncSocketTask.timeOut = null;
                if (null != asyncSocketTask.callback)
                    asyncSocketTask.callback.onSuccess((T)packet);
                this.asyncTaskMap.remove(key);
            }
        }
    }

    /**
     * 判断是否可进行断网重连
     */
    private void canReconnectThenError() {
        // 判断是否应该启动断网重连机制
        if (this.isReconnecting || !this.canReconnect || this.isBrokenReconnecting) return;

        // 停止心跳
        this.stopBeatTimer();

        // 回调当前所有发送信息错误块
        this.callAllAsyncTaskOnReconnecting();

        // 开始断网重连机制
        this.isReconnecting = true;
        this.reconnect();
    }

    /**
     * 断网重连方法，在重连尝试失败时会递归调用自己
     */
    private void reconnect() {
        // 判断是否已由外部主动断开
        if (this.isBrokenReconnecting) {
            this.isReconnecting = false;
            return;
        }

        // 开始尝试重连服务端
        if (null != this.listener)
            this.listener.onReconnectChangeState(this,
                    MTP2Socket.ReconnectChangeState_BeginReconnect);
        LogUtils.eTag("RMA", "---------------开始重连");
        this.conn(this.IP, this.port, new Callback<T>() {
            @Override
            public void onSuccess(T res) {
                LogUtils.eTag("RMA", "---------------连接链路成功");
                // 连接成功
                MTP2Socket.this.isReconnecting = false;
                if (null != MTP2Socket.this.listener)
                    MTP2Socket.this.listener.onReconnectChangeState(MTP2Socket.this,
                            MTP2Socket.ReconnectChangeState_ReconnectSuccessed);
            }

            @Override
            public void onFail(Error err) {
                LogUtils.eTag("RMA", "---------------连接链路失败");
                // 连接失败
                if (null != MTP2Socket.this.listener)
                    MTP2Socket.this.listener.onReconnectChangeState(MTP2Socket.this,
                            MTP2Socket.ReconnectChangeState_FailAndWaitPeriod);

                // 5秒后尝试重连
                if (null != MTP2Socket.this.reconnectTimer) {
                    MTP2Socket.this.reconnectTimer.cancel();
                    MTP2Socket.this.reconnectTimer = null;
                }
                MTP2Socket.this.reconnectTimer = new Timer(true);
                TimerTask timerTask = new MyTimerTask() {
                    @Override
                    public void run() {
                        MTP2Socket.this.reconnect();
                    }
                };
                MTP2Socket.this.reconnectTimer.schedule(timerTask, 5 * 1000);
            }
        });
    }

    /**
     * 在断网重连时调用所有的错误回调
     */
    private void callAllAsyncTaskOnReconnecting() {
        try {
            for (String key: this.asyncTaskMap.keySet()) {
                AsyncSocketTask<T> asyncSocketTask = this.asyncTaskMap.get(key);
                if (null != asyncSocketTask.callback) {
                    asyncSocketTask.callback.onFail(new Error("发生断网重连"));
                }
                asyncSocketTask.timeOut.cancel();
                asyncSocketTask.timeOut = null;
            }
            this.asyncTaskMap.clear();
        } catch (Exception e) {
            LogUtils.eTag("RMA", e.getMessage());
        }
    }
}
