"use strict";
import {Logger} from "../common/Logger";
import {ErrorManager} from "../common/ErrorManager";
import * as PubSub from "pubsub-js";
import {PresenceEventHandler} from "../connection/XMPPServiceHandler/presenceEventHandler";
import {getJsonFromXML, isDefined, isStarted, logEntryExit, until} from "../common/Utils";
import {SettingsService} from "./SettingsService";
import {EventEmitter} from "events";
import {ROOMROLE} from "./S2SService";
import {Core} from "../Core";
import {BubblesService} from "./BubblesService";
import {PresenceLevel, PresenceRainbow, PresenceStatus} from "../common/models/PresenceRainbow";
import {GenericService} from "./GenericService";
import {interval} from "rxjs";
import {Bubble, getBubbleLogInfos} from "../common/models/Bubble";
import {ConversationEventHandler} from "../connection/XMPPServiceHandler/conversationEventHandler.js";
import {ConversationHistoryHandler} from "../connection/XMPPServiceHandler/conversationHistoryHandler.js";
import {CalendarManager} from "../common/CalendarManager.js";
import {AutoReplyManager} from "../common/AutoReplyManager.js";

export {};

const LOG_ID = "PRES/SVCE - ";
const API_ID = "API_CALL - ";

@logEntryExit(LOG_ID)
@isStarted([])
/**
 * @module
 * @name PresenceService
 * @version SDKVERSION
 * @public
 * @description
 *      This module manages the presence of the connected user. <br>
 *      <br><br>
 *      The main methods proposed in that module allow to: <br>
 *      - Change the connected user presence <br>
 */
class PresenceService extends GenericService{
    private _settings: SettingsService;
    private _presenceEventHandler: PresenceEventHandler;
    private _presenceHandlerToken: any;
    private manualState: boolean;
    private _currentPresence: PresenceRainbow;
    public RAINBOW_PRESENCE_ONLINE: PresenceLevel.Online;
    public RAINBOW_PRESENCE_DONOTDISTURB: PresenceLevel.Dnd;
    public RAINBOW_PRESENCE_AWAY: PresenceLevel.Away;
    public RAINBOW_PRESENCE_INVISIBLE: PresenceLevel.Invisible;
    private _bubbles: BubblesService;

    static getClassName(){ return 'PresenceService'; }
    getClassName(){ return PresenceService.getClassName(); }

    static getAccessorName(){ return 'presence'; }
    getAccessorName(){ return PresenceService.getAccessorName(); }

    constructor(_core:Core,_eventEmitter : EventEmitter, _logger : Logger, _startConfig: {
        start_up:boolean,
        optional:boolean
    }) {
        super(_logger, LOG_ID, _eventEmitter);
        this.setLogLevels(this);
        let that = this;
        this._startConfig = _startConfig;

        this._xmpp = null;
        this._rest = null;
        that._s2s = null;
        this._options = {};
        this._useXMPP = false;
        this._useS2S = false;
        that._eventEmitter = _eventEmitter;
        that._logger = _logger;

        that._core = _core;

        that.manualState = false;
        that._currentPresence = new PresenceRainbow(PresenceLevel.Online);
        that.RAINBOW_PRESENCE_ONLINE = PresenceLevel.Online;
        that.RAINBOW_PRESENCE_DONOTDISTURB = PresenceLevel.Dnd;
        that.RAINBOW_PRESENCE_AWAY = PresenceLevel.Away;
        that.RAINBOW_PRESENCE_INVISIBLE = PresenceLevel.Invisible;


        that._eventEmitter.on("evt_internal_usersettingschanged", that._onUserSettingsChanged.bind(that));
        that._eventEmitter.on("evt_internal_mypresencechanged", that._onMyPresenceChanged.bind(that));
        that._eventEmitter.on("evt_internal_EWSgeteventsreceived", that._onEWSgeteventsReceived.bind(that));
        that._eventEmitter.on("evt_internal_EWSgetautoreplyreceived", that._onEWSgetautoreplyReceived.bind(that));
    }
    start(_options ) { // , _xmpp : XMPPService, _s2s: S2SService, _rest : RESTService, _settings : SettingsService
        let that = this;
        that.initStartDate();
        return new Promise(function(resolve, reject) {
            try {
                that._options = _options;
                that._xmpp = that._core._xmpp;
                that._rest = that._core._rest;
                that._s2s = that._core._s2s;
                that._settings = that._core.settings;
                that._useXMPP = that._options.useXMPP;
                that._useS2S = that._options.useS2S;
                that._bubbles = that._core.bubbles;


                that.attachHandlers();

/*
                that._eventEmitter.removeListener("evt_internal_usersettingschanged", that._onUserSettingsChanged.bind(that));
                that._eventEmitter.removeListener("evt_internal_presencechanged", that._onPresenceChanged.bind(that));

                that._eventEmitter.on("evt_internal_usersettingschanged", that._onUserSettingsChanged.bind(that));
                that._eventEmitter.on("evt_internal_presencechanged", that._onPresenceChanged.bind(that));
*/
                that.setStarted();
                resolve(undefined);
            } catch (err) {
                that._logger.log(that.ERROR, LOG_ID + "(start) Catch Error !!!");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(start) Catch Error !!! : ", err);
                return reject();
            }
        });
    }

    stop() {
        let that = this;
        return new Promise(function(resolve, reject) {
            try {
                delete that._presenceEventHandler;
                that._presenceEventHandler = null;
                PubSub.unsubscribe(that._presenceHandlerToken);

                that._xmpp = null;
/*
                that._eventEmitter.removeListener("evt_internal_usersettingschanged", that._onUserSettingsChanged.bind(that));
                that._eventEmitter.removeListener("evt_internal_presencechanged", that._onPresenceChanged.bind(that));
*/
                that.setStopped ();
                resolve(undefined);
            } catch (err) {
                return reject();
            }
        });
    }

    async init (useRestAtStartup : boolean) {
        let that = this;
        that.setInitialized();
    }

    attachHandlers() {
        let that = this;
        that._presenceEventHandler = new PresenceEventHandler(that._xmpp, that._core._contacts);
        that._presenceHandlerToken = [
            PubSub.subscribe( that._xmpp.hash + "." + that._presenceEventHandler.PRESENCE, that._presenceEventHandler.onPresenceReceived.bind(that._presenceEventHandler) ),
            PubSub.subscribe( that._xmpp.hash + "." + that._presenceEventHandler.IQ_SET, that._presenceEventHandler.onIqGetSetReceived.bind(that._presenceEventHandler) ),
            PubSub.subscribe( that._xmpp.hash + "." + that._presenceEventHandler.IQ_GET, that._presenceEventHandler.onIqGetSetReceived.bind(that._presenceEventHandler) )
        ];

    }

    //region Presence CONNECTED USER

    /**
     * @private
     * @method sendInitialPresence
     * @instance
     * @async
     * @category Presence CONNECTED USER
     * @description
     *  Send the initial presence (online) <br>
     * @return {Promise<ErrorManager.Ok>} A promise containing the result
     */
    async sendInitialPresence() {
        let that = this;
        return new Promise((resolve) => {
            that._eventEmitter.once("evt_internal_presencechanged", function fn_onpresencechanged(presence) {
                that._logger.log(that.INFO, LOG_ID + "(sendInitialPresence) received.");
                that._logger.log(that.INTERNAL, LOG_ID + "(sendInitialPresence) received : ", presence);
                that._eventEmitter.removeListener("evt_internal_presencechanged", fn_onpresencechanged);
                resolve(ErrorManager.getErrorManager().OK);
            });
            let presenceRainbow = new PresenceRainbow();
            that._logger.log(that.INTERNAL, LOG_ID + "(sendInitialPresence) presenceRainbow : ", presenceRainbow);
            that._xmpp.setPresence(presenceRainbow.presenceShow, presenceRainbow.presenceStatus);
            //that._xmpp.setPresence("online", "");
        });
    }

    /**
     * @public
     * @nodered true
     * @method setPresenceTo
     * @instance
     * @async
     * @category Presence CONNECTED USER
     * @description
     *    Allow to change the presence of the connected user <br>
     *    Only the following values are authorized: 'dnd', 'away', 'invisible' or 'online' <br>
     * @param {String} presence The presence value to set i.e: 'dnd', 'away', 'invisible' ('xa' on server side) or 'online'
     * @return {Promise<any, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result (ErrorManager.getErrorManager().OK in case of success)
     
     */
    async setPresenceTo(presence : PresenceLevel) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(setPresenceTo) is presence defined : ", isDefined(presence));
        let presenceRainbow = new PresenceRainbow(presence);
       // let show = "online";
        //let status = "";
        return new Promise(async (resolve, reject) => {

            /* switch (presence) {
                case "online":
                    //show = "online";
                    //status = "";
                    show = undefined;
                    status = "mode=auto";
                    break;
                case "away":
                    show = "xa";
                    status = "away";
                    break;
                case "dnd":
                    show = "dnd";
                    status = "";
                    break;
                case "invisible":
                    show = "xa";
                    status = "";
                    break;
                default:
                    that._logger.log(that.WARN, LOG_ID + "(setPresenceTo) Bad or empty 'presence' parameter");
                    that._logger.log(that.INTERNALERROR, LOG_ID + "(setPresenceTo) Bad or empty 'presence' parameter : ", presence);
                    return reject(ErrorManager.getErrorManager().BAD_REQUEST);
                    break;
            } // */

            that._eventEmitter.once("evt_internal_presencechanged", function fn_onpresencechanged(_presence) {
                that._logger.log(that.INFO, LOG_ID + "(setPresenceTo) received.");
                that._logger.log(that.INTERNAL, LOG_ID + "(setPresenceTo) received : ", _presence);
                that._eventEmitter.removeListener("evt_internal_presencechanged", fn_onpresencechanged);
                resolve(ErrorManager.getErrorManager().OK);
            });

            that._logger.log(that.INTERNAL, LOG_ID + "(setPresenceTo) that._useXMPP : ", that._useXMPP, ", that._useS2S : ", that._useS2S, ", presenceRainbow : ", presenceRainbow, ", presence : ", presence);

            if (that._useXMPP) {
                that._xmpp.setPresence(presenceRainbow.presenceShow, presenceRainbow.presenceStatus);
            }
            if (that._useS2S) {
                await that._s2s.sendS2SPresence(presenceRainbow.toJsonForServer());
            }
            await that._settings.updateUserSettings({presence: presenceRainbow.presenceLevel});
        });
    }
    
    /**
     * @public
     * @nodered true
     * @method getUserConnectedPresence
     * @instance
     * @category Presence CONNECTED USER
     * @description
     *      Get user presence status calculated from events. <br>
     */
    getUserConnectedPresence() : PresenceRainbow {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getUserConnectedPresence) .");
        return this._currentPresence ;
    }
   
     /**
     * @private
     * @method _setUserPresenceStatus
     * @instance
     * @async
     * @category Presence CONNECTED USER
     * @description
     *      Send user presence status and message to xmpp. <br>
     */
    async _setUserPresenceStatus( presenceRainbow : PresenceRainbow) {
         let that = this;
         return new Promise(async (resolve, reject) => {

             if (that._useXMPP) {
                 that._eventEmitter.once("evt_internal_presencechanged", function fn_onpresencechanged(presence) {
                     that._logger.log(that.INFO, LOG_ID + "(_setUserPresenceStatus) received.");
                     that._logger.log(that.INTERNAL, LOG_ID + "(_setUserPresenceStatus) received : ", presence);
                     that._eventEmitter.removeListener("evt_internal_presencechanged", fn_onpresencechanged);
                     resolve(undefined);
                 });
                 await that._xmpp.setPresence(presenceRainbow.presenceShow, presenceRainbow.presenceStatus);
             }
             if (that._useS2S) {
                 let presenceChangedReceived = false;
                 that._eventEmitter.once("evt_internal_presencechanged", function fn_onpresencechanged(presence) {
                     that._logger.log(that.INFO, LOG_ID + "(_setUserPresenceStatus) received.");
                     that._logger.log(that.INTERNAL, LOG_ID + "(_setUserPresenceStatus) received : ", presence);
                     that._eventEmitter.removeListener("evt_internal_presencechanged", fn_onpresencechanged);
                     presenceChangedReceived = true;
                     //resolve(undefined);
                 });
                 let result = await that._s2s.sendS2SPresence(presenceRainbow.toJsonForServer());
                 that._logger.log(that.INTERNAL, LOG_ID + "(_setUserPresenceStatus) sendS2SPresence result : ", result);
                 until(() => { return (presenceChangedReceived === true); }, "Wait for presencechanged after set presence S2S.", 10000).then((untilResult)=>{
                     that._logger.log(that.INTERNAL, LOG_ID + "(_setUserPresenceStatus) evt_internal_presencechanged received, can continue : ", untilResult);
                     resolve(undefined);
                 }).catch((err)=>{
                     that._logger.log(that.WARN, LOG_ID + "(_setUserPresenceStatus) evt_internal_presencechanged NOT received, force continue : ", err);
                     resolve(undefined);
                 })
             }
         });
     }

    /**
     * @private
     * @method _sendPresenceFromConfiguration
     * @instance
     * @async
     * @category Presence CONNECTED USER
     * @description
     *      Send user presence according to user settings presence. <br>
     */
    async _sendPresenceFromConfiguration(useRestAtStartup) {
        let that = this;
        return new Promise( (resolve, reject) => {
            let presenceRainbow : PresenceRainbow = new PresenceRainbow();
            if (useRestAtStartup) {
                that._settings.getUserSettings().then(function (settings: any) {
                    //let message = "";
                    presenceRainbow.presenceLevel = settings.presence;
                    /*if (presence === "invisible") {
                        presence = "xa";
                    } else if (presence === "away") {
                        presence = "xa";
                        message = "away";
                    } // */

                    that._logger.log(that.INTERNAL, LOG_ID + "(_sendPresenceFromConfiguration) -> getUserSettings are ", presenceRainbow);
                    //if (that._currentPresence && (that._currentPresence !== presence || (that._currentPresence. === "xa" && message !== that._currentPresence.status))) {
                    //if (that._currentPresence && (that._currentPresence.presenceLevel !== presenceRainbow.presenceLevel )) {
                    if (that._currentPresence) {
                        that._logger.log(that.INTERNAL, LOG_ID + "(_sendPresenceFromConfiguration) should update my status from ", that._currentPresence, " to ", presenceRainbow);
                        that._setUserPresenceStatus(presenceRainbow).then(() => {
                            resolve(undefined);
                        }).catch((err) => {
                            reject(err);
                        });
                    } else {
                        resolve(undefined);
                    }
                })
                    .catch(function (error) {
                        that._logger.log(that.DEBUG, LOG_ID + "(_sendPresenceFromConfiguration) failure, send online");
                        that._setUserPresenceStatus(new PresenceRainbow()).then(() => {
                            resolve(undefined);
                        }).catch(() => {
                            reject(error);
                        });
                    });
            } else {
                presenceRainbow.presenceLevel = PresenceLevel.Online;
                presenceRainbow.presenceStatus = PresenceStatus.ModeAuto;
                that._logger.log(that.INFO, LOG_ID + "(_sendPresenceFromConfiguration) -> Will send presence : ", presenceRainbow);
                that._setUserPresenceStatus(presenceRainbow).then(() => {
                    resolve(undefined);
                }).catch((err) => {
                    reject(err);
                });
            }
        });
    }

    /**
     * @public
     * @nodered true
     * @method getMyPresenceInformation
     * @since 2.16.0 
     * @instance
     * @category Presence CONNECTED USER
     * @description
     *      Get user's resources presences information from server. <br>
     */
    getMyPresenceInformation() {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getMyPresenceInformation) .");

        return new Promise(async (resolve, reject) => {
            try {
                let result = await that._rest.getMyPresenceInformation();
                that._logger.log(that.DEBUG, "(getMyPresenceInformation) - sent.");
                that._logger.log(that.INTERNAL, "(getMyPresenceInformation) - result : ", result);

                resolve (result);
            } catch (err) {
                that._logger.log(that.ERROR, LOG_ID + "(getMyPresenceInformation) Error.");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(getMyPresenceInformation) Error : ", err);
                return reject(err);
            }
        });
    }

    /**
     * @public
     * @nodered true
     * @method setApplyMsTeamsPresenceSettings
     * @instance
     * @async
     * @category Presence CONNECTED USER
     * @description
     *    Allow to activate the exchange of presence of the connected user between rainbow and MS Teams on UI side.<br>
     * @param {boolean} connectTeams=false The boolean to activate or not the feature.
     * @return {Promise<any>}
     */
    async setApplyMsTeamsPresenceSettings(connectTeams : boolean= false) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(setApplyMsTeamsPresenceSettings) is connectTeams defined : ", isDefined(connectTeams));

        return new Promise(async (resolve, reject) => {
            that._logger.log(that.INTERNAL, LOG_ID + "(setApplyMsTeamsPresenceSettings) connectTeams : ", connectTeams);

            resolve( that._settings.updateUserSettings({applyMsTeamsPresence: connectTeams}));
        });
    }
    
    //endregion Presence CONNECTED USER

    //region Presence Bubbles

    /**
     * @private
     * @method sendInitialAllBubblePresence
     * @instance
     * @async
     * @category Presence Bubbles
     * @param {boolean} webinar=false should option webinar be setted.
     * @param {boolean} acknowledge=true should option acknowledge should be setted.
     * @description
     *      Method called to sen presence to all bubbles <br>
     */
    async sendInitialAllBubblePresence(webinar : boolean = false, acknowledge : boolean = true): Promise<any> {
        let that = this;
        that._logger.log(that.INFO, LOG_ID + API_ID + "(sendInitialAllBubblePresence) -- entry -- ");

        return new Promise(async function (resolve, reject) {
            if (that._useXMPP) {
                let result = that._xmpp.sendInitialAllBubblePresence(webinar, acknowledge);
                that._logger.log(that.DEBUG, LOG_ID + "(sendInitialAllBubblePresence) sent.");
                resolve(result);
            }
        });
    }

    /**
     * @private
     * @method sendInitialBubblePresenceSync
     * @instance
     * @async
     * @category Presence Bubbles
     * @param {Bubble} bubble The Bubble
     * @param {number} intervalDelay=7500 The interval between sending presence to a Bubble while it failed. default value is 75000 ms.
     * @description
     *      Method called when receiving an invitation to join a bubble <br>
     */
    async sendInitialBubblePresenceSync(bubble: Bubble, intervalDelay: number = 7500): Promise<any> {
        let that = this;
        that._logger.log(that.INFO, LOG_ID + API_ID + "(sendInitialBubblePresenceSync) is bubble defined : ", isDefined(bubble));

        return new Promise(async function (resolve, reject) {
            let initialPresenceSent = that.sendInitialBubblePresenceSyncFn(bubble, intervalDelay).catch((errOfSent) => {
                that._logger.log(that.WARN, LOG_ID + "(sendInitialBubblePresenceSync) Error while sendInitialBubblePresenceSyncFn : ", errOfSent);
                return errOfSent;
            });
            if (initialPresenceSent) {
                that._logger.log(that.INTERNAL, LOG_ID + "(sendInitialBubblePresenceSync) initialPresenceSent initialized.");
                resolve (initialPresenceSent);
            } else {
                let err = {
                    label : "no initial bubble presence promise."
                };
                that._logger.log(that.INTERNAL, LOG_ID + "(sendInitialBubblePresenceSync) initialPresenceSent is undefined, err : ", err);
                reject(err);
            }
        });
    }

    /**
     * @private
     * @method sendInitialBubblePresenceById
     * @instance
     * @async
     * @category Presence Bubbles
     * @param {string} id The Bubble id.
     * @param {number} attempt=0 To log a number of attempt of sending presence to the Bubble. default value is 0.
     * @description
     *      Method called when receiving an invitation to join a bubble <br>
     */
    async sendInitialBubblePresenceById(id: string,  attempt : number = 0) {
        let bubble:any = {id};
        return this.sendInitialBubblePresence(bubble,attempt);
    }
    

    /**
     * @private
     * @method sendInitialBubblePresence
     * @instance
     * @async
     * @category Presence Bubbles
     * @param {Bubble} bubble The Bubble
     * @param {number} attempt=0 To log a number of attempt of sending presence to the Bubble. default value is 0.
     * @description
     *      Method called when receiving an invitation to join a bubble <br>
    */
    async sendInitialBubblePresence(bubble : Bubble,  attempt : number = 0) {
            let that = this;
            that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(sendInitialBubblePresence) is bubble : ", getBubbleLogInfos(bubble));

            return new Promise(async function (resolve, reject) {
                if (!bubble || !bubble.jid) {
                    that._logger.log(that.DEBUG, LOG_ID + "(sendInitialBubblePresence) failed");
                    that._logger.log(that.INFO, LOG_ID + "(sendInitialBubblePresence) No Bubble id provided");
                    reject({code: -1, label: "Bubble id is not defined!!!"});
                } else {
                    const attemptInfo = attempt ? " -- attempt " + attempt:"";
                    that._logger.log(that.INFO, LOG_ID + "(sendInitialBubblePresence) " + attemptInfo + " -- " + getBubbleLogInfos(bubble));
                    if (that._useXMPP) {
                        let result = that._xmpp.sendInitialBubblePresence(bubble.jid)
                        that._logger.log(that.INTERNAL, LOG_ID + "(sendInitialBubblePresence) begin wait for the bubble to be active : ", getBubbleLogInfos(bubble));
                        // Wait for the bubble to be active
                        await until(() => {
                            return bubble.isActive===true;
                        }, "Wait for the Bubble " + bubble.jid + " to be active").catch((err) => {
                            that._logger.log(that.INTERNAL, LOG_ID + "(sendInitialBubblePresence) FAILED wait for the bubble to be active : ", getBubbleLogInfos(bubble), " : ", err);
                        });
                        that._logger.log(that.INTERNAL, LOG_ID + "(sendInitialBubblePresence) end wait for the bubble to be active : ", getBubbleLogInfos(bubble));
                        resolve(result);
                    }
                    if (that._useS2S) {
                        let bubbleInfos = await that._bubbles.getBubbleByJid(bubble.jid);
                        resolve(that._s2s.joinRoom(bubbleInfos.id, ROOMROLE.MEMBER));
                    }
                }
            });
        }

    /**
     * @private
     * @method sendInitialBubblePresenceSyncFn
     * @instance
     * @async
     * @category Presence Bubbles
     * @param {Bubble} bubble The Bubble
     * @param {number} intervalDelay=7500 The interval between sending presence to a Bubble while it failed. default value is 75000 ms.
     * @description
     *      Method called when receiving an invitation to join a bubble <br>
     */
    public sendInitialBubblePresenceSyncFn(bubble: Bubble, intervalDelay: number = 7500): Promise<any> {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(sendInitialBubblePresenceSyncFn) is bubble : ", getBubbleLogInfos(bubble));

        if (!bubble) {
            that._logger.log(that.WARN, LOG_ID + "(sendInitialBubblePresenceSyncFn) bad or empty 'bubble' parameter.");
            //that._logger.log(that.INTERNALERROR, LOG_ID + "(sendInitialBubblePresenceSyncFn) bad or empty 'bubble' parameter : ", bubble);
            return Promise.reject(ErrorManager.getErrorManager().BAD_REQUEST);
        }
        that._logger.log(that.INFO, LOG_ID + "(sendInitialBubblePresenceSyncFn) " + intervalDelay + " -- " + getBubbleLogInfos(bubble));
        if (bubble.initialPresence.initPresencePromise) {
            that._logger.log(that.DEBUG, LOG_ID + "(sendInitialBubblePresenceSyncFn) -- " + getBubbleLogInfos(bubble) + " -- bubble activation in progress.");
            return bubble.initialPresence.initPresencePromise;
        }
        if (bubble.initialPresence.initPresenceAck) {
            that._logger.log(that.DEBUG, LOG_ID + "(sendInitialBubblePresenceSyncFn) -- " + getBubbleLogInfos(bubble) + " -- bubble already activated.");
            return Promise.resolve();
        }
//                    bubble.initialPresence.initPresencePromise()
        bubble.initialPresence.initPresencePromise = new Promise((resolve, reject) => {
            bubble.initialPresence.initPresencePromiseResolve = resolve;
            //that.sendInitialRoomPresence(bubble, 0);
            that.sendInitialBubblePresence(bubble);

            // Retry mechanism
            const maxAttemptNumber = 3;
            let attemptNumber = 0;

            bubble.initialPresence.initPresenceInterval = interval(intervalDelay).subscribe(() => {
                if (attemptNumber < maxAttemptNumber) {
                    // up to <maxAttemptNumber> retries
                    attemptNumber += 1;
                    that._logger.log(that.WARN, LOG_ID + "(sendInitialBubblePresenceSyncFn) : re sendInitialBubblePresence, attemptNumber : " + attemptNumber + " retries for " + getBubbleLogInfos(bubble) + ", bubble.initialPresence : ", bubble.initialPresence);
                    that.sendInitialBubblePresence(bubble, attemptNumber);
                } else {
                    // if no response after <maxAttemptNumber> retries, we clean the presence promise in the bubble 
                    // (to make it possible for further trials to re-establish presence state and chat history access)
                    that._logger.log(that.WARN, LOG_ID + "(sendInitialBubblePresenceSyncFn) : no response after " + attemptNumber + " retries => clean presence promise and interval for " + getBubbleLogInfos(bubble));
                    reject("(sendInitialBubblePresenceSyncFn) : no response");
                    bubble.initialPresence.initPresencePromise = null;
                    if (bubble.initialPresence.initPresenceInterval) {
                        bubble.initialPresence.initPresenceInterval.unsubscribe();
                        bubble.initialPresence.initPresenceInterval = null;
                    }

                    //refresh the bubble from the local bubble cache
                    //bubble = this.bubbles[bubble.dbId];
                    //should sent an update, so that the conversation is updated and we can sent chat messages inside
                    //that.eventService.publish(this.ROOM_UPDATE_EVENT, bubble);
                    that._eventEmitter.emit("evt_internal_bubblepresencesent", bubble);
                }
            });
        });
        return bubble.initialPresence.initPresencePromise;
    }

    //endregion Presence Bubbles

    //region Events
    
    /**
     * @private
     * @method _onUserSettingsChanged
     * @instance
     * @description
     *      Method called when receiving an update on user settings <br>
     */
    _onUserSettingsChanged() {
        let that = this;
        that._sendPresenceFromConfiguration(that._options._restOptions.useRestAtStartup).catch(err=>{
            that._logger.log(that.WARN, LOG_ID + "(_onUserSettingsChanged) _sendPresenceFromConfiguration error : ", err);
        });
    }

    /**
     * @private
     * @method _onPresenceChanged
     * @instance
     * @description
     *      Method called when receiving an update on user presence <br>
     */
    _onMyPresenceChanged(user) {
        let that = this;
        //that._logger.log(that.INTERNAL, LOG_ID + "(_onPresenceChanged) user : ", user);
       // if ( presence.jid === that._xmpp.jid ) {
            that._logger.log(that.INTERNAL, LOG_ID + "(_onPresenceChanged) set for connected user the presence : ", user);
            that._currentPresence.presenceLevel = user.presence;
            //that._currentPresence.presenceShow = presence.show;
            that._currentPresence.presenceStatus = user.status;

        //}
    }

    _onEWSgeteventsReceived (data) {
        let that = this;
        that._logger.log(that.INTERNAL, "(_onEWSgeteventsReceived) - data : ", data);
        that._eventEmitter.emit("evt_internal_EWSgetevents", data);
    }

    _onEWSgetautoreplyReceived (data) {
        let that = this;
        that._logger.log(that.INTERNAL, "(_onEWSgetautoreplyReceived) - data : ", data);
        that._eventEmitter.emit("evt_internal_EWSgetautoreply", data);
    }

    //endregion Events

    //region Presence CALENDAR

    /**
     * @public
     * @nodered true
     * @method getCalendarState
     * @instance
     * @category Presence CALENDAR
     * @description
     *    Allow to get the calendar presence of the connected user <br>
     *    return promise with {  <br>
     *    busy: boolean, // Does the connected user is busy ? <br>
     *    status: string, // The status of the connected user (one of "free", "busy" or "out_of_office") <br> 
     *    subject: string, // The meeting subject. <br>
     *    since: string, // The meeting since date. <br>
     *    until: string // Date until the current presence is valid <br> 
     *    }  <br>
     *    <br>
     *        Note : "applyCalendarPresence" boolean is not return by this api, so you can not know if the presence should be used in general calculated presence. </br>
     * @async
     * @return {Promise<{  
     *    busy: boolean, 
     *    status: string,  
     *    subject: string, 
     *    since: string, 
     *    until: string  
     *    }, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     
     */
    async getCalendarState() {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getCalendarState) .");

        return new Promise((resolve, reject) => {

            that._rest.getCalendarState().then((calendarInfo : any) => {
                that._logger.log(that.INFO, LOG_ID + "(getCalendarState) calendarInfo : ", calendarInfo);
                //let presenceCalendar = new PresenceCalendar();
                resolve(calendarInfo);
            }).catch((err) => {
                that._logger.log(that.ERROR, LOG_ID + "(getCalendarState) error");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(getCalendarState) error : ", err);
                let error : any = ErrorManager.getErrorManager().OTHERERROR;
                error.label = "Catch Error while trying to get calendar presence.";
                error.msg = err.message;
                return reject(error);
                //return reject(err);
            });
        });
    }
    
    /**
     * @public
     * @nodered true
     * @method getCalendarStates
     * @instance
     * @category Presence CALENDAR
     * @param {Array<string>} users The list of the Rainbow user's references - id or logins (Contact::loginEmail) - to retrieve the calendar presence.
     * @description
     *    Allow to get the calendar presence of severals users <br>
     *    return promise with {  
     *    usersIdentifier : { // List of calendar user states. <br>
     *    busy: boolean, // Does the connected user is busy ? <br>
     *    status: string, // The status of the connected user (one of "free", "busy" or "out_of_office") <br> 
     *    subject: string, // The meeting subject. <br>
     *    since: string, // The meeting since date. <br>
     *    until: string // Date until the current presence is valid <br> 
     *    }  <br>
     *    <br>
     *        Note : "applyCalendarPresence" boolean is not return by this api, so you can not know if the presence should be used in general calculated presence. </br>
     * @async
     * @return {Promise< { 
     *    busy: boolean, 
     *    status: string, 
     *    subject: string,
     *    since: string,
     *    until: string 
     *    }, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.     
     */
    async getCalendarStates(users : Array<string> = [undefined]) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getCalendarStates) is users defined : ", isDefined(users));

        return new Promise((resolve, reject) => {

            that._rest.getCalendarStates(users).then((calendarInfo : any) => {
                that._logger.log(that.INFO, LOG_ID + "(getCalendarStates) calendarInfo : ", calendarInfo);
                resolve(calendarInfo);
            }).catch((err) => {
                that._logger.log(that.ERROR, LOG_ID + "(getCalendarStates) error");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(getCalendarStates) error : ", err);
                let error : any = ErrorManager.getErrorManager().OTHERERROR;
                error.label = "Catch Error while trying to get calendar presence.";
                error.msg = err.message;
                return reject(error);
                //return reject(err);
            });
        });
    }

    /**
     * @public
     * @nodered true
     * @method setCalendarRegister
     * @instance
     * @category Presence CALENDAR
     * @param {string} type Calendar type. Default : office365, Authorized values : office365, google
     * @param {boolean} redirect Immediately redirect to login page (OAuth2) or generate an HTML page. Default : false.
     * @param {string} callback Redirect URL to the requesting client.
     * @description
     *    Register a new calendar.<br>
     *    return promise with {  
     *    "url" : string // Calendar provider's OAuth URL <br>
     *    } <br>
     * @async
     * @return {Promise<{  
     *    "url" : string
     *    }, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    async setCalendarRegister(type? : string, redirect? : boolean, callbackUrl? : string) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(setCalendarRegister) is type defined : ", isDefined(type));

        return new Promise((resolve, reject) => {

            that._rest.setCalendarRegister(type,  redirect, callbackUrl).then((calendarInfo : any) => {
                that._logger.log(that.INFO, LOG_ID + "(setCalendarRegister) calendarInfo : ", calendarInfo);
                resolve(calendarInfo);
            }).catch((err) => {
                that._logger.log(that.ERROR, LOG_ID + "(setCalendarRegister) error");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(setCalendarRegister) error : ", err);
                let error : any = ErrorManager.getErrorManager().OTHERERROR;
                error.label = "Catch Error while trying to set calendar register.";
                error.msg = err.message;
                return reject(error);
                //return reject(err);
            });
        });
    }

    /**
     * @public
     * @nodered true
     * @method getCalendarAutomaticReplyStatus
     * @instance
     * @category Presence CALENDAR
     * @param {string} userId The id of user to retrieve the calendar automatic reply status.
     * @description
     *    Allow to retrieve the calendar automatic reply status <br>
     *    return promise with { <br>
     *    enabled : string, //  its status <br>
     *    start : string, // its start date <br>
     *    end : string, // its end date <br>
     *    message_text : string, // its message as plain text <br>
     *    message_thtml : string // its message as html <br> 
     *    }  <br>
     *    <br>
     * @async
     * @return {Promise<{ 
     *    enabled : string,
     *    status : string,
     *    start : string,
     *    end : string,
     *    message_text : string,
     *    message_thtml : string 
     *    }, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    async getCalendarAutomaticReplyStatus(userId? : string ) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getCalendarAutomaticReplyStatus) is userId defined : ", isDefined(userId));

        return new Promise((resolve, reject) => {

            that._rest.getCalendarAutomaticReplyStatus(userId).then((automaticReplyStatus : any) => {
                that._logger.log(that.INFO, LOG_ID + "(getCalendarAutomaticReplyStatus) automaticReplyStatus : ", automaticReplyStatus);
                resolve(automaticReplyStatus);
            }).catch((err) => {
                that._logger.log(that.ERROR, LOG_ID + "(getCalendarAutomaticReplyStatus) error");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(getCalendarAutomaticReplyStatus) error : ", err);
                let error : any = ErrorManager.getErrorManager().OTHERERROR;
                error.label = "Catch Error while trying to get calendar presence.";
                error.msg = err.message;
                return reject(error);
                //return reject(err);
            });
        });
    }
    
    /**
     * @private
     * @method enableCalendar
     * @instance
     * @category Presence CALENDAR
     * @deprecated
     * @description
     *    Allow to enable the calendar. <br>
     *    return promise with { <br>
     *       Status : string // Operation status ("enabled" or "disabled") <br>
     *    }  <br>
     *    <br>
     * @async
     * @return {Promise< { 
     *       Status : string 
     *    }, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    /*
    private async enableCalendar( ) {
        let that = this;

        return new Promise((resolve, reject) => {

            that._rest.enableOrNotCalendar(false).then((result : any) => {
                that._logger.log(that.INFO, LOG_ID + "(enableCalendar) result : ", result);
                resolve(result);
            }).catch((err) => {
                that._logger.log(that.ERROR, LOG_ID + "(enableCalendar) error");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(enableCalendar) error : ", err);
                let error : any = ErrorManager.getErrorManager().OTHERERROR;
                error.label = "Catch Error while trying to enable calendar.";
                error.msg = err.message;
                return reject(error);
                //return reject(err);
            });
        });
    }
    // */
    
    /**
     * @private
     * @method disableCalendar
     * @instance
     * @category Presence CALENDAR
     * @deprecated
     * @description
     *    Allow to disable the calendar. <br>
     *    return promise with { <br>
     *       Status : string // Operation status ("enabled" or "disabled") <br>
     *    }  <br>
     *    <br>
     * @async
     * @return {Promise< { 
     *       Status : string 
     *    }, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     
     */
    /*
  private async disableCalendar( ) {
        let that = this;

        return new Promise((resolve, reject) => {

            that._rest.enableOrNotCalendar(true).then((result : any) => {
                that._logger.log(that.INFO, LOG_ID + "(disableCalendar) result : ", result);
                resolve(result);
            }).catch((err) => {
                that._logger.log(that.ERROR, LOG_ID + "(disableCalendar) error");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(disableCalendar) error : ", err);
                let error : any = ErrorManager.getErrorManager().OTHERERROR;
                error.label = "Catch Error while trying to disable calendar.";
                error.msg = err.message;
                return reject(error);
                //return reject(err);
            });
        });
    }
    // */

    /**
     * @public
     * @nodered true
     * @method controlCalendarOrIgnoreAnEntry
     * @instance
     * @category Presence CALENDAR
     * @param {boolean} disable disable calendar, true to re-enable
     * @param {string} ignore ignore the current calendar entry, false resumes the entry. Possible values : current, false
     * @description
     *    Enable/disable a calendar sharing or ignore a calendar entry. <br>
     *    return promise with { <br>
     *       Status : string // Operation status ("enabled" or "disabled") <br>
     *    }  <br>
     *    <br>
     * @async
     * @return {Promise< { 
     *       Status : string 
     *    }, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    controlCalendarOrIgnoreAnEntry (disable? : boolean, ignore? : string) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(controlCalendarOrIgnoreAnEntry) is disable defined : ", isDefined(disable));

        return new Promise((resolve, reject) => {

            that._rest.controlCalendarOrIgnoreAnEntry(disable, ignore).then((result : any) => {
                that._logger.log(that.INFO, LOG_ID + "(controlCalendarOrIgnoreAnEntry) result : ", result);
                resolve(result);
            }).catch((err) => {
                that._logger.log(that.ERROR, LOG_ID + "(controlCalendarOrIgnoreAnEntry) error");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(controlCalendarOrIgnoreAnEntry) error : ", err);
                let error : any = ErrorManager.getErrorManager().OTHERERROR;
                error.label = "Catch Error while trying to control calendar.";
                error.msg = err.message;
                return reject(error);
                //return reject(err);
            });
        });
    }

     /**
     * @public
      * @nodered true
     * @method unregisterCalendar
     * @instance
     * @category Presence CALENDAR
     * @description
     *    Delete a calendar sharing. <br>
     *    return promise with { <br>
     *       Status : string // Operation status ("deleted") <br>
     *    }  <br>
     *    <br>
     * @async
     * @return {Promise< { 
     *       Status : string 
     *    }, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    async unregisterCalendar ( ) {
        let that = this;
         that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(unregisterCalendar) .");

        return new Promise((resolve, reject) => {

            that._rest.unregisterCalendar().then((result : any) => {
                that._logger.log(that.INFO, LOG_ID + "(unregisterCalendar) result : ", result);
                resolve(result);
            }).catch((err) => {
                that._logger.log(that.ERROR, LOG_ID + "(unregisterCalendar) error");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(unregisterCalendar) error : ", err);
                let error : any = ErrorManager.getErrorManager().OTHERERROR;
                error.label = "Catch Error while trying to unregister calendar.";
                error.msg = err.message;
                return reject(error);
                //return reject(err);
            });
        });
    }


    // endregion Presence CALENDAR
    
    // region Presence MSTeams

    /**
     * @public
     * @nodered true
     * @method controlMsteamsPresence
     * @since 2.20.0
     * @instance
     * @category Manage Presence MSTeams
     * @param {boolean} disable disable presence, true to re-enable
     * @param {string} ignore ignore the current rainbow presence sharing
     * @async
     * @description
     *     Enable/disable a presence sharing or ignore rainbow presence sharing. <br>
     *     When disabled or enabled, a message stanza is sent to the user for multi-devices constraints. <br>
     * @return {Promise<any>}
     */
    async controlMsteamsPresence (disable? : boolean, ignore? : string) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(controlMsteamsPresence) is disable defined : ", isDefined(disable));
        return that._rest.controlMsteamsPresence(disable, ignore);
    }

    /**
     * @public
     * @nodered true
     * @method getMsteamsPresenceState
     * @since 2.20.0
     * @instance
     * @category Manage Presence MSTeams
     * @param {string} userId The Rainbow user id, his jid or the email of his attached Microsoft teams presence. Default value is the current connected user's id
     * @async
     * @description
     *     Get a MS-teams presence state <br>
     * @return {Promise<any>} </br> </br>
     * 
     * enabled
     * -------
     *
     *  | Champ | Type | Description |
     *  | --- | --- | --- |
     *  | busy | Boolean | presence busy flag. |
     *  | status | String | presence status (one of "chat", "busy" or "dnd") |
     *
     *  disabled
     *  --------
     *
     *  | Champ | Type | Description |
     *  | --- | --- | --- |
     *  | status | String | presence sharing is disabled (status = 'disabled') |
     *
     *  subscription_error
     *  ------------------
     *
     *  | Champ | Type | Description |
     *  | --- | --- | --- |
     *  | status | String | presence sharing with a expired subscription |
     *
     *  none
     *  ----
     *
     *  | Champ | Type | Description |
     *  | --- | --- | --- |
     *  | status | String | presence sharing is not defined (status = 'none') |
     *  
     *  
     *  */
    async getMsteamsPresenceState(userId?  : string) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getMsteamsPresenceState) is userId defined : ", isDefined(userId));
        if (!userId) {
            userId = that._rest.account.id;
        }
        return that._rest.getMsteamsPresenceState(userId);
    }

    /**
     * @public
     * @nodered true
     * @method getMsteamsPresenceStates
     * @since 2.20.0
     * @instance
     * @category Manage Presence MSTeams
     * @param {Array<string>} users The Rainbow user references (can be email or id). Default value is an Array with the current connected user's id.
     * @async
     * @description
     *     Get a MS-teams presence states of several users <br>
     * @return {Promise<any>}  </br> </br>
     * 
     * enabled
     * -------
     *
     *  | Champ | Type | Description |
     *  | --- | --- | --- |
     *  | users | Object\[\] | list of presence user states. |
     *  | busy | Boolean | presence busy flag. |
     *  | status | String | presence status (one of "chat", "busy" or "dnd"). |
     *  
     *  others
     *  ------
     *
     *  | Champ | Type | Description |
     *  | --- | --- | --- |
     *  | users | Object\[\] | list of presence user states. |
     *  | status | String | presence status (one of "disabled", "subscription_error", "none"). |
     *
     */
    async getMsteamsPresenceStates( users : Array<string> = []) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getMsteamsPresenceStates) is users defined : ", isDefined(users));
        if (users.length == 0) {
            users.push(that._rest.account.id);
        }
        return that._rest.getMsteamsPresenceStates(users);
    }

    /**
     * @public
     * @nodered true
     * @method registerMsteamsPresenceSharing
     * @since 2.20.0
     * @instance
     * @category Manage Presence MSTeams
     * @param {boolean} redirect Immediately redirect to login page (OAuth2) or generate an HTML page.
     * @param {string} callback Redirect URL to the requesting client.
     * @async
     * @description
     *     The requesting client get a redirection URL or could be redirected immediately to the provider login page to gather user consent. <br>
     * @return {Promise<any>}  </br> </br>
     *
     * | Champ | Type | Description |
     * | --- | --- | --- |
     * | url | String | Microsoft Teams Presence OAuth URL |
     * 
     * */
    async registerMsteamsPresenceSharing( redirect? : boolean, callback? : string) {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(registerMsteamsPresenceSharing) is redirect defined : ", isDefined(redirect));
        return that._rest.registerMsteamsPresenceSharing(redirect, callback);
    }

    /**
     * @public
     * @nodered true
     * @method unregisterMsteamsPresenceSharing
     * @since 2.20.0
     * @instance
     * @category Manage Presence MSTeams
     * @async
     * @description
     *     Delete a MS Teams presence sharing. <br>
     *     A message stanza is sent to the user for multi-devices constraints. <br>
     * @return {Promise<any>}  </br> </br>
     *
     * | Champ | Type | Description |
     * | --- | --- | --- |
     * | Status | String | Operation status |
     *
     * */
    async unregisterMsteamsPresenceSharing() {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(unregisterMsteamsPresenceSharing) .");
        return that._rest.unregisterMsteamsPresenceSharing();
    }

    /**
     * @public
     * @nodered true
     * @method activateMsteamsPresence
     * @since 2.20.0
     * @instance
     * @category Manage Presence MSTeams
     * @async
     * @description
     *     activate a MS Teams presence sharing. <br>
     *     A message stanza is sent to the user for multi-devices constraints. <br>
     * @return {Promise<any>}  </br> </br>
     *
     * | Champ | Type | Description |
     * | --- | --- | --- |
     * | Status | String | Operation status |
     *
     * */
    async activateMsteamsPresence() {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(activateMsteamsPresence) .");
        return that._rest.activateMsteamsPresence();
    }

    /**
     * @public
     * @nodered true
     * @method deactivateMsteamsPresence
     * @since 2.20.0
     * @instance
     * @category Manage Presence MSTeams
     * @async
     * @description
     *     desactivate a MS Teams presence sharing. <br>
     *     A message stanza is sent to the user for multi-devices constraints. <br>
     * @return {Promise<any>}  </br> </br>
     *
     * | Champ | Type | Description |
     * | --- | --- | --- |
     * | Status | String | Operation status |
     *
     * */
    async deactivateMsteamsPresence() {
        let that = this;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(deactivateMsteamsPresence) .");
        return that._rest.deactivateMsteamsPresence();
    }

    // endregion Presence MSTeams

    // region Presence Contact

    /**
     * @private
     * @method subscribePresence
     * @instance
     * @category Presence Contact
     * @description
     *    Allows to subscribe presence to a contact. <br>
     * @async
     * @return {Promise< any, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    async subscribePresence(to) {
        /*
        <presence to="user@otherhost.com" type="subscribe" />
         */
        let that = this ;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(subscribePresence) is to defined : ", isDefined(to));

        if (that._useXMPP) {
            let result = that._xmpp.subscribePresence(to);
            that._logger.log(that.INFO, LOG_ID + "(subscribePresence) begin wait for the bubble to be active : ", to);
/*
            // Wait for the bubble to be active
            await until(() => {
                return bubble.isActive === true;
            }, "Wait for the Bubble " + bubble.jid + " to be active").catch((err)=>{
                that._logger.log(that.INTERNAL, LOG_ID + "(sendInitialBubblePresence) FAILED wait for the bubble to be active : ", bubble , " : ", err);
            });
*/
            that._logger.log(that.INFO, LOG_ID + "(subscribePresence) end wait for the bubble to be active : ", to);
            return (result);
        }
    }
    // endregion Presence Contact

    //region Presence Synchronize CPE Exchange Calendar [AD/LDAP]
    // RQRAINB-12269
    /**
     * @public
     * @method notifyCalendarProvider
     * @instance
     * @category Presence Contact
     * @async
     * @description
     * Allows to subscribe users to EWS using a webhook in Rainbow. </BR>
     * EWS cnx opens an event session for each subscribed user.</BR>
     * EWS cnx notify to Rainbow each new appointment changes.</BR>
     *
     * For each event, a IQ GetEvent request will be sent to retrieve a potential event that will occur in current time frame window.
     * @param {Array<string>} ids The ids specified in field value of body request are the Rainbow user id for which a new calendar event has been created.
     * @param {Object} headers Allow to define specifics headers to the request.
     * @param {boolean} forceNotify Allow to to force the refresh of xmpp status as a comparaison is done between old and new presence status.
     * @return {Promise< any, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    notifyCalendarProvider(ids: Array<string>, headers : any = {}, forceNotify: boolean = undefined) {
        let that = this ;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(notifyCalendarProvider) is ids defined : ", isDefined(ids));
        return that._rest.notifyCalendarProvider(ids, headers, forceNotify);
    }

    /**
     * @public
     * @method sendResultCalendarEvents
     * @instance
     * @category Presence Contact
     * @async
     * @description
     * Allows to send result of a get events iq request from Rainbow. </BR>
     *
     * Note: To be used when a "rainbow_onEWSgetevents" event is received to send events response.
     *
     * @example
     *
     *   rainbowSDK.events.on("rainbow_onEWSgetevents", (data) => {
     *         _logger.log("debug", "MAIN - (rainbow_onEWSgetevents) - rainbow event received.", data);
     *         let calendarManager = new RainbowSDK.CalendarManager(data.id, data.from, data.email);
     *         calendarManager.addEvent(
     *             {
     *                 "id": "evt-001",
     *                 "subject": "Réunion projet",
     *                 "type": "singleInstance",
     *                 "showAs": "busy",
     *                 startDate: {
     *                     "value": "2025-08-14T09:00:00Z",
     *                     "timezone": "Europe/Paris"
     *                 },
     *                 "endDate": {
     *                     "value": "2025-08-14T10:30:00Z",
     *                     timezone: "Europe/Paris"
     *                 }
     *             });
     *         calendarManager.addEvent(
     *             {
     *                 "id": "evt-002",
     *                 "subject": "Pause café",
     *                 "type": "occurrence",
     *                 "showAs": "free",
     *                 "startDate": {
     *                     "value": "2025-08-14T10:30:00Z"
     *                 },
     *                 "endDate": {
     *                     "value": "2025-08-14T10:45:00Z"
     *                 }
     *             });
     *
     *         rainbowSDK.presence.sendResultCalendarEvents(calendarManager);
     *     });
     *
     * @param {CalendarManager} calendarManager Allow to define the result of a get events iq request from Rainbow.
     * @return {Promise< any, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    sendResultCalendarEvents(calendarManager : CalendarManager) {
        let that = this ;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(sendResultCalendarEvents) is calendarManager defined : ", isDefined(calendarManager));
        return new Promise(async (resolve, reject) => {
            if (!calendarManager) {
                that._logger.log(that.ERROR, LOG_ID + "(sendResultCalendarEvents) Parameter 'calendarManager' is missing or null");
                throw ErrorManager.getErrorManager().BAD_REQUEST();
            }

            try {
                let result = that._xmpp.sendResultCalendarEvents(calendarManager);
                that._logger.log(that.DEBUG, "(sendResultCalendarEvents) - sent.");
                that._logger.log(that.INTERNAL, "(sendResultCalendarEvents) - result : ", result);
                resolve(result);
            } catch (err) {
                that._logger.log(that.ERROR, LOG_ID + "(sendResultCalendarEvents) Error.");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(sendResultCalendarEvents) Error : ", err);
                return reject(err);
            }
        });
    }

    /**
     * @public
     * @method sendAutoReplyEvents
     * @instance
     * @category Presence Contact
     * @async
     * @description
     * Allows to send result of a get auto reply events iq request from Rainbow. </BR>
     *
     * Note: To be used when a "rainbow_onEWSgetevents" event is received to send events response.
     *
     * @example
     *
     *     rainbowSDK.events.on("rainbow_onEWSgetautoreply", (data) => {
     *         _logger.log("debug", "MAIN - (rainbow_onEWSgetautoreply) - rainbow event received.", data);
     *
     *         let autoReplyManager = new RainbowSDK.AutoReplyManager({
     *             id: data.id,
     *             to: data.from,
     *             state: "scheduled",
     *             email: data.email,
     *             startDate: { value: "2025-08-20T09:00:00Z", timezone: "Europe/Paris" },
     *             endDate: { value: "2025-08-25T18:00:00Z", timezone: "Europe/Paris" },
     *             internalReplyMessage: "Je suis absent du bureau, merci de contacter mon collègue.",
     *             externalReplyMessage: {
     *                 value: "Je suis actuellement en congés.",
     *                 audience: "known"
     *             }
     *         });
     *
     *         rainbowSDK.presence.sendAutoReplyEvents(autoReplyManager);
     *     });
     * @param {AutoReplyManager} autoReplyManager Allow to define  get auto reply events iq.
     * @return {Promise< any, ErrorManager>}
     * @fulfil {ErrorManager} - ErrorManager object depending on the result.
     */
    sendAutoReplyEvents(autoReplyManager : AutoReplyManager) {
        let that = this ;
        that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(sendAutoReplyEvents) is autoReplyManager defined : ", isDefined(autoReplyManager));
        return new Promise(async (resolve, reject) => {
            if (!autoReplyManager) {
                that._logger.log(that.ERROR, LOG_ID + "(sendAutoReplyEvents) Parameter 'autoReplyManager' is missing or null");
                throw ErrorManager.getErrorManager().BAD_REQUEST();
            }

            try {
                let result = that._xmpp.sendAutoReplyEvents(autoReplyManager);
                that._logger.log(that.DEBUG, "(sendAutoReplyEvents) - sent.");
                that._logger.log(that.INTERNAL, "(sendAutoReplyEvents) - result : ", result);
                resolve(result);
            } catch (err) {
                that._logger.log(that.ERROR, LOG_ID + "(sendAutoReplyEvents) Error.");
                that._logger.log(that.INTERNALERROR, LOG_ID + "(sendAutoReplyEvents) Error : ", err);
                return reject(err);
            }
        });
    }

    //endregion Presence Synchronize CPE Exchange Calendar [AD/LDAP]
}

module.exports.PresenceService = PresenceService;
export {PresenceService } ;
