package cn.iododer.yudao.module.mqtt.config;

//import cn.iododer.yudao.module.mqtt.customer.MqttMessageListener;
import cn.iododer.yudao.module.mqtt.enums.DefineSubTopicEnum;
import cn.iododer.yudao.module.mqtt.service.*;
import cn.iododer.yudao.module.mqtt.util.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

/**
 * Mqtt 工厂类
 */
@Slf4j
@Component
public class MqttFactory {

    @Autowired
    private MqttProperties config;

    private static MqttFactory factory;

    private static MqttClient client;


    @PostConstruct
    public void init() {
        factory = this;
        factory.config = this.config;
    }


    /**
     * 获取客户端实例
     *      单例模式：存在即返回，不存在则初始化
     *
     * @return client
     * @throws MqttException 此处刻意抛出异常，否则无法执行断线重连
     */
    public static MqttClient getInstance() throws MqttException {
        if (client == null) {
            connect();
        }
        return client;
    }


    /**
     * 清空客户端实例
     *      当 mqtt 断开连接时，需清空 clientId，再执行断线重连
     */
    public static void clear() {
        client = null;
    }


    /**
     * 断线重连方法
     */
    public static void reconnect() {
        int count = 0;
        while (true) {
            clear();
            ++ count;

            try {
                log.info("----------------[MQTT]即将执行自动重连----------------");
                getInstance();
                log.info("----------------[MQTT]自动重连成功----------------");
                break;
            } catch (MqttException e) {
                log.error("----------------[MQTT]自动重连失败，当前为第 {} 次尝试----------------", count);
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException ex) {
                    log.error("----------------[MQTT]自动重连，休眠失败！----------------", e);
                }
            }
        }
    }

    /**
     * 客户端连接服务端
     *
     * @throws MqttException 此处刻意抛出异常，否则无法执行断线重连
     */
    private static void connect() throws MqttException {
        // 创建MQTT客户端对象
        client = new MqttClient(factory.config.getHostUrl(), factory.config.getClientId(), new MemoryPersistence());
        // 连接设置
        MqttConnectOptions options = new MqttConnectOptions();
        // 是否清空session，设置false表示服务器会保留客户端的连接记录（订阅主题，qos）,客户端重连之后能获取到服务器在客户端断开连接期间推送的消息
        // 设置为true表示每次连接服务器都是以新的身份
        options.setCleanSession(false);
        // 设置连接用户名
        options.setUserName(factory.config.getUsername());
        // 设置连接密码
        options.setPassword(factory.config.getPassword().toCharArray());
        // 设置超时时间，单位为秒
        options.setConnectionTimeout(100);
        // 设置心跳时间 单位为秒，表示服务器每隔 20 秒的时间向客户端发送心跳判断客户端是否在线
        options.setKeepAliveInterval(20);
        // 调整客户端连接参数
        options.setMaxInflight(factory.config.getMaxInflight());
        // 设置遗嘱消息的话题，若客户端和服务器之间的连接意外断开，服务器将发布客户端的遗嘱信息
        options.setWill("willTopic", (factory.config.getClientId() + "MQTT客户端与服务器断开连接").getBytes(), 0, false);
        // 设置回调
        client.setCallback(new MqttCallBack());
        client.connect(options);

        // 设置默认订阅主题
        // 消息等级，与主题数组一一对应
        int[] qos = DefineSubTopicEnum.queryAllQos();
        // 主题
        String[] topics = DefineSubTopicEnum.queryAllTopic();
        // 订阅主题
        client.subscribe(topics, qos);
    }


    /**
     * 根据topic去实现自己的业务
     * @param topic
     * @return
     */
    public static MqttService getMqttFactory(String topic){
        DefineSubTopicEnum defineSubTopicEnum = DefineSubTopicEnum.getDefineSubTopicEnumByTopic(topic);
        switch (defineSubTopicEnum){
            case ROBOT_STATUS:
                return BeanUtils.getBean(RobotStatusServiceImpl.class);
            case ROBOT_REACTIVE_STATUS:
                return BeanUtils.getBean(RobotReactiveStatusServiceImpl.class);
            case ROBOT_GENERICS_STATUS:
                return BeanUtils.getBean(RobotGenericsStatusServiceImpl.class);
            case ROBOT_PATH_STATUS:
                return BeanUtils.getBean(RobotPathStatusServiceImpl.class);
            case ROBOT_WORK_STATUS:
                return BeanUtils.getBean(RobotWorkStatusServiceImpl.class);
            case ROBOT_UPDATE_PALLET_HEIGHT:
                return BeanUtils.getBean(RobotUpdatePalletHeightServiceImpl.class);
            case ROBOT_OBSTACLES_STATUS:
                return BeanUtils.getBean(RobotObstaclesStatusServiceImpl.class);
            case ROBOT_WIRELESS_SIGNAL_STATUS:
                return BeanUtils.getBean(RobotWirelessSignalStatusServiceImpl.class);
            case PLANNING_INIT_DATA:
                return BeanUtils.getBean(PathPlanningInitDataServiceImpl.class);
            case PLANNING_DISTRIBUTION_TASK:
                return BeanUtils.getBean(PathPlanningDistributionTaskServiceImpl.class);
            case PLANNING_DISTRIBUTION_FAIL:
                return BeanUtils.getBean(PathPlanningDistributionFailServiceImpl.class);
            case PLANNING_SIMULATION_ROBOT_POSE_REQUEST:
                return BeanUtils.getBean(PlanningSimulationRobotPoseRequestServiceImpl.class);
            case PLANNING_MOVE:
                return BeanUtils.getBean(PathPlanningMoveServiceImpl.class);
            case PLANNING_ROUTE_DISPLAY:
                return BeanUtils.getBean(PathRouteDisplayPlanningServiceImpl.class);
            case PLANNING_GRAPH_MATCH_DATA:
                return BeanUtils.getBean(PlanningGraphMatchDataServiceImpl.class);
            case PLANNING_REMOTE_ROBOT_MOVE_POSE_PLANNING:
                return BeanUtils.getBean(PlanningRemoteRobotMovePosePlanningImpl.class);
            default :
                return BeanUtils.getBean(RobotTaskStatusServiceImpl.class);
        }
    }

}