text: /*!
    * Copyright (c) 2015-2019 Cisco Systems, Inc. See LICENSE file.
  */
  
  import '@webex/internal-plugin-mercury';
  
  import {WebexPlugin} from '@webex/webex-core';
  
  import Metrics from '../metrics';
  import {trigger, eventType} from '../metrics/config';
  import LoggerConfig from '../common/logs/logger-config';
  import StaticConfig from '../common/config';
  import LoggerProxy from '../common/logs/logger-proxy';
  import Trigger from '../common/events/trigger-proxy';
  import {
    MEETINGS,
    EVENTS,
    EVENT_TRIGGERS,
    READY,
    LOCUSEVENT,
    LOCUS_URL,
    ROAP,
    _MEETING_,
    _JOIN_,
    _LOCUS_ID_,
    _INCOMING_,
    LOCUS,
    CORRELATION_ID,
    SIP_URI,
    _LEFT_,
    _ID_
  } from '../constants';
  import MeetingInfo from '../meeting-info';
  import Meeting from '../meeting';
  import PersonalMeetingRoom from '../personal-meeting-room';
  import Reachability from '../reachability';
  import Request from '../meetings/request';
  import StatsAnalyzer from '../analyzer/analyzer';
  import StatsCalculator from '../analyzer/calculator';
  
  import MeetingCollection from './collection';
  import MeetingsUtil from './util';
  
  
  /**
     * Meetings Ready Event
     * Emitted when the meetings instance on webex is ready
     * @event meetings:ready
     * @instance
     * @memberof Meetings
     */
  
  /**
   * Meetings Registered Event
   * Emitted when the meetings instance has been registered and listening
   * @event meetings:registered
   * @instance
   * @memberof Meetings
   */
  
  /**
      * Meeting Removed Event
      * Emitted when a meeting was removed from the cache of meetings
      * @event meeting:removed
      * @instance
      * @type {Object}
      * @property {String} meetingId the removed meeting
      * @property {Object} response the server response
      * @property {String} type what type of meeting it was
      * @memberof Meetings
      */
  
  /**
      * Meeting Added Event
      * Emitted when a meeting was added to the cache of meetings
      * @event meeting:added
      * @instance
      * @type {Object}
      * @property {String} meetingId the added meeting
      * @property {String} type what type of meeting it was
      * @memberof Meetings
      */
  
  /**
     * Maintain a cache of meetings and sync with services.
     * @class
     */
  export default class Meetings extends WebexPlugin {
      namespace = MEETINGS;
  
      /**
       * Initializes the Meetings Plugin
       * @constructor
       * @public
       * @memberof Meetings
       */
      constructor(...args) {
        super(...args);
        /**
         * The MeetingInfo object to interact with server
         * @instance
         * @type {Object}
         * @private
         * @memberof Meetings
         */
        this.meetingInfo = new MeetingInfo({}, {parent: this.webex});
        /**
         * The Meetings request to interact with server
         * @instance
         * @type {Object}
         * @private
         * @memberof Meetings
         */
        this.request = new Request({}, {parent: this.webex});
        this.meetingCollection = new MeetingCollection();
        /**
         * The PersonalMeetingRoom object to interact with server
         * @instance
         * @type {Object}
         * @public
         * @memberof Meetings
         */
        this.personalMeetingRoom = new PersonalMeetingRoom({}, {parent: this.webex});
        /**
         * The Reachability object to interact with server, starts as null until {@link Meeting#setReachability} is called
         * starts as null
         * @instance
         * @type {Object}
         * @private
         * @memberof Meetings
         */
        this.reachability = null;
  
        /**
         * If the meetings plugin has been registered and listening via {@link Meetings#register}
         * @instance
         * @type {Boolean}
         * @public
         * @memberof Meetings
         */
        this.registered = false;
  
        this.onReady();
        Metrics.initialSetup(this.meetingCollection, this.webex.version);
      }
  
      /**
       * handle locus events and takes meeting actions with them as they come in
       * @param {Object} data a locus event
       * @param {String} data.locusUrl
       * @param {Object} data.locus
       * @param {String} data.eventType
       * @returns {undefined}
       * @private
       * @memberof Meetings
       */
      handleLocusEvent(data) {
        let meeting = null;
  
        // getting meeting by correlationId. This will happen for the new event
        // Either the locus
        // TODO : Add check for the callBack Address
        meeting = this.meetingCollection.getByKey(LOCUS_URL, data.locusUrl) ||
        this.meetingCollection.getByKey(CORRELATION_ID, MeetingsUtil.checkForCorrelationId(this.webex.internal.device.url, data.locus)) ||
        this.meetingCollection.getByKey(SIP_URI, data.locus.self && data.locus.self.callbackInfo && data.locus.self.callbackInfo.callbackAddress);
  
        if (!meeting) {
          // TODO: create meeting when we get a meeting object
          // const checkForEnded = (locus) => {
          // TODO: you already ended the meeting but you got an event later
          // Mainly for 1:1 Callsor meeting
          // Happens mainly after refresh
  
          // 1:1 Meeting
          // 1)  You ended a call before but you got a mercury event
          // Make sure end the call and cleanup the meeting only if the mercury
          // event says so
          // 2) Maintain lastSync time in the meetings object which helps to compare
          // If the meeting came befor or after the sync . ANy meeting start time before the sync time is invalid
  
          // For space Meeting
          // Check the locus object and see who has joined
  
          // };
          // rather then locus object change to locus url
  
          if (data.locus && data.locus.fullState && data.locus.fullState.state === LOCUS.STATE.INACTIVE) {
            // just ignore the event as its already ended and not active
            LoggerProxy.logger.warn('Meetings->handleLocusEvent#Locus event received for meeting, after it was ended.');
  
            return;
          }
  
  
          // When its wireless share or guest and user leaves the meeting we dont have to keep the meeting object
          // Any future events will be neglected
  
          if (data.locus && data.locus.self && (data.locus.self.state === _LEFT_ && data.locus.self.removed === true)) {
            // just ignore the event as its already ended and not active
            LoggerProxy.logger.warn('Meetings->handleLocusEvent#Locus event received for meeting, after it was ended.');
  
            return;
          }
  
          this.create(data.locus, _LOCUS_ID_).then((newMeeting) => {
            meeting = newMeeting;
  
            if (data.eventType === LOCUSEVENT.DIFFERENCE) {
              // its a delta object and we have a new meeting
              meeting.locusInfo.initialSetup(data.locus, meeting);
            }
            else {
              // Its a new meeting and have a fresh locus object
              meeting.locusInfo.initialSetup(data.locus);
            }
          })
            .finally(() => {
              // There will be cases where locus event comes in gets created and deleted because its a 1:1 and meeting gets deleted
              // because the other user left so before sending 'added' event make sure it exists in the collection
  
              if (this.getMeetingByType(_ID_, meeting.id)) {
              // Had to put `meeting:added here as the parsing of the locus object is done here
                Metrics.postEvent({event: eventType.NOTIFICATION_RECEIVED, meeting, data: {trigger: trigger.MERCURY_EVENT}});
  
                Metrics.postEvent({event: eventType.REMOTE_STARTED, meeting, data: {trigger: trigger.MERCURY_EVENT}});
                Trigger.trigger(
                  this,
                  {
                    file: 'meetings',
                    function: 'handleLocusEvent'
                  },
                  EVENT_TRIGGERS.MEETING_ADDED,
                  {
                    meeting,
                    type: meeting.type === _MEETING_ ? _JOIN_ : _INCOMING_
                  }
                );
              }
              else {
                // Meeting got added but was not found in the collection. It might have got destroyed
                LoggerProxy.logger.warn('Meetings->handleLocusEvent#Created and destroyed meeting object before sending an event');
              }
            });
        }
        else {
          Metrics.postEvent({event: eventType.NOTIFICATION_RECEIVED, meeting, data: {trigger: trigger.MERCURY_EVENT}});
          meeting.locusInfo.parse(meeting, data);
        }
      }
  
      /**
       * handles locus events through mercury that are not roap
       * @param {Object} envelope
       * @param {Object} envelope.data
       * @param {String} envelope.data.eventType
       * @returns {undefined}
       * @private
       * @memberof Meetings
       */
      handleLocusMercury(envelope) {
        const {data} = envelope;
        const {eventType} = data;
  
        if (eventType && eventType !== LOCUSEVENT.MESSAGE_ROAP) {
          this.handleLocusEvent(data);
        }
      }
  
      /**
       * registers for locus and roap mercury events
       * @returns {undefined}
       * @private
       * @memberof Meetings
       */
      listenForEvents() {
        this.webex.internal.mercury.on(LOCUSEVENT.LOCUS_MERCURY, (envelope) => {
          this.handleLocusMercury(envelope);
        });
        this.webex.internal.mercury.on(ROAP.ROAP_MERCURY, (envelope) => {
          MeetingsUtil.handleRoapMercury(envelope, this.meetingCollection);
        });
      }
  
      /**
       * stops listening for locus and roap mercury events
       * @returns {undefined}
       * @private
       * @memberof Meetings
       */
      stopListeningForEvents() {
        this.webex.internal.mercury.off(LOCUSEVENT.LOCUS_MERCURY);
        this.webex.internal.mercury.off(ROAP.ROAP_MERCURY);
      }
  
      /**
       * @returns {undefined}
       * @private
       * @memberof Meetings
       */
      onReady() {
        this.webex.once(READY, () => {
          StaticConfig.set(this.config);
          LoggerConfig.set(this.config.logging);
          LoggerProxy.set(this.webex.logger);
          Trigger.trigger(
            this,
            {
              file: 'meetings',
              function: 'onReady'
            },
            EVENT_TRIGGERS.MEETINGS_READY
          );
        });
      }
  
      /**
       * Explicitly sets up the meetings plugin by registering
       * the device, connecting to mercury, and listening for locus events.
       *
       * @returns {Promise}
       * @public
       * @memberof Meetings
       */
      register() {
        if (!this.webex.canAuthorize) {
          LoggerProxy.logger.error('meetings->register#ERROR, Unable to register, SDK cannot authorize');
  
          return Promise.reject(new Error('SDK cannot authorize'));
        }
  
        if (this.registered) {
          LoggerProxy.logger.info('meetings->register#INFO, Meetings plugin already registered');
  
          return Promise.resolve();
        }
  
        return this.webex.internal.device.register()
          .then(() => this.webex.internal.mercury.connect())
          .then(() => {
            this.listenForEvents();
            Trigger.trigger(
              this,
              {
                file: 'meetings',
                function: 'register'
              },
              EVENT_TRIGGERS.MEETINGS_REGISTERED
            );
            this.registered = true;
          })
          .catch((error) => {
            LoggerProxy.logger.error(`meetings->register#ERROR, Unable to register, ${error.message}`);
  
            return Promise.reject(error);
          });
      }
  
      /**
       * Explicitly tears down the meetings plugin by deregistering
       * the device, disconnecting from mercury, and stops listening to locus events
       *
       * @returns {Promise}
       * @public
       * @memberof Meetings
       */
      unregister() {
        if (!this.registered) {
          LoggerProxy.logger.info('meetings->unregister#INFO, Meetings plugin already unregistered');
  
          return Promise.resolve();
        }
  
        this.stopListeningForEvents();
  
        return this.webex.internal.mercury.disconnect()
          .then(() => this.webex.internal.device.unregister())
          .then(() => {
            Trigger.trigger(
              this,
              {
                file: 'meetings',
                function: 'unregister'
              },
              EVENT_TRIGGERS.MEETINGS_UNREGISTERED
            );
            this.registered = false;
          });
      }
  
      /**
       * initializes the reachability instance for Meetings
       * @returns {undefined}
       * @public
       * @memberof Meetings
       */
      setReachability() {
        this.reachability = new Reachability({}, {parent: this.webex});
      }
  
      /**
       * gets the reachability instance for Meetings
       * @returns {Reachability}
       * @public
       * @memberof Meetings
       */
      getReachability() {
        return this.reachability;
      }
  
      /**
       * initializes and starts gathering reachability for Meetings
       * @returns {undefined}
       * @public
       * @memberof Meetings
       */
      startReachability() {
        if (!this.reachability) {
          this.setReachability();
        }
        this.getReachability().gatherReachability();
      }
  
      /**
       * gets the personal meeting room instance, for saved PMR values for this user
       * @returns {PersonalMeetingRoom}
       * @public
       * @memberof Meetings
       */
      getPersonalMeetingRoom() {
        return this.personalMeetingRoom;
      }
  
      /**
       * @param {String} meetingId
       * @param {Object} response
       * @param {String} type
       * @returns {Object}
       * @private
       * @memberof Meetings
       */
      destroy(meetingId, response) {
        this.meetingCollection.delete(meetingId);
        Trigger.trigger(
          this,
          {
            file: 'meetings',
            function: 'destroy'
          },
          EVENT_TRIGGERS.MEETING_REMOVED,
          {
            meetingId,
            response
          }
        );
  
        return response;
      }
  
      /**
       * Create a meeting.
       * @param {string} destination - sipURL, spaceId, phonenumber, meeting link, or locus object}
       * @param {string} [type] - the optional specified type, such as locusId
       * @returns {Promise} A new Meeting.
       * @public
       * @memberof Meetings
       */
      create(destination, type = null) {
        // TODO: type should be from a dictionary
        // Check if there is already meeting
        const meeting = this.meetingCollection.getByKey(SIP_URI, destination);
  
        if (!meeting) {
          return this.createMeeting(destination, type)
            .then((meeting) => {
              if (meeting && meeting.on) {
                meeting.on(EVENTS.DESTROY_MEETING, (payload) => {
                  this.destroy(payload.meetingId, payload.response);
                });
              }
              else {
                LoggerProxy.logger.error(`meetings->create#ERROR, meeting does not have on method, will not be destroyed, meeting cleanup impossible for meeting: ${meeting}`);
              }
  
              return Promise.resolve(meeting);
            });
        }
  
        return Promise.resolve(meeting);
      }
  
      /**
       * @param {String} destination see create()
       * @param {String} type see create()
       * @returns {Promise} a new meeting instance complete with meeting info and destination
       * @private
       * @memberof Meetings
       */
      createMeeting(destination, type = null) {
        const meeting = new Meeting(
          {
            userId: this.webex.internal.device.userId,
            deviceUrl: this.webex.internal.device.url,
            roapSeq: 0,
            locus: type === _LOCUS_ID_ ? destination : null // pass the locus object if present
          },
          {
            parent: this.webex
          }
        );
  
        this.meetingCollection.set(meeting);
  
        return this.meetingInfo
          .fetchMeetingInfo(MeetingsUtil.extractDestination(destination, type), type)
          .then((info) => {
            meeting.parseMeetingInfo(info);
            meeting.meetingInfo = info;
  
            return meeting;
          })
          .catch((err) => {
            // if there is no meeting info we assume its a 1:1 call or wireless share
            LoggerProxy.logger.info(`Meetings->createMeeting#Error ${err} fetching /meetingInfo for creation.`);
            LoggerProxy.logger.info('Meetings->createMeeting#Info assuming this destination is a 1:1 or wireless share');
            // We need to save this info for future reference
            meeting.destination = destination;
  
            return meeting;
          })
          .finally(() => {
            // For type LOCUS_ID we need to parse the locus object to get the information
            // about the caller and callee
            // TODO: check for a better solution
            if (type === _LOCUS_ID_) {
              return;
            }
  
            if (!meeting.sipUri) {
              meeting.setSipUri(destination);
            }
            // TODO: check if we have to move this to parser
            const meetingAddedType = MeetingsUtil.getMeetingAddedType(type);
  
            // We typically shouldn't need to trigger both and event and return a promise.
            // Is this a special case? We want to make the public API usage as simple as possible.
            Trigger.trigger(
              this,
              {
                file: 'meetings',
                function: 'createMeeting'
              },
              EVENT_TRIGGERS.MEETING_ADDED,
              {
                meeting,
                type: meetingAddedType
              }
            );
          });
  
        // Create the meeting calling the necessary service endpoints.
  
        // Internally, there are many more destinations:
        //
        // - locusID
        // - meetingURL
        // - globalMeetingID, e.g, *00*meetingID
        // - meetingID
        // - meetingURL
        // - PSTN
        // - phone number
        //
        // Our job is to determine the appropriate one
        // and its corresponding service so that developers
        // need only sipURL or spaceID to get a meeting
        // and its ID, but have the option to use createWithType()
        // and specify those types to get meetingInfo
      }
  
      /**
       * get a specifc meeting given it's type matched to the value, i.e., locus url
       * @param {String} type
       * @param {Object} value
       * @returns {Meeting}
       * @public
       * @memberof Meetings
       */
      getMeetingByType(type, value) {
        return this.meetingCollection.getByKey(type, value);
      }
  
      /**
       * Get all meetings.
       * @param {object} options
       * @param {object} options.startDate - get meetings after this start date
       * @param {object} options.endDate - get meetings before this end date
       * @returns {Object} All active and scheduled meetings.
       * @public
       * @memberof Meetings
       */
      getAllMeetings(options = {}) {
        // Options may include other parameters to filter this collection
        // of meetings.
        return this.meetingCollection.getAll(options);
      }
  
      /**
       * syncs all the meeting from server
       * @returns {undefined}
       * @public
       * @memberof Meetings
       */
      syncMeetings() {
        return this.request.getActiveMeetings().then((locus) => {
          if (locus.loci && locus.loci.length > 0) {
            locus.loci.forEach((locus) => {
              this.handleLocusEvent({
                locus,
                locusUrl: locus.url
              });
            });
          }
        });
      }
  
      /**
       * Get all scheduled meetings.
       * @param {object} options
       * @param {object} options.startDate - get meetings after this start date
       * @param {object} options.endDate - get meetings before this end date
       * @returns {Object} All scheduled meetings.
       * @memberof Meetings
       */
      getScheduledMeetings() {
        return this.meetingCollection.getAll({scheduled: true});
      }
  
      /**
       * Get the logger instance for plugin-meetings
       * @returns {Logger}
       */
      getLogger() {
        return LoggerProxy.get();
      }
  
      /**
       * Get the Stats Analyzer singleton
       * @returns {StatsAnalyzer}
       * @public
       * @memberof Meetings
       */
      getAnalyzer() {
        return StatsAnalyzer;
      }
  
      /**
       * Get the Stats Calculator singleton
       * @returns {StatsCalculator}
       * @public
       * @memberof Meetings
       */
      getCalculator() {
        return StatsCalculator;
      }
  }
  