/* eslint-disable no-console */

import StateMachine from 'javascript-state-machine';
import StateMachineHistory from 'javascript-state-machine/lib/history';

import Media from '../media';
import MeetingUtil from '../meeting/util';
import {MEETING_AUDIO_STATE_MACHINE, EVENT_TRIGGERS, EVENT_TYPES} from '../constants';

const handleTransition = (audio) => {
  if (audio.mute && audio.self) {
    return MEETING_AUDIO_STATE_MACHINE.STATES.MUTE_SELF;
  }
  if (!audio.mute && audio.self) {
    return MEETING_AUDIO_STATE_MACHINE.STATES.UNMUTE_SELF;
  }
  return null;
};

const triggerEvent = (meeting, state, result, trigger, type) => {
  meeting.trigger(trigger, {
    type,
    status: state.state,
    muted: state.muted,
    result
  });
};

const doToggle = (state, transition, audio, meeting) => {
  MeetingUtil.remoteUpdateAudioVideo(audio, {mute: meeting.video.muted}, meeting)
    .then((resolution) => {
      try {
        Media.setLocalTracks(audio, {mute: meeting.video.muted}, meeting.mediaProperties.localStream);
      }
      catch (error) {
        MeetingUtil.remoteUpdateAudioVideo({mute: state.muted}, {mute: meeting.video.muted}, meeting).catch(
          (remoteUpdateError) => {
            console.log(
              `audio:state#onBeforeToggle->${transition.event} fired! State failed to change with transition '${
                transition.transition
              }''. After local audio toggle failed, resetting remote also failed, meeting audio in bad state with error: ${remoteUpdateError}.`
            );
          }
        );
        triggerEvent(meeting, state, error, EVENT_TRIGGERS.MEDIA_AUDIO_CHANGED, EVENT_TYPES.ERROR);
        console.log(
          `audio:state#onBeforeToggle->${transition.event} fired! State failed to change with transition '${
            transition.transition
          }''. State has been reset and toggling has been reversed.`
        );
        return Promise.reject(error);
      }
      triggerEvent(meeting, state, resolution, EVENT_TRIGGERS.MEDIA_AUDIO_CHANGED, EVENT_TYPES.LOCAL);
      console.log(
        `audio:state#onAfterToggle->${transition.event} fired! State changed from '${transition.from}' to '${
          transition.to
        }' with transition '${transition.transition}''.`
      );
      return Promise.resolve(resolution);
    })
    .catch((error) => {
      triggerEvent(meeting, state, error, EVENT_TRIGGERS.MEDIA_AUDIO_CHANGED, EVENT_TYPES.ERROR);
      console.log(
        `audio:state#onAfterHistoryBack->${transition.event} fired! State failed to change with transition '${
          transition.transition
        }''. State has been reset and toggling has been reversed.`
      );
      return Promise.reject(error);
    });
};

const AudioStateMachine = {
  /**
   *
   * @param {Object} mediaDirection An object that contains whether we send audio/video/screen streams
   * @param {Meeting} meeting the meeting instance we are using for this state machine
   * @returns {StateMachine} returns a StateMachine instance
   */
  create(mediaDirection, meeting) {
    if (!mediaDirection.sendAudio) {
      return null;
    }
    return new StateMachine({
      transitions: [
        {
          name: MEETING_AUDIO_STATE_MACHINE.TRANSITIONS.TOGGLE,
          from: '*',
          /**
           *
           * @param {Object} audio The audio options
           * @param {Boolean} audio.mute Whether to mute the meeting or not
           * @param {Boolean} audio.self Whether the was muted by the end user
           * @param {Boolean} audio.remote Whether the meeting was muted by the server
           * @returns {String} returns a new value to set the state to
           */
          to(audio) {
            return handleTransition(audio) || this.state;
          }
        }
      ],
      data: {
        muted: false,
        self: true
      },
      methods: {
        /**
         * Convenience method to return whether the call is muted or not
         * @returns {Boolen} whether the audio is muted or not
         */
        isMuted() {
          return this.muted;
        },

        /**
         * Convenience method to expose this.self
         * @returns {Boolen} this.self
         */
        isSelf() {
          return this.self;
        },

        /**
         *
         * @param {Object} audio the audio state to change
         * @returns {null}
         */
        setData(audio) {
          this.muted = audio.mute;
          this.self = audio.self;
        },

        /**
         * Method that gets fired before the toggle state change.
         * If this fails, return false will cancel the transition and the state will remain unchanged

         * @param {Object} transition the StateMachine transition object
         * @param {Object} audio Audio options
         * @returns {Object} this.data which contains {muted, self}
         */
        onBeforeToggle(transition, audio) {
          if (transition.from !== transition.to) {
            doToggle(this, transition, audio, meeting);
          }
          return Promise.resolve(this.data);
        },

        /**
         *
         * @param {Object} transition StateMachine transition
         * @param {Object} audio audio options
         * @returns {Object} this.data which contains {muted, self}
         */
        onAfterToggle(transition, audio) {
          this.setData(audio);
          return Promise.resolve(this.data);
        },
        plugins: [new StateMachineHistory({max: 5})]
      }
    });
  }
};

export default AudioStateMachine;
