import { MQTTConfig } from "../../config/MQTTConfig";
import { SDKLog } from "../../util/SDKLog";
import { Utils } from "../../util/Utils";
import { IMQTTCallback } from "./IMQTTCallback";
import { IMQTTClient } from './IMQTTClient';
import { MQTTConnectOption } from "./MQTTConnectOption";
import { AccountBindDeviceMessage } from "./message/AccountBindDeviceMessage";
import { CastSessionMessage } from "./message/CastSessionMessage";
import { DataMessage } from "./message/DataMessage";
import { DisplayReportMessage } from "./message/DisplayReportMessage";
import { MsgType } from "./message/MsgType";
import { UserHasBeenKickedMessage } from "./message/UserHasBeenKickedMessage";
import { WillMessage } from "./message/WillMessage";
import { YMSMessage } from "./message/YMSMessage";
import { MessageParser } from "./parser/MessageParser";

export abstract class MQTTLogic implements IMQTTCallback {
    readonly TAG: string = "MQTTLogic";
    private parser: MessageParser;
    private clientId: string;
    private messageTimestampOfSenderUnderTopics: Map<string, Map<string, number>>;
    private client: IMQTTClient | undefined;
    private config: MQTTConfig | null;
    private displayCode: number;
    protected isConnected: boolean;

    constructor(clientId: string) {
        this.messageTimestampOfSenderUnderTopics = new Map();
        this.parser = new MessageParser();
        this.clientId = clientId;
        this.config = null;
        this.displayCode = -1;
        this.isConnected = false;
    }

    public setMQTTClientImpl(impl: IMQTTClient, config: MQTTConfig): void {
        this.client = impl;
        this.config = config;
        this.client?.init(this.getConfig().getBroker(), this.clientId);
    }

    /**
     * 连接MQTT服务器
     * @param displayCode 传入值<=0时，代表需要通配（#）
     */
    public connect(displayCode: number): void {
        this.displayCode = displayCode;
        let options: MQTTConnectOption = this.createMqttOptions();
        let willMessage: WillMessage = this.createMySelfWill(displayCode, this.clientId);
        let topic: string;
        if (displayCode <= 0) {
            // 遗嘱消息不能使用通配符
            topic = Utils.getTopicPrefix(MsgType.WILL);
        } else {
            topic = Utils.getTopicPrefix(MsgType.WILL) + displayCode;
        }
        // set will message
        options.willTopic = topic;
        options.willMessage = this.parser.toJson(willMessage);
        options.willQoS = willMessage.getQoS();
        options.isWillRetained = willMessage.isRetained();
        // connect
        this.connectWithOptions(options);
        this.subscribeInterestTopic(displayCode);
    }

    protected connectWithOptions(options: MQTTConnectOption): void {
        this.client?.connect(options);
        this.client?.setCallback(this);
        this.isConnected = true;
    }

    protected createMqttOptions(): MQTTConnectOption {
        let options: MQTTConnectOption = new MQTTConnectOption();
        options.userName = this.getConfig().getUserName();
        options.password = this.getConfig().getPassWord();
        options.isCleanSession = this.getConfig().isCleanSession();
        options.keepAliveInterval = this.getConfig().getKeepAlive();
        options.connectionTimeout = this.getConfig().getConnectTimeout();
        return options;
    }

    public publish(displayCode: number, ymsMessage: YMSMessage): void {
        ymsMessage.timestamp = Date.now();
        ymsMessage.senderClientId = this.clientId;
        this.client?.publish(Utils.getTopicPrefix(ymsMessage.msgType) + displayCode,
                this.parser.toJson(ymsMessage), ymsMessage.getQoS(), ymsMessage.isRetained());
    }

    public disconnect(displayCode: number): Promise<string> {
        return new Promise((resolve, reject) => {
          this.unsubscribeAllTopics(displayCode);
          this.client?.disconnect()
            .then(() => {
              this.isConnected = false;
              resolve("MQTT断连成功");
            })
            .catch(reject);
        });
    }

    // override
    connectionLost(cause: string): void {
        SDKLog.e(this.TAG, cause);
        this.isConnected = false;
        this.onConnectionLost(cause);
    }

    // override
    connectComplete(reconnect: boolean, serverURI: string): void {
        this.isConnected = true;
        // 如果MQTT实现库内部会尝试自动重连，则在此响应自动重连成功后的回调，并再次进行主题订阅
        if (reconnect) {
            this.subscribeInterestTopic(this.displayCode);
        }
    }

    // override
    messageArrived(topic: string, message: string): void {
        SDKLog.i(this.TAG, "message arriavd, topic = " + topic);
        let ymsMessage: YMSMessage = this.parser.parse(message);
        if (null === ymsMessage || undefined === ymsMessage) return;
        if (this.isMessageFromSelf(ymsMessage)) {
            return;
        }
        if (!this.isMessageForMe(ymsMessage)) {
            return;
        }
        if (this.isMessageExpired(topic, ymsMessage)) {
            return;
        }
        switch (ymsMessage.msgType) {
            case MsgType.WILL:
                this.onWillMessageArrived(ymsMessage as WillMessage);
                break;
            case MsgType.REPORT:
                this.onDisplayReportMessageArrived(ymsMessage as DisplayReportMessage);
                break;
            case MsgType.CAST:
                this.onCastSessionMessageArrived(ymsMessage as CastSessionMessage);
                break;
            case MsgType.DATA:
                this.onDataMessageArrived(ymsMessage as DataMessage);
                break;
            case MsgType.ACCOUNT:
                this.onUserHasBeenKickedMessageArrived(ymsMessage as UserHasBeenKickedMessage);
                break;
            case MsgType.BIND_DEVICE:
                this.onAccountBindDeviceMessageArrived(ymsMessage as AccountBindDeviceMessage);
                break;
        }
    }

    private isMessageExpired(topic: string, message: YMSMessage): boolean {
        // 如果是will遗嘱消息，则不做过期判断，直接返回未过期
        if (MsgType.WILL === message.msgType) {
            return false;
        }
        // 判断message的topic是否已经缓存在map，没有则进行首次添加
        if (0 === this.messageTimestampOfSenderUnderTopics.size ||
                !this.messageTimestampOfSenderUnderTopics.has(topic)) {
            let firstTimeOfTopic: Map<string, number>  = new Map();
            this.messageTimestampOfSenderUnderTopics.set(topic, firstTimeOfTopic);
        }
        // 取对应topic的value，就是这个topic下所有发送端设备上一次发送消息的时间戳map
        let messageTimestampOfSender: Map<string, number> | undefined = this.messageTimestampOfSenderUnderTopics.get(topic);
        // 判断此clientId设备是否已经缓存上次时间戳，没有则进行首次添加
        if (0 === messageTimestampOfSender!.size ||
                !messageTimestampOfSender!.has(message.senderClientId)) {
            // 首次添加，则将时间戳设置为0，表示第一次收到此clientId设备的消息
            messageTimestampOfSender!.set(message.senderClientId, 0);
        }
        // 取此clientId设备对应的上次消息时间戳，进行判断本次时间戳是否比上一次新，不是则标识为过期消息
        let lastMessageTimestamp: number | undefined = messageTimestampOfSender!.get(message.senderClientId);
        let expired: boolean = (undefined === lastMessageTimestamp) ? true : (message.timestamp <= lastMessageTimestamp);
        // 如果不是过期消息，则刷新时间戳缓存
        if (!expired) {
            lastMessageTimestamp = message.timestamp;
            messageTimestampOfSender!.set(message.senderClientId, lastMessageTimestamp);
            this.messageTimestampOfSenderUnderTopics.set(topic, messageTimestampOfSender!);
        }
        return expired;
    }

    protected getClient(): IMQTTClient | undefined {
        return this.client;
    }

    protected getConfig(): MQTTConfig { 
        return this.config!;
    }

    protected abstract isMessageFromSelf(message: YMSMessage): boolean;

    protected abstract isMessageForMe(message: YMSMessage): boolean;

    protected abstract createMySelfWill(displayCode: number, clientId: string): WillMessage;

    protected abstract subscribeInterestTopic(displayCode: number): void;

    protected abstract unsubscribeAllTopics(displayCode: number): void;

    protected abstract onWillMessageArrived(message: WillMessage): void;

    protected abstract onDisplayReportMessageArrived(message: DisplayReportMessage): void;

    protected abstract onCastSessionMessageArrived(message: CastSessionMessage): void;

    protected abstract onDataMessageArrived(message: DataMessage): void;

    protected abstract onUserHasBeenKickedMessageArrived(message: UserHasBeenKickedMessage): void;

    protected abstract onAccountBindDeviceMessageArrived(message: AccountBindDeviceMessage): void;

    protected abstract onConnectionLost(cause: string): void;

}