# src/SignalingConnectionHandler.js

    import Context from './Context';
    import l from './Logger';
    import util from './Util';
    import signalingStates from './SignalingStates'
    function bindSignalingConnectionEvents({
      context,
      media,
    }) {
      function handleDescription(description) {
        l.g('Signaling: Handle local description: Set local description to peerconnection and send to signaling server.');
        l.v('Local Description:', description);
        if (!context.videoCodec){
          l.v("Signaling: video codec: H264");
          description.sdp = replaceCodec(description.sdp, /m=video(:?.*)?/, "H264");
        }else{
          l.v("Signaling: video codec: "+ context.videoCodec);
          description.sdp = replaceCodec(description.sdp, /m=video(:?.*)?/, context.videoCodec);
        }
        const message = context.signalingConnection.createMessage({ command: 'sdp', body: JSON.stringify(description) });

        context.peerConnection.setLocalDescription(description)
          .then(() => {
            l.v('Local Description Setted:', context.peerConnection.localDescription);
            l.v('Message ->:', message);
            context.signalingConnection.send(JSON.stringify(message));
          })
          .catch((error) => {
            l.e('PeerConnection: set Local description failed', error);
            if (context.eventManager.hasEventListener('onError')) {
              var e1 = {
                code:'ConnectChannelFailedError',
                body:'PeerConnection: set Local description failed',
              };
              context.eventManager.dispatchEvent('onError', e1);
            }
          });

        l.gEnd();
      }
      const signalingEvents = {
        onCreate(message) {
          l.g('Signaling: On create channel');
          context.isCaller = false;

          if (context.eventManager.hasEventListener('onStateChange')) { context.eventManager.dispatchEvent('onStateChange', 'WAIT'); }
          if (!message.channel) {
            if (context.eventManager.hasEventListener('onError')) {
              var e1 = {
                code:'ConnectChannelFailedError',
                body:'Can not make a channel',
              };
              context.eventManager.dispatchEvent('onError', e1);
            }

          }

          context.channel = message.channel;
          l.i('Channel id:', context.channel.id);
          l.i('Channel type: ', context.channel.type);

          // if (!peerConnection.localDescription) {
          if (context.channel.type!=="VIEWER")
            media.bindLocalStreamToPeerConnection(context.peerConnection);
          // }

          if (context.eventManager.hasEventListener('onCreateChannel')) {
            context.eventManager.dispatchEvent('onCreateChannel', context.channel.id);
          }
          if (context.channel.type ==='BROADCAST'){
            createPresenterOffer();
          }else if (context.channel.type ==='VIEWER'){
            createViewerOffer();
          }

          l.gEnd();
        },

        onConnect(message) {
          l.g('Signaling: On connect channel');
          context.isCaller = true;
          context.channel = message.channel;
          l.i('Channel id:', context.channel.id);
          l.i('Channel type: ', context.channel.type);
          l.d('isCaller: true');
          if (context.eventManager.hasEventListener('onStateChange')) {
            context.eventManager.dispatchEvent('onStateChange', 'CONNECT');
          }
          if (!message.channel) {
            if (context.eventManager.hasEventListener('onError')) {
              var e1 = {
                code:'ConnectChannelFailedError',
                body:'Can not make a channel',
              };
              context.eventManager.dispatchEvent('onError', e1);
            }
            return;
          }

          // if (!peerConnection.localDescription) {
          media.bindLocalStreamToPeerConnection(context.peerConnection);
          // }

          if (context.eventManager.hasEventListener('onConnectChannel')) {
            context.eventManager.dispatchEvent('onConnectChannel', context.channel.id);
          }

          l.i('Create offer');

          const offerOptions = {
            offerToReceiveAudio: 1,
            offerToReceiveVideo: 1
          };

          context.peerConnection.createOffer(offerOptions)
            .then(handleDescription)
            .catch((error) => {
              l.e('PeerConnection: Create offer failed:', error);
              if (context.eventManager.hasEventListener('onError')) {
                var e1 = {
                  code:'ICEFailedError',
                  body:'PeerConnection: Create offer failed',
                };
                context.eventManager.dispatchEvent('onError', e1);
                return;
              }
            });

          l.gEnd();
        },

        onSdp(message) {
          l.g('Signaling: On sdp');

          const description = new RTCSessionDescription(JSON.parse(message.body));
          l.i('-> Remote Description:', description);

          context.peerConnection.setRemoteDescription(description)
            .then(() => {
              l.i('Remote Description Setted');
            })
            .catch((error) => {
              l.e('PeerConnection: Remote description set failed:', error);
              if (context.eventManager.hasEventListener('onError')) {
                var e1 = {
                  code:'ICEFailedError',
                  body:'PeerConnection: Remote description set failed',
                };
                context.eventManager.dispatchEvent('onError', e1);
                return;
              }
            });

          l.i('Am I a caller?:', context.isCaller);
          if (!context.isCaller && context.channel.type !=="BROADCAST" && context.channel.type !=="VIEWER") {
            l.i('Create answer');
            context.peerConnection.createAnswer()
              .then(handleDescription)
              .catch((error) => {
                l.e('PeerConnection: Create Answer failed:', error);
                if (context.eventManager.hasEventListener('onError')) {
                  var e1 = {
                    code:'ICEFailedError',
                    body:'PeerConnection: Create Answer failed',
                  };
                  context.eventManager.dispatchEvent('onError', e1);
                  return;
                }
              });
          }

          l.gEnd();
        },
        onDisconnectChannel(message){
          l.i('Signaling: onDisconnectChannel');
          context.health.stop();
          context.peerConnection.close();
          context.signalingConnection.close();
          if (context.eventManager.hasEventListener('onDisconnectChannel')) { context.eventManager.dispatchEvent('onDisconnectChannel'); }
        },
        onStateChange(message) {
          l.g('Signaling: On state change');

          if (!signalingStates.includes(message.body)) {
            l(l.e('Unknown signaling state:'+message.body));
          } else {
            context.state = message.body;
            if (context.eventManager.hasEventListener('onStateChange')) {
              context.eventManager.dispatchEvent('onStateChange', message.body);
            }

            switch (message.body) {
              case 'INIT': { l.i('>STATE:INIT');break; }
              case 'WAIT': { l.i('>STATE:WAIT');break; }
              case 'CONNECT': { l.i('>STATE:CONNECT');break; }
              case 'COMPLETE': {
                l.i('>STATE:COMPLETE');
                if (context.eventManager.hasEventListener('onStateChange')) { context.eventManager.dispatchEvent('onStateChange', 'COMPLETE'); }
                if (context.eventManager.hasEventListener('onComplete')) { context.eventManager.dispatchEvent('onComplete'); }
                break;
              }
              case 'CLOSE': {
                l.i('>STATE:CLOSE');
                context.health.stop();
                context.peerConnection.close();
                context.signalingConnection.close();

                if (context.eventManager.hasEventListener('onStateChange')) { context.eventManager.dispatchEvent('onStateChange', 'CLOSE'); }
                //if (context.eventManager.hasEventListener('onDisconnectChannel')) { context.eventManager.dispatchEvent('onDisconnectChannel'); }
                break;
              }
              case 'FAIL': { l.i('>STATE:FAIL');break; }
              default: { break; }
            }
          }

          l.gEnd();
        },

        onIce(message) {
          l.g('Signaling: On ice');

          const candidate = new RTCIceCandidate(JSON.parse(message.body));
          l.v('Candidate:', JSON.stringify(candidate));

          context.peerConnection.addIceCandidate(candidate)
            .then(() => { l.v('Add ICE candidate success'); })
            .catch((error) => {
              l.e('Peer Connection: Add ICE candidate failed', error);
              if (context.eventManager.hasEventListener('onError')) {
                var e1 = {
                  code:'ICEFailedError',
                  body:'Peer Connection: Add ICE candidate failed',
                };
                context.eventManager.dispatchEvent('onError', e1);
              }
            });

          l.gEnd();
        },

        onMessage(message) {
          l.v('Signaling: On message: ' + message.body);
          if (context.eventManager.hasEventListener('onMessage')) {
            context.eventManager.dispatchEvent('onMessage', message.body);
          }
        },
        onSearch(message){
          l.v('Signaling: On search: '+ message.body);
          if (context.eventManager.hasEventListener('onSearch')) {
            context.eventManager.dispatchEvent('onSearch', message.body);
          }
        },

        onError(message) {
          l.e('Signaling error -> Message:', message);
          if (context.eventManager.hasEventListener('onError')) { context.eventManager.dispatchEvent('onError', message); }
        },
      };
      function createViewerOffer(){
        l.i("createViewerOffer is called");
        // const offerOptions = {
        //   offerToReceiveAudio: 1,
        //   offerToReceiveVideo: 1
        // };
        const offerOptions= {
          offerToReceiveAudio: 1,
          offerToReceiveVideo: 1

        };
        l.i("with option:");
        l.i( offerOptions);
        context.peerConnection.createOffer(offerOptions)
          .then(handleDescription)
          .catch((error) => {
            l.e('PeerConnection: Create offer failed:', error);
            if (context.eventManager.hasEventListener('onError')) {
              var e1 = {
                code:'ICEFailedError',
                body:'PeerConnection: Create offer failed',
              };
              context.eventManager.dispatchEvent('onError', e1);
              return;
            }
          });
      }
      function createPresenterOffer(){
        const offerOptions = {
          offerToReceiveAudio: false,
          offerToReceiveVideo: false
        };

        context.peerConnection.createOffer(offerOptions)
          .then(handleDescription)
          .catch((error) => {
            l.e('PeerConnection: Create offer failed:', error);
            if (context.eventManager.hasEventListener('onError')) {
              var e1 = {
                code:'ICEFailedError',
                body:'PeerConnection: Create offer failed',
              };
              context.eventManager.dispatchEvent('onError', e1);
              return;
            }
          });
      }

      function handleDescription(description) {
        l.g('Signaling: Handle local description: Set local description to peerconnection and send to signaling server.');
        l.i('Local Description:', description);
        if (!context.videoCodec){
          l.v("Signaling: video codec: H264");
          description.sdp = replaceCodec(description.sdp, /m=video(:?.*)?/, "H264");
        }else{
          l.v("Signaling: video codec: "+ context.videoCodec);
          description.sdp = replaceCodec(description.sdp, /m=video(:?.*)?/, context.videoCodec);
        }
        const message = context.signalingConnection.createMessage({ command: 'sdp', body: JSON.stringify(description) });
        message.channel.type=context.channel.type;
        context.peerConnection.setLocalDescription(description)
          .then(() => {
            l.v('Local Description Setted:', context.peerConnection.localDescription);
            l.i('Message ->:', message);
            context.signalingConnection.send(JSON.stringify(message));
          })
          .catch((error) => {
            l.e('PeerConnection: set Local description failed', error);
            if (context.eventManager.hasEventListener('onError')) {
              var e1 = {
                code:'ConnectChannelFailedError',
                body:'PeerConnection: set Local description failed',
              };
              context.eventManager.dispatchEvent('onError', e1);
            }
          });

        l.gEnd();
      }

      function handleMessageEvent(event) {
        l.d('Signaling: Got command from server');

        const message = JSON.parse(event.data);
        const type = message.command;

        l.v(`-> Message: ${message.command}:`, message);

        signalingEvents[type](message);

      }

      function replaceCodec(sdp, mLineReg, preferCodec){
    		var mLine,
    			newMLine = [],
    			sdpCodec,
    			mLineSplit,
    			reg = new RegExp("a=rtpmap:(\\d+) " + preferCodec + "/\\d+");

    		mLine = sdp.match(mLineReg);
    		if(!mLine){
    			return sdp;
    		}

    		sdpCodec = sdp.match(reg);
    		if(!sdpCodec){
    			return sdp;
    		}

    		mLine = mLine[0];
    		sdpCodec = sdpCodec[1];

    		mLineSplit = mLine.split(" ");
    		newMLine.push(mLineSplit[0]);
    		newMLine.push(mLineSplit[1]);
    		newMLine.push(mLineSplit[2]);
    		newMLine.push(sdpCodec);

    		for(var i=3; i<mLineSplit.length; i++){
    			if(mLineSplit[i] !== sdpCodec){
    				newMLine.push(mLineSplit[i]);
    			}
    		}

    		return sdp.replace(mLine, newMLine.join(" "));
    	}

      context.signalingConnection.onMessage(handleMessageEvent);
    }

    export default bindSignalingConnectionEvents;