import { HTTPConfig } from "../config/HTTPConfig";
import { GlobalCache } from "../core/GlobalCache";
import { HttpRequestExecutor } from "../core/http/HttpRequestExecutor";
import { IHttpRequester } from "../core/http/IHttpRequester";
import { AccountBindDeviceResponse } from "../core/http/response/AccountBindDeviceResponse";
import { CastSessionResponse } from "../core/http/response/CastSessionResponse";
import { Code } from "../core/http/response/Code";
import { ConnectDisplayResponse } from "../core/http/response/ConnectDisplayResponse";
import { EmptyResponse } from "../core/http/response/EmptyResponse";
import { LoginUserResponse } from "../core/http/response/LoginUserResponse";
import { Result } from "../core/http/response/Result";
import { VerifyCodeResponse } from "../core/http/response/VerifyCodeResponse";
import { JsonParser } from "../core/json/JsonParser";
import { MQTTLogic } from "../core/mqtt/MQTTLogic";
import { ControllerMQTTImpl } from "../core/mqtt/impl/ControllerMQTTImpl";
import { IControllerMQTTCallback } from "../core/mqtt/impl/IControllerMQTTCallback";
import { BindDeviceAction } from "../core/mqtt/message/BindDeviceAction";
import { CastSessionMessage } from "../core/mqtt/message/CastSessionMessage";
import { DataMessage } from "../core/mqtt/message/DataMessage";
import { MsgClientType } from "../core/mqtt/message/MsgClientType";
import { SessionAction } from "../core/mqtt/message/SessionAction";
import { CastError } from "../util/CastError";
import { SDKLog } from "../util/SDKLog";
import { AccountBindDeviceVO } from "../vo/account/AccountBindDeviceVO";
import { LoginUserVO } from "../vo/account/LoginUserVO";
import { CastProtocol } from "../vo/cast/CastProtocol";
import { CastSessionVO } from "../vo/cast/CastSessionVO";
import { CastType } from "../vo/cast/CastType";
import { ControllerDeviceType } from "../vo/device/ControllerDeviceType";
import { ControllerDeviceVO } from "../vo/device/ControllerDeviceVO";
import { DevicePair } from "../vo/device/DevicePair";
import { DisplayDeviceVO } from "../vo/device/DisplayDeviceVO";
import { P2PKeyVO } from "../vo/p2p/P2PKeyVO";
import { DisplayReportVO } from "../vo/report/DisplayReportVO";
import { IControllerCallback } from "./IControllerCallback";
import { SessionDestroyReason } from '../core/mqtt/message/SessionDestroyReason';

export class ControllerManager implements IControllerMQTTCallback {
    private readonly TAG: string = "ControllerManager";
    private readonly jsonParser: JsonParser;
    private readonly p2pKeyFromPeer: P2PKeyVO;
    private readonly httpRequestExecutor: HttpRequestExecutor;
    private readonly cache: GlobalCache;
    private guid: string;
    private mqttLogic: MQTTLogic | undefined;
    private callback: IControllerCallback | undefined;
    

    constructor() {
        this.p2pKeyFromPeer = new P2PKeyVO();
        this.cache = new GlobalCache();
        this.httpRequestExecutor = new HttpRequestExecutor();
        this.jsonParser = new JsonParser();
        this.guid = "fake-guid";
    }

    setGuid(guid: string): this {
        this.guid = guid;
        return this;
    }

    setCallback(callback: IControllerCallback): this {
        this.callback = callback;
        return this;
    }

    setHttpRequester(requester: IHttpRequester): this {
        this.httpRequestExecutor.setHttpRequester(requester);
        return this;
    }

    setHttpConfig(config: HTTPConfig): this {
        this.httpRequestExecutor.setHTTPConfig(config);
        return this;
    }

    setMQTTLib(mqtt: MQTTLogic): this {
        this.mqttLogic = mqtt;
        return this;
    }

    createControllerDevice(controllerDevice: ControllerDeviceVO): this {
        this.cache.addControllerDevice(controllerDevice.guid, controllerDevice);
        return this;
    }

    updateControllerType(type: ControllerDeviceType): this {
        let cd = this.cache.getControllerDevice(this.guid);
        cd!.controllerDeviceType = type;
        this.cache.addControllerDevice(this.guid, cd!);
        return this;
    }

    updateNetwork(ip: string, gateway: string, mask: string): this {
        let cd = this.cache.getControllerDevice(this.guid);
        cd!.ip = ip;
        cd!.gateway = gateway;
        cd!.mask = mask;
        this.cache.addControllerDevice(this.guid, cd!);
        return this;
    }

    /**
     * 阻塞方法，控制终端给指定手机号发送注册/登录验证码
     * 请使用Task调用，不需要在Task中进行await，因为函数结果是通过回调触发
     * @param phoneNumber 手机号
     */
    async requestVerifyCode(phoneNumber: string): Promise<boolean> {
        let result: Result<VerifyCodeResponse> = await this.httpRequestExecutor.requireVerifyCode(phoneNumber);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_SMS_REACH_TODAY_COUNT:
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_SEND_SMS_REACH_TODAY_COUNT_FAIL));
                    break;
                case Code.ERR_SMS_INVALID_PHONE_NUMBER | Code.ERR_SMS_OTHER_API_EXCEPTION:
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_SEND_SMS_FAIL));
                    break;
                default:
                    break;
            }
            return false;
        }
        SDKLog.e(this.TAG, "requestVerifyCode: " + result);
        return result.data!.isSMSSuccessSend;
    }

    private subscribeAccountTopic(user: LoginUserVO): void {
        let mqtt: ControllerMQTTImpl = this.mqttLogic as ControllerMQTTImpl;
        mqtt.subscribeAccountTopic(user.account.accountId);
    }

    private unsubscribeAccountTopic(user: LoginUserVO): void {
        let mqtt: ControllerMQTTImpl = this.mqttLogic as ControllerMQTTImpl;
        mqtt.unsubscribeAccountTopic(user.account.accountId);
    }

    /**
     * 阻塞方法，控制终端通过手机号和短信验证码登录账号,如果账号不存在则自动注册
     * 返回值为空意味着登录失败，请检查错误信息
     * @param phoneNumber 手机号
     * @param verifyCode 短信验证码
     * @returns LoginUserVO | null 登录成功后返回用户信息，失败则返回null
     */
    async login(phoneNumber: string, verifyCode: string): Promise<LoginUserVO | null> {
        let temp = this.cache.getControllerDevice(this.guid);
        let result: Result<LoginUserResponse> = await this.httpRequestExecutor.login(phoneNumber,
                    verifyCode, temp!.controllerDeviceType, this.guid, "verifyCode"/*目前先写死走手机验证码*/);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_AUTH_INVALID_VERIFY_CODE :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_VERIFY_CODE_INVALID_FAIL));
                    break;
                default:
                    break;
            }
            return null;
        }
        SDKLog.e(this.TAG, "login: " + result);
        let user: LoginUserVO = result.data!.user;
        this.subscribeAccountTopic(user);
        return user;
    }

    /**
     * 阻塞方法，控制终端登录账号后，将access token存储下来，每次启动如果本地有access token，则调用此方法
     * 此方法如果未正常返回，则说明需要通过UI引导用户重新登录，刷新access token
     * 返回值为空意味着刷新失败，请检查错误信息
     * @param accessToken: 控制端登录账号成功后拿到的唯一令牌
     * @returns LoginUserVO | null 登录成功后返回用户信息，失败则返回null
     */
    async refresh(accessToken: string): Promise<LoginUserVO | null> {
        let result: Result<LoginUserResponse> = await this.httpRequestExecutor.refresh(this.guid, 
                accessToken);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_AUTH_INVALID_TOKEN :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_USER_TOKEN_INVALID_FAIL));
                    break;
                case Code.ERR_USER_LOGIN_ACCOUNT_NOT_EXIST :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_ACCOUNT_NOT_EXIST_FAIL));
                    break;
                default:
                    break;
            }
            return null;
        }
        SDKLog.e(this.TAG, "refresh: " + result);
        let user: LoginUserVO = result.data!.user;
        this.subscribeAccountTopic(user);
        return user;
    }

    /**
     * 阻塞方法，控制终端登录账号后，退出登录此账号
     * @param user: 已登录账号的用户信息
     * @returns boolean 退出成功返回true，否则返回false
     */
    async logout(user: LoginUserVO): Promise<boolean> {
        let result: Result<EmptyResponse> = await this.httpRequestExecutor.logout(user.accessToken);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_AUTH_INVALID_TOKEN :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_USER_TOKEN_INVALID_FAIL));
                    break;
                case Code.ERR_USER_LOGIN_ACCOUNT_NOT_EXIST :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_ACCOUNT_NOT_EXIST_FAIL));
                    break;
                default:
                    break;
            }
            return false;
        }
        SDKLog.e(this.TAG, "logout: " + result);
        this.unsubscribeAccountTopic(user);
        return true;
    }

    /**
     * 阻塞方法，控制终端注销指定账号（已登录的账号）
     * @param user: 已登录账号的用户信息
     * @returns boolean 注销成功返回true，否则返回false
     */
    async unRegister(user: LoginUserVO): Promise<boolean> {
        let result: Result<EmptyResponse> = await this.httpRequestExecutor.unRegister(user.accessToken);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_AUTH_INVALID_TOKEN :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_USER_TOKEN_INVALID_FAIL));
                    break;
                case Code.ERR_USER_LOGIN_ACCOUNT_NOT_EXIST :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_ACCOUNT_NOT_EXIST_FAIL));
                    break;
                default:
                    break;
            }
            return false;
        }
        SDKLog.e(this.TAG, "unRegister: " + result);
        this.unsubscribeAccountTopic(user);
        return true;
    }

    /**
     * 阻塞方法，控制终端登录账号后，获取此账号下所有已绑定的显示设备
     * @param accessToken: 控制端登录账号成功后拿到的唯一令牌
     * @returns AccountBindDeviceVO[] | null 已绑定的显示设备列表，失败则返回null
     */
    async getAllBoundDevices(accessToken: string): Promise<AccountBindDeviceVO[] | null> {
        let result: Result<AccountBindDeviceResponse> = await this.httpRequestExecutor.getAllBoundDevices(accessToken);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_AUTH_INVALID_TOKEN :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_USER_TOKEN_INVALID_FAIL));
                    break;
                default:
                    break;
            }
            return null;
        }
        SDKLog.e(this.TAG, "getAllBoundDevices: " + result);
        return result.data!.boundDisplayDevices;
    }

    /**
     * 阻塞方法，控制终端登录账号后，对指定显示终端与本账号建立绑定关系
     * @param accessToken: 控制端登录账号成功后拿到的唯一令牌
     * @param displayDeviceId 显示终端设备ID
     * @param alias 显示终端设备别名
     * @returns AccountBindDeviceVO | null 绑定的显示设备，失败则返回null
     */
    async bindDisplay(displayDeviceId: string, alias: string, accessToken: string): Promise<AccountBindDeviceVO | null> {
        let result: Result<AccountBindDeviceResponse> = await this.httpRequestExecutor.bindDisplay(displayDeviceId, alias, accessToken);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_AUTH_INVALID_TOKEN :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_USER_TOKEN_INVALID_FAIL));
                    break;
                case Code.ERR_DEVICE_NOT_EXIST :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_BIND_DISPLAY_NOT_EXIST_FAIL));
                    break;
                case Code.ERR_DEVICE_ALREADY_BOUND :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_BIND_DISPLAY_ALREADY_BOUND_FAIL));
                    break;
                default:
                    break;
            }
            return null;
        }
        SDKLog.e(this.TAG, "bindDisplay: " + result);
        for (let device of result.data!.boundDisplayDevices) {
            if (device.displayDevice.displayDeviceId === displayDeviceId) {
                SDKLog.e(this.TAG, "bindDisplay, deivce: " + device);
                return device
            }
        }
        return null;
    }

    /**
     * 阻塞方法，控制终端登录账号后，对指定的已绑定的显示设备进行更新，更新其别名（UI展示名称）
     * @param accessToken: 控制端登录账号成功后拿到的唯一令牌
     * @param alias 显示终端设备别名
     * @param displayDeviceId 显示终端设备ID
     * @returns AccountBindDeviceVO | null 绑定的显示设备，失败则返回null
     */
    async updateBindDisplayAlias(displayDeviceId: string, alias: string, accessToken: string): Promise<AccountBindDeviceVO | null> {
        let result: Result<AccountBindDeviceResponse> = await this.httpRequestExecutor.updateBindDisplayAlias(displayDeviceId, alias, accessToken);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_AUTH_INVALID_TOKEN :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_USER_TOKEN_INVALID_FAIL));
                    break;
                case Code.ERR_DEVICE_NOT_EXIST :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_BIND_DISPLAY_NOT_EXIST_FAIL));
                    break;
                case Code.ERR_DEVICE_NOT_BOUND :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_BIND_DISPLAY_NOT_BOUND_FAIL));
                    break;
                default:
                    break;
            }
            return null;
        }
        SDKLog.e(this.TAG, "updateBindDisplayAlias: " + result);
        for (let device of result.data!.boundDisplayDevices) {
            if (device.displayDevice.displayDeviceId === displayDeviceId) {
                SDKLog.e(this.TAG, "updateBindDisplayAlias, deivce: " + device);
                return device;
            }
        }
        return null;
    }

    /**
     * 阻塞方法，控制终端登录账号后，解除指定显示终端与此账号之间的绑定关系
     * @param accessToken: 控制端登录账号成功后拿到的唯一令牌
     * @param displayDeviceId 显示终端设备ID
     * @returns 解除绑定成功返回true，失败则返回false
     */
    async unBindDisplay(displayDeviceId: string, accessToken: string): Promise<boolean> {
        let result: Result<EmptyResponse> = await this.httpRequestExecutor.unBindDisplay(displayDeviceId, accessToken);
    
        if (Code.OK !== result.code) {
            switch (result.code) {
                case Code.ERR_AUTH_INVALID_TOKEN :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_USER_TOKEN_INVALID_FAIL));
                    break;
                case Code.ERR_DEVICE_NOT_BOUND :
                    this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL, 
                            CastError.SUB_BIND_DISPLAY_NOT_EXIST_FAIL));
                    break;
                default:
                    break;
            }
            return false;
        }
        SDKLog.e(this.TAG, "unBindDisplay: " + result);
        return true;
    }

    async connectDisplay(displayCode: number): Promise<DevicePair | null> {
        let temp = this.cache.getControllerDevice(this.guid);
        let result: Result<ConnectDisplayResponse> = await this.httpRequestExecutor.connectDisplay(displayCode, temp!);
        if (Code.OK !== result.code) {
            this.callback?.onError(result.msg);
            this.callback?.onError(CastError.create(CastError.CONNECT_FAIL, CastError.WAN_CONNECT_FAIL));
            return null;
        }
        let report: DisplayReportVO = DisplayReportVO.copy(result.data!.displayReport);
        this.cache.addConnectedDisplay(this.guid, report.displayDevice.displayCode, report);

        temp!.outIp = result.data!.outIp;
        this.cache.addControllerDevice(this.guid, temp!);

        this.mqttLogic?.connect(report.displayDevice.displayCode);
        this.callback?.onDisplayConnected(report.displayDevice, this.cache.getControllerDevice(this.guid)!);
        //正式服不支持Retained消息，因此SDK在connect后单独触发一下
        this.callback?.onDisplayReport(report);
        return new DevicePair(report, this.cache.getControllerDevice(this.guid)!);
    }

    async disconnectDisplay(displayCode: number): Promise<DisplayDeviceVO | null> {
        let result: Result<EmptyResponse> = await this.httpRequestExecutor.disconnectDisplay(displayCode, 
                this.cache.getControllerDevice(this.guid)!);
    
        if (Code.OK !== result.code) {
            this.callback?.onError(result.msg);
            this.callback?.onError(CastError.create(CastError.DISCONNECT_FAIL, CastError.DISCONNECT_FAIL));
            return null;
        }
        
        await this.mqttLogic?.disconnect(displayCode);

        let r = this.cache.getConnectedDisplayByID(this.guid);
        if (null === r) {
            SDKLog.e(this.TAG, "disconnectDisplay error: " + displayCode + " not in cache");
        } else {
            this.cache.removeConnectedDisplay(this.guid, displayCode);
        }
        this.callback?.onDisplayDisconnected(r!.displayDevice);
        return r!.displayDevice;
    }

    async createCastSession(castType: CastType, protocol: CastProtocol, accessToken?: string): Promise<CastSessionVO | null> {
        let r = this.cache.getConnectedDisplayByID(this.guid);
        if (null === r) {
            SDKLog.e(this.TAG, "createCastSession error: controller not connect to any display");
            return null;
        }
        let result: Result<CastSessionResponse> = await this.httpRequestExecutor.createCastSession(
            r.displayDevice.displayCode, this.guid, castType, protocol, accessToken
        );

        if (Code.OK !== result.code) {
            this.callback?.onError(result.msg);
            this.callback?.onError(CastError.create(CastError.TRANSMIT_FAIL, CastError.WAN_TRANSMIT_SESSION_CREATE_FAIL));
            return null;
        }
        let s: CastSessionVO = new CastSessionVO();
        s.castSessionId = result.data!.castSessionId;;
        s.castType = castType;
        s.castProtocol = protocol;
        s.guid = this.guid;
        s.displayCode = r!.displayDevice.displayCode;
        this.cache.addCastSession(s.castSessionId, s);
        return s;
    }
    
    /**
     * 投屏会话接收端通知发送端自己已经做好准备，发送端可以准备开始往指定的投屏会话推流
     * 会触发发送端的 onReadyToPush 函数
     * @param sessionId 要推流的投屏会话id
     */
    readyToPush(sessionId: number): void {
        let session: CastSessionVO | null = this.cache.getCastSession(sessionId);
        if (null === session) {
            SDKLog.e(this.TAG, "readyToPush: session not found");
            return;
        }
        this.sendCastSessionMessage(session, SessionAction.CONFIG, this.generateMediaPayload(session, ""));
    }

    startPush(sessionId: number, mediaPayload: string): void {
        let session: CastSessionVO | null = this.cache.getCastSession(sessionId);
        if (null === session) {
            SDKLog.e(this.TAG, "startPush: session not found");
            return;
        }
        this.sendCastSessionMessage(session, SessionAction.START, this.generateMediaPayload(session, mediaPayload));
    }

    stopPush(sessionId: number): void {
        let session: CastSessionVO | null = this.cache.getCastSession(sessionId);
        if (null === session) {
            SDKLog.e(this.TAG, "stopPush: session not found");
            return;
        }
        this.sendCastSessionMessage(session, SessionAction.STOP, "");
    }

    private generateMediaPayload(session: CastSessionVO, mediaPayload: string): string {
        let s: string = "";
        if (CastProtocol.P2P === session.castProtocol) {
            s = this.jsonParser.toJson(this.p2pKeyFromPeer);
        } else if (CastProtocol.NRTC === session.castProtocol) {
            s = session.castSessionId + "";
        } else if (CastProtocol.HTTP === session.castProtocol) {
            s = mediaPayload;
        }
        return s;
    }

    private sendCastSessionMessage(session: CastSessionVO, action: SessionAction, mediaPayload: string): void {
        let r = this.cache.getConnectedDisplayByID(this.guid);
        if (null === r) {
            SDKLog.e(this.TAG, "sendCastSessionMessage error: we have not connect display in cache");
            return;
        }
        let message: CastSessionMessage = new CastSessionMessage(MsgClientType.CONTROLLER, MsgClientType.DISPLAY);
        session.controllerDevice = this.cache.getControllerDevice(this.guid!)!;
        message.castSession = session;
        message.action = action;
        message.mediaPayload = mediaPayload;
        this.mqttLogic?.publish(r!.displayDevice.displayCode, message);
    }

    /**
     * 主动销毁投屏会话，携带销毁原因
     * @param sessionId 试图销毁的投屏会话id
     * @param reason 销毁原因，可选
     * @returns 如果会话成功被销毁则返回会话数据类，否则返回null
     */
    async destroyCastSession(sessionId: number, reason?: SessionDestroyReason): Promise<CastSessionVO | null> {
        let result: Result<EmptyResponse> = await this.httpRequestExecutor.destroyCastSession(sessionId, reason?? SessionDestroyReason.CONTROLLER_NORMAL_FINISH);
        if (Code.OK !== result.code) {
            this.callback?.onError(result.msg);
            this.callback?.onError(CastError.create(CastError.TRANSMIT_FAIL, CastError.WAN_TRANSMIT_SESSION_DESTROY_FAIL));
            return null;
        }
        let s: CastSessionVO | null = this.cache.getCastSession(sessionId);
        this.cache.removeCastSession(sessionId);
        return s;
    }

    async destroyAllCastSessions(): Promise<void> {
        let all: IterableIterator<CastSessionVO> = this.cache.castSessionValues();
        for (let session of all) {
            await this.destroyCastSession(session.castSessionId);
        };
    }

    /**
     * 阻塞方法，控制终端主动告知显示端，当前投屏会话出现异常需要处理（停推、停收、销毁）
     * @param sessionId 异常投屏会话id
     * @param error 异常原因
     */
    castSessionError(sessionId: number, error: string | CastError): void {
        let session: CastSessionVO | null = this.cache.getCastSession(sessionId);
        if (null == session) {
            SDKLog.e(this.TAG, "castSessionError: session not found");
            return;
        }
        if (typeof error === 'string') {
            this.sendCastSessionMessage(session, SessionAction.ERROR, error);
        } else {
            this.sendCastSessionMessage(session, SessionAction.ERROR, this.jsonParser.toJson(error));
        }
    }

    sendPrivateData(data: string): void {
        let r = this.cache.getConnectedDisplayByID(this.guid);
        if (null === r) {
            SDKLog.e(this.TAG, "sendPrivateData error: we have not connect display in cache");
            return;
        }
        let message: DataMessage = new DataMessage();
        message.data = data;
        this.mqttLogic?.publish(r!.displayDevice.displayCode, message);
    }

    getP2PKey(): P2PKeyVO | null {
        // 目前的网页端无法使用供应商的p2p sdk，所以网页发起的所有投屏方式都不能走p2p
        return null;
    }

    onServerDied(): void {
        let result: Result<EmptyResponse> = new Result<EmptyResponse>();
        result.code = Code.ERR;
        result.msg = "server died!!";
        this.callback?.onError(result.msg);
        this.callback?.onError(CastError.create(CastError.CONNECT_PEER_OFFLINE, CastError.WAN_CONNECT_SERVER_OFFLINE));
    }

    onDisplayDied(): void {
        SDKLog.i(this.TAG, "onDisplayDied");
        let r = this.cache.getConnectedDisplayByID(this.guid);
        if (null !== r) {
            this.cache.removeOnlineDisplay(r!.displayDevice.displayDeviceId, r!.displayDevice.displayCode);
            this.cache.removeConnectedDisplay(this.guid, r!.displayDevice.displayCode);
            this.callback?.onDisplayOffline(r!.displayDevice);
        } else {
            SDKLog.e(this.TAG, "onDisplayDied error: we have not connected to this display");
        }
    }

    onMySelfDisconnected(cause: string): void {
        this.callback?.onMySelfDisconnected(cause);
    }

    onDisplayReport(report: DisplayReportVO): void {
        SDKLog.i(this.TAG, "onDisplayReport: " + report);
        let r = this.cache.getOnlineDisplayByID(report.displayDevice.displayDeviceId);
        if (null !== r) {
            let prevIsDisplayOnline = r.isDisplayDeviceOnline;

            r = DisplayReportVO.copy(report);
            this.cache.addOnlineDisplay(r.displayDevice.displayDeviceId, r.displayDevice.displayCode, r);
            this.callback?.onDisplayReport(report);

            if (prevIsDisplayOnline && !r.isDisplayDeviceOnline) {
                // 从在线到离线，一定是display主动调用了displayOffline，因此主动触发onDisplayOffline
                SDKLog.d(this.TAG, "onDisplayReport, display device call displayOffline: " + report);
                this.callback?.onDisplayOffline(r.displayDevice);
            }
        } else {
            SDKLog.e(this.TAG, "onDisplayDied error: we have not connected to this display");
        }
    }

    onCastSession(session: CastSessionVO, action: SessionAction, mediaPayload: string, reason?: SessionDestroyReason): void {
        if (this.guid != session.guid) {
            SDKLog.e(this.TAG, "onCastSession error: guid not equal, except: " + this.guid + ", actual: " + session.guid);
            return;
        }
        if (null === this.cache.getConnectedDisplayByID(this.guid)) {
            SDKLog.e(this.TAG, "onCastSession error: we have not connected to any display");
            return;
        }
        if (this.cache.getConnectedDisplayByID(this.guid)!.displayDevice.displayCode
                != session.displayCode) {
            SDKLog.e(this.TAG, "onCastSession error: session display not equal connected display");
            return;
        }
        switch (action) {
            case SessionAction.CREATE:
                this.cache.addCastSession(session.castSessionId, session);
                this.callback?.onCastSessionCreate(session);
                break;
            case SessionAction.CONFIG:
                // 目前的网页端无法使用供应商的p2p sdk，所以网页发起的所有投屏方式都不能走p2p
                // 也就不需要做任何P2PKEY解析处理
                switch (session.castType) {
                    case CastType.MIX_CAST:
                    case CastType.DOCUMENT:
                        this.callback?.onReadyToPush(session, mediaPayload);
                        break;
                }
                break;
            case SessionAction.START:
                // 目前的网页端无法使用供应商的p2p sdk，所以网页发起的所有投屏方式都不能走p2p
                // 也就不需要做任何P2PKEY解析处理
                this.callback?.onCastSessionPushStart(session, mediaPayload);
                break;
            case SessionAction.STOP:
                this.callback?.onCastSessionPushEnd(session);
                break;
            case SessionAction.DESTROY:
                this.cache.removeCastSession(session.castSessionId);
                this.callback?.onCastSessionDestroy(session, reason?? SessionDestroyReason.NONE);
                break;
            case SessionAction.ERROR:
                try {
                    let tmp: any = this.jsonParser.fromJson(mediaPayload);
                    if (this.isValidMyDataType(tmp)) {
                        this.callback?.onError(CastError.create(tmp.main, tmp.sub));
                    } else {
                        this.callback?.onError(mediaPayload);
                    } 
                } catch (error) {
                    SDKLog.e(this.TAG, `Failed to parse JSON: ${error}`);
                    this.callback?.onError(mediaPayload);
                }
                break;
        }
    }

    private readonly isValidMyDataType = (data: any): data is CastError => {
        return (
            typeof data === "object" &&
            data !== null &&
            typeof data.main === "number" &&
            typeof data.sub === "number"
        );
    };

    onUserHasBeenKicked(user: LoginUserVO): void {
        SDKLog.i(this.TAG, "onUserHasBeenKicked: " + user);
        this.callback?.onUserHasBeenKicked(user);
    }

    onAccountBindDevice(accountBindDevice: AccountBindDeviceVO, action: BindDeviceAction): void {
        SDKLog.i(this.TAG, "onAccountBindDevice: " + accountBindDevice + ", action: " + action);
        if (BindDeviceAction.DEVICE_ONLINE == action) {
            this.callback?.onAccountBindDeviceOnline(accountBindDevice);
        } else if (BindDeviceAction.DEVICE_OFFLINE == action) {
            this.callback?.onAccountBindDeviceOffline(accountBindDevice);
        }
    }

}