# src/Remon.js

    /*
    eslint new-cap: ['error', {
     'capIsNewExceptions': [
       'PeerConnection',
       'SignalingConnection',
       'EventManager',
      ]
    }]
    */

    import 'webrtc-adapter';
    import 'platform';
    import Config from './Configure';
    import Context from './Context';
    import Media from './Media';
    import EventManager from './EventManager';
    import signalingStates from './SignalingStates';
    import bindPeerConnectionEvents from './PeerConnectionHandler';
    import SignalingConnection from './SignalingConnection';
    import bindSignalingConnectionEvents from './SignalingConnectionHandler';
    import RemonRecorder from './RemonRecorder';
    import util from './Util';
    import l from './Logger';
    /**
    * RemoteMonster API를 사용하기 위한 가장 중요한 클래스. Remon클래스를 통해 서버와 연결하고 명령을 보내고 Close한다. 서버로부터 메시지를 받는 것은 주로 RemonObserver를 통해 수행한다
    */
    class Remon{
      /**
      * config와 listener를 입력받아서 Remon server에 연결 및 인증 절차를 밟는다.
      * 활용 예: var v = new Remon({config: rtcConfig, listener: rtcListener});
      */
      constructor({config, listener}){
        this.version = "0.2.5";

        this.context = new Context();
        this.context.eventManager = EventManager();
        this.config = config;
        util.validateConfig(this.context, this.config);
        this.media = new Media(this.context);
        this.context.mediaManager = this.media;
        this.uri = Config.appServer.url+"/init";
        this.key = this.config.credential.key;
        this.serviceId = this.config.credential.serviceId;
        this.context.key = this.key;
        this.context.serviceId = this.serviceId;
        this.context.state = 'INIT';
        this.logLevel = (this.config.dev && this.config.dev.logLevel)? this.config.dev.logLevel : "INFO";
        l.init(this.logLevel);
        if (listener){
          Object.keys(listener).forEach((type) => {
            const listenerItem = listener[type];
            this.context.eventManager.addEventListener({ type, listenerItem });
          });
        }
        if (!this.config.media)this.config.media={audio:true, video:true, record:false};
        if (this.config.media.record)this.context.useRecord = this.config.media.record;
        if (this.config.media.video.codec) this.context.videoCodec = this.config.media.video.codec;
        if (this.config.media.audio.codec) this.context.audioCodec = this.config.media.audio.codec;
        if (this.config.media.video===false) this.context.useVideo = false;
        if (this.config.media.audio===false) this.context.useAudio = false;
        if (this.config.credential.resturl) Config.appServer.url = this.config.credential.resturl;
        if (this.config.credential.wsurl) Config.signalingServer.url = this.config.credential.wsurl;

        const messageBody = {
          credential: {key:this.key,serviceId:this.serviceId},
          env: {
            os: platform.os.family,
            osVersion: platform.os.version||"0",
            device: platform.name,
            deviceVersion: platform.version||"0",
            networkType: Navigator.connection,
            sdkVersion: this.version,
          }
        };
        if (this.config.media.sendonly===true) messageBody.sendonly = "true";
        if (this.config.media.recvonly===true) messageBody.recvonly = "true";
        const message = { method: 'POST', body: JSON.stringify(messageBody) };
        l.v('INIT MSG ->:', message);
        fetch(this.uri, message).then((response) =>{
          response.json().then((responseJson) => {
            l.d('-> Message:', responseJson);
            Object.keys(responseJson).forEach((responseJsonKey) => {
              switch(responseJsonKey){
                case 'iceServers': {
                  Config.rtc.iceServers = responseJson[responseJsonKey];
                  break;
                }
                case 'credential': {break;}
                case 'token': {
                  this.context.token = responseJson[responseJsonKey];
                  break;
                }
                case 'env':{break;}
                case 'sendonly':{break;}
                case 'recvonly':{break;}
                default: { l.e('Init: Unknown property:' + responseJsonKey);}
              }
            });

            l.gEnd();
            this.context.signalingConnection = SignalingConnection({
              url : Config.signalingServer.url,
              context: this.context,
            });
            this.context.peerConnection = new RTCPeerConnection(Config.rtc);
            //Config.rtc.iceServers = null;
            this.context.hasAddTrack = (this.context.peerConnection.addTrack !== undefined);
            bindSignalingConnectionEvents({
              context:this.context,
              media:this.media,
            });
            bindPeerConnectionEvents({
              context:this.context,
              media:this.media,
            });
            if (this.context.useVideo){
              if (typeof this.config.view.local !==undefined)
                Config.rtc.localVideo = document.querySelector(`${this.config.view.local}`);
              this.context.remoteVideo = document.querySelector(`${this.config.view.remote}`);
              this.media.createLocalStream(this.context, this.config.media);
            }
            if (this.config.media.recvonly){
              this.context.remoteVideo = document.querySelector(`${this.config.view.remote}`);
            }
          }).catch((error) =>{
            if (this.context.eventManager.hasEventListener('onError')) { this.context.eventManager.dispatchEvent('onError', 'WebSocketFailedError'); }
            l.e('Init: failed:', error);
          });
        }).catch((error) => {
          if (this.context.eventManager.hasEventListener('onError')) { this.context.eventManager.dispatchEvent('onError', 'WebSocketFailedError'); }
          l.e('Init: failed', error);
        });
      }

      /**
      * 이미 개설된 채널(방)에 입장하거나 이미 개설된 방이 없으면 입력한 방 이름으로 방을 개설한다.
      * 사용예: remon.connectChannel("roomname1");
      */
      connectChannel(...args) {
        return this.context.signalingConnection.connectChannel(...args);
      }
      /**
      * 방송을 위한 방을 개설한다.
      */
      createChannel(args){
        if (this.config.media.sendonly === true){
          //this.context.peerConnection.createPresenterOffer();
          l.w("createBroadcastChannel is called");
          this.context.signalingConnection.createBroadcastChannel(args);
        }else if (this.config.media.recvonly === true){
          this.context.signalingConnection.createViewerChannel(args);
        }
      }
      /**
      * 현재 통신 상태정보를 가져온다.
      */
      getHealth(){
        return this.context.health.result;
      }
      /**
      * 현재 sdk 버전 정보를 가져온다
      */
      getVersion() {
        return this.version;
      }
      /**
      * 현재 접속한 채널의 id(방이름)를 가져온다
      */
      getChannelId() {
        return this.context.channel.id;
      }
      /**
      * 현재 연결을 끊는다.
      */
      disconnect(){
        this.close();
      }
      /**
      * 자신의 영상을 잠시 멈춘다.
      * @param (bool) bool true이면 멈추고 아니면 보인다
      */
      pauseLocalVideo(bool) {
        this.media.mediaStreamTrackSwitch(Config.rtc.localStream).type('Video').enabled(!!bool);
      }
      /**
      * 상대의 영상을 잠시 멈춘다.
      * @param (bool) bool true이면 멈추고 아니면 보인다
      */
      pauseRemoteVideo(bool) {
        this.media.mediaStreamTrackSwitch(this.context.remoteStream).type('Video').enabled(!!bool);
      }

      cameraSwitch() {
        this.media.setUserDevices(null, this.context.devices[1].deviceId);
      }
      muteLocalAudio(bool) {
        this.media.mediaStreamTrackSwitch(Config.rtc.localStream).type('Audio').enabled(!!bool);
      }

      muteRemoteAudio(bool) {
        this.media.mediaStreamTrackSwitch(this.context.remoteStream).type('Audio').enabled(!!bool);
      }
      /**
      * 특정 방 이름을 검색하여 그 목록을 가져온다.
      * @param (string) id 검색할 방 이름. 부분 검색 가능
      */
      search(id){
        l.i("search by" + id);
    //    const message = this.context.signalingConnection.createMessage({ command: 'search', body: id});
        const message = {
          command:'search',
          token: this.context.token,
          serviceId: this.context.serviceId,
          body: id,
        };

        this.context.signalingConnection.send(JSON.stringify(message));
      }
      /**
      * 통신으로 서로 연결된 상태에서 상대방에게 특정 문자형식의 메시지를 보낼수 있다. 상대편은 RemonObserver의 onMessage에서 메시지를 수신함
      * @param (string) userMessage 상대방에게 보낼 메시지
      */
      sendMessage(userMessage) {
        l.g('Signaling: Send user message');
        const message = this.context.signalingConnection.createMessage({ command: 'message', body: JSON.stringify(userMessage) });
        l.d('Message ->:', message);
        this.context.signalingConnection.send(JSON.stringify(message));
        l.gEnd();
      }

      /**
      * Remon 객체가 가진 모든 자원을 해제함.
      */
      close() {
        l.i("Remon.close");
        if (this.context.useRecord && this.context.remoteRecorder){
          this.context.remoteRecorder.stop();
          this.context.remoteRecorder = null;
        }
        if (this.context.useRecord && this.context.localRecorder){
          this.context.localRecorder.stop();
          this.context.localRecorder = null;
          this.context.useRecord = false;
        }
        if (this.context.remoteVideo.srcObject) {
          this.context.remoteVideo.srcObject.getTracks().forEach(track => track.stop());
        }

        if (!Config.rtc.localVideo && Config.rtc.localVideo.srcObject) {
          Config.rtc.localVideo.srcObject.getTracks().forEach(track => track.stop());
        }
        if (this.context.remoteVideo)
          this.context.remoteVideo.srcObject = null;
        if (!this.context.signalingConnection) return;
        //this.context.localVideo.srcObject = null;
        // FIXME: Chrome, adapter does not support addTrack.
        if (!this.context.peerConnection) return;
        if (this.context.health)
          this.context.health.stop();
        if (this.context.hasAddTrack) {
          this.context.peerConnection.ontrack = null;
        } else {
          this.context.peerConnection.onaddstream = null;
        }
        this.context.peerConnection.onremovestream = null;
        this.context.peerConnection.onicecandidate = null;
        this.context.peerConnection.oniceconnectionstatechange = null;
        this.context.peerConnection.onsignalingstatechange = null;
        this.context.peerConnection.onicegatheringstatechange = null;
        this.context.peerConnection.onnegotiationneeded = null;
        this.context.peerConnection.close();
        this.context.peerConnection = null;
        this.context.signalingConnection.close();
      }
    }

    export default Remon;