 /**
 * Created by Gifer on 28.01.2016.
 */

 function MediaCall(){
     "use strict";

     var Self      = this;
     var myStream  = null;
     var pcList    = {};
     var pcStreams = {};
     var calleeUIN = null;
     var failTimer = null;
     var dialingTimer = null;
     var pc        = null;
     var mirrorContainer = null;
     var calleeContainer = null;
     var devices   = {

     };
     var deviceList= {};
     var iceRestart= false;
     var isWeb     = location.protocol !== "file:";
     var isMyCall  = false;

     var CALLTYPE = {
         VOICE  : 0,
         VIDEO  : 1,
         SHARING: 2
     };
     var recordOpt = {
         mimeType: 'video/webm; codecs=vp8'
     };
     var getDefaultConfig  = function () {
         return {
             iceTransportPolicy : "all",
             iceTransport : "all",
             iceServers : []
         }
     };
     var configuration  = getDefaultConfig();
     var streamBackup   = null;
     var streamAssigned = false;
     var isDisconnectICE= false;
     var screenID       = "screen:0:0";
     var callType       = CALLTYPE.VOICE;

     var callStreams  = {
         income : null,
         my     : null,
         get    : function () {
             var res = [];

             if (callStreams.income){
                 res.push(callStreams.income);
             }

             if (callStreams.my){
                 res.push(callStreams.my);
             }

             return res;
         }
     }

     // ===========================================================

     this.onFailedInit  = null;
     this.onError       = null;
     this.sendAnswer    = null;
     this.sendOffer     = null;
     this.sendCandidate = null;
     this.showMedia     = null;
     this.iceClosed     = null;
     this.onDisconnected   = null;
     this.onRestoreConnect = null;

     this.restoreCall   = true;
     this.useVideo      = false;
     this.MID           = -1;
     this.initFailTime  = 95000;
     this.Share         = false;
     this.receiveScreen = false;

     // ===========================================================

     function getUserMedia(opt, ok, err) {
         if (navigator.getUserMedia) {
             navigator.getUserMedia(opt, ok, err);
         } else
         if (navigator.mediaDevices && (typeof navigator.mediaDevices.getUserMedia !== 'undefined')) {
             navigator.mediaDevices.getUserMedia(opt).then(ok).catch(err);
         } else {
             console.error("Hasn't getUserMedia!");
         }
     }

     function isArray(obj){
         return Object.prototype.toString.call( obj ) === '[object Array]';
     }

     function isObject(obj) {
         return Object.prototype.toString.call( obj ) === '[object Object]';
     }

     function onIceStateChange(__pc) { // event
         if (__pc) {
             if ( __pc.iceConnectionState === 'connected' || __pc.iceConnectionState === 'completed') {
                 __pc.getStats(null).then(function(results) {
                     // figure out the peer's ip
                     var activeCandidatePair = null;
                     var remoteCandidate = null;

                     // search for the candidate pair
                     Object.keys(results).forEach(function(result) {
                         var report = results[result];

                         if (report.type === 'candidatepair' && report.selected || report.type === 'googCandidatePair' &&
                             report.googActiveConnection === 'true') {
                             activeCandidatePair = report;
                         }
                     });

                     if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {
                         Object.keys(results).forEach(function(result) {
                             var report = results[result];

                             if (report.type === 'remotecandidate' && report.id === activeCandidatePair.remoteCandidateId) {
                                 remoteCandidate = report;
                             }
                         });
                     }

                     if (remoteCandidate && remoteCandidate.ipAddress && remoteCandidate.portNumber) {
                         console.log('[onIceStateChange]: Remote address changed to ' + remoteCandidate.ipAddress + ":" + remoteCandidate.portNumber);
                     }
                 });
             }
         }
     }
     
     function assignStream(callee) {
         if (streamBackup && streamBackup.stream){
             var videoContainer = calleeContainer || document.createElement('video');

             videoContainer.className = "VideoCaller";
             videoContainer.autoplay  = true;
             videoContainer.srcObject = streamBackup.stream;

             videoContainer.onloadedmetadata = function (e) {
                 videoContainer.play();
             };

             if (typeof videoContainer.sinkId !== 'undefined' && devices.sound) {
                 setTimeout(function () {
                     videoContainer.setSinkId(devices.sound).then(function () {
                         console.log("Set sound device: " + devices.sound);
                     }).catch(function (error) {
                         console.error(error.message);
                     });
                 }, 10);
             }/* else {
                 console.log('NO sinkId!');
             }*/
             
             pcStreams[callee] = videoContainer;

             streamAssigned = true;
         }

         console.log('AssignStream iceConnectionState: ' + pc.iceConnectionState);
         console.log(streamBackup);

         // window.___callStream = streamBackup;  // todo: remove
     }

     function offer(callee, restart) {
         pc.createOffer({
             offerToReceiveAudio: true,
             offerToReceiveVideo: true,
             iceRestart         : restart || iceRestart
         }).then( function (desc) {
             pc.setLocalDescription(desc);

             Self.sendOffer(callee, Self.MID, JSON.stringify(desc));
         }, function(err){
             Self.hangUp('CreateOffer: ' + JSON.stringify(err));
         });
     }

     function recall(callee, __pc, restart, _now) {
         if (dialingTimer) {
             clearTimeout(dialingTimer);
         }

         if (restart){
             dialingTimer = setTimeout(function () {
                 dialingTimer = null;

                 if (__pc.iceConnectionState === 'checking' || __pc.iceConnectionState === 'failed'){ // disconnected
                     console.log("Recall on IceConnectionState:" + __pc.iceConnectionState);

                     offer(callee, restart);
                 } else {
                     console.log("Start recall");

                     recall(callee, __pc, !restart); // true
                 }
             }, _now ? 0 : 4000);
         }
     }

     function getScreenThumbs(cb) {
         if (!isWeb) {
             var desktopCapturer = require('electron').desktopCapturer;
             var res = {};

             desktopCapturer.getSources({ types: ['screen'] })
             .then(function (sources) {
                 for (var source in sources) {
                     var img = document.createElement("img");
                     img.src = source.thumbnail.toDataURL();

                     res[source.id] = img;
                 }
                 
                 cb(res);
             });
         }
     }
     
     function gotDevices(allDevices) {
         deviceList = {
             audioinput  : [],
             audiooutput : [],
             videoinput  : []
         };

         for (var i = 0; i !== allDevices.length; ++i) {
             var deviceInfo = allDevices[i];

             if (deviceList[deviceInfo.kind]) {
                 deviceList[deviceInfo.kind].push({
                     deviceId : deviceInfo.deviceId,
                     label    : deviceInfo.label
                 });
             }
         }

         console.log(deviceList);
     }

     function handleError(error) {
         console.log('navigator.getUserMedia error: ', error);
     }

     function startCall(callee, sdp) {
         var createOffer = !sdp;

         isDisconnectICE = false;

         pc = pcList[callee] || new RTCPeerConnection(configuration);

         console.log("Start call config: " + JSON.stringify(configuration));

         if (!pcList[callee]) {
             pcList[callee] = pc;

             // navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);

             pc.oniceconnectionstatechange = function(evt) {
                 console.log("ICE connection state change: " + evt.target.iceConnectionState);

                 if (pc.iceConnectionState === 'disconnected') {
                     iceRestart = Self.restoreCall;

                     isDisconnectICE = true;

                     if (iceRestart) {
                         recall(callee, pc, iceRestart, true);
                     } else
                     if (Self.onDisconnected) {
                         Self.onDisconnected(callee);
                     }
                 } else
                 if (pc.iceConnectionState === 'closed' && Self.iceClosed) {
                     Self.iceClosed();
                 } else

                 if (pc.iceConnectionState === 'failed'){
                     iceRestart = Self.restoreCall;

                     if (iceRestart) {
                         recall(callee, pc, iceRestart, true);
                     } else {
                         runError('Error: ICE Connection State FAILED!', 1);
                     }
                 } else

                 if ((pc.iceConnectionState === 'connected' || pc.iceConnectionState === 'completed') && Self.showMedia){
                     clearTimeout(failTimer);
                     clearTimeout(dialingTimer);

                     if (isDisconnectICE){
                         isDisconnectICE = false;

                         if (Self.onRestoreConnect){
                             Self.onRestoreConnect(callee);
                         }
                     }

                     dialingTimer = null;
                     failTimer    = null;

                     onIceStateChange(pc, evt);

                     if (!iceRestart) {
                         callStreams.income = streamBackup && streamBackup.stream ? streamBackup.stream : null;
                         callStreams.my     = myStream;

                         Self.showMedia.apply(null, [pcStreams[callee], streamBackup, myStream]);
                     }

                     if (!streamAssigned){
                         assignStream(callee);
                     }
                 }
             };

             pc.onicecandidateerror = function(err) {
                 //runError('OnIceCandidateError: ' + JSON.stringify(err), 1);
                 console.log('OnIceCandidateError (code:' + err.errorCode + ") " + err.errorText);
                 console.log('hostCandidate: ' + err.hostCandidate);
                 console.log('url ' + err.url);
             };

             pc.onicecandidate = function (evt) { // send any ice candidates to the other peer
                 //console.log("ICE gathering state change: " + evt.target.iceGatheringState);

                 if (evt.candidate && evt.candidate.candidate.indexOf(' tcp ') === -1) {
                     console.log("OUTGOING ICE: " + evt.candidate.candidate);

                     Self.sendCandidate(callee, Self.MID, JSON.stringify(evt.candidate));
                 }
             };

             pc.onerror = function(err) {
                 console.log(err);
             };

             pc.onaddstream = function (evt) {
                 streamBackup = evt;

                 console.log('Get Remote Stream complete!');
                 // console.log(streamBackup);
             };

             if (Self.useVideo){
             // if (callType === CALLTYPE.VIDEO){
                 if (Object.prototype.toString.call( mirrorContainer ) === '[object String]'){
                     mirrorContainer = document.getElementById(mirrorContainer);
                 }

                 if (['[object HTMLVideoElement]', '[object HTMLAudioElement]'].indexOf(Object.prototype.toString.call( mirrorContainer )) === -1){
                     // console.error('\"mirrorContainer\" is not a HTML element!');

                     Self.hangUp('\"mirrorContainer\" is not a HTML element!');

                     return false;
                 } else
                 if (myStream){
                     mirrorContainer.srcObject = myStream;
                 }
             }

             if (myStream) pc.addStream(myStream);
         }

         console.log("ICE  GatheringState: " + pc.iceGatheringState);
         console.log("ICE ConnectionState: " + pc.iceConnectionState);

         if (createOffer){
             offer(callee);

             recall(callee, pc, true, false);
         } else {
             if (!isObject(sdp)){
                 sdp = JSON.parse(sdp);
             }

             pc.setRemoteDescription(new RTCSessionDescription(sdp));

             pc.createAnswer(function (desc) {
                 pc.setLocalDescription(desc);

                 Self.sendAnswer(callee, Self.MID, JSON.stringify(desc));
             }, function(err){
                 Self.hangUp('CreateAnswer: ' + JSON.stringify(err));
             });
         }

         return pc;
     }

     function runError() {
         clearTimeout(failTimer);

         if (Self.onError){
             Self.onError.apply(null, arguments);
         }
     }

     function getScreen(nextStep) {
         if (!isWeb) {
             getUserMedia({
                 audio : false,
                 video : {
                     mandatory: {
                         chromeMediaSource  : 'desktop',
                         chromeMediaSourceId: screenID, // "screen:0:0",
                         minWidth           : 160,
                         minHeight          : 120,
                         maxWidth           : 1920,
                         maxHeight          : 1080,
                         minFrameRate       : 5,
                         maxFrameRate       : 10
                     }
                 }
             }, function successGetMedia(screenStream) {
                 // console.log("GetMedia screen: " + JSON.stringify(shareStream));
                 if (!myStream) {
                     myStream = screenStream;
                 } else {
                     myStream.addTrack(screenStream.getVideoTracks()[0]);
                 }

                 var tracks = screenStream.getVideoTracks();

                 if (tracks && tracks[0]) {
                     tracks[0].enabled = !!Self.Share; // callType === CALLTYPE.SHARING && isMyCall; //!!Self.Share;
                 }

                 nextStep();
             }, function errorGetMedia(err){
                 console.log('Error getting screen stream: ' + JSON.stringify(err));

                 if (myStream){
                     nextStep();
                 } else {
                     Self.hangUp("No media devices");
                 }
             });
         } else {
             if (myStream){
                 nextStep();
             } else {
                 Self.hangUp("No media devices");
             }
         }
     }

     function GetMedia(cb){
         if (!myStream){
             var opt = {
                 "audio": {
                     optional: [
                         { echoCancellation: true },
                         { noiseSuppression: true },
                         { autoGainControl: true },
                         { googEchoCancellation: true },
                         { googEchoCancellation2: true },
                         { googNoiseSuppression: true },
                         { googNoiseSuppression2: true },
                         { googAutoGainControl: true },
                         { googAutoGainControl2: true },
                         { googHighpassFilter: true },
                         { googAudioMirroring: false },
                     ].concat(devices && devices.microphone ? {sourceId: devices.microphone} : [])
                 },
                 "video": Self.useVideo //callType === CALLTYPE.VIDEO //
                     ? {
                         mandatory: {
                             minWidth : 640,
                             minHeight: 480,

                             maxWidth : 1280,
                             maxHeight: 720,

                             minAspectRatio: 4/3,
                             maxAspectRatio: 16/9,

                             minFrameRate  : 15,
                             maxFrameRate  : 25
                         },
                         optional: [].concat(/*callType === CALLTYPE.VIDEO*/ Self.useVideo && devices.camera ? { sourceId: devices.camera } : [])
                     } 
                     : false
             };

             console.log('GetMedia config: ' + JSON.stringify(opt));

             if (!failTimer){
                 failTimer = setTimeout(function () {
                     failTimer = null;

                     if (Self.onFailedInit){
                         Self.onFailedInit();
                     }

                     Self.hangUp("initFailTime");
                 }, Self.initFailTime);
             }

             getUserMedia(opt, function successGetMedia(stream) {
                 console.log('getUserMedia: Media stream get successfully');

                 myStream = stream;

                 if (!myStream){
                     console.log('Getting empty stream!');
                 }

                 getScreen(cb);
             }, function errorGetMedia(err){
                 myStream = new MediaStream();

                 if (err.name === "NotFoundError" && Self.Share){
                     getScreen(cb);
                 } else
                 if (isMyCall){
                     console.error("Error getting media stream: hasn't camera or microphone");

                     cb();
                 }
                 else {
                     cb();
                 }
             })
         } else {
             cb();
         }
     }

     // ===========================================================

     this.getScreenThumbs = getScreenThumbs;

     this.setScreenId   = function (id){
         screenID = id || "screen:0:0";
     };

     this.addIceServers = function (srv, iceTransportPolicy){
         configuration.iceServers         = srv || [];
         configuration.iceTransportPolicy = iceTransportPolicy || "all";
         configuration.iceTransport       = iceTransportPolicy || "all";
     };

     this.setCallInfo   = function (_callType/*video, share*/, MID) {
         callType = parseInt(_callType);
         Self.useVideo       = callType === CALLTYPE.VIDEO;
         Self.receiveScreen  = callType === CALLTYPE.SHARING;
         Self.MID            = MID;
     };

     this.setDevices    = function (microphome, camera, sound) {
         if (isObject(microphome)){
             devices = {
                 microphone : microphome.microphome,
                 camera     : microphome.camera,
                 sound      : microphome.sound
                 // sound      : "574c823d189c645f65f6bf51105b970b70e32b37dbc14d45e1ad285d52089bba" // microphome.sound
                 // sound      : "514edac8a4f360b1f3afdda7a5f77907e8c2132e2dcb5dc44fde0584433b8978" // microphome.sound
             };
         } else {
             devices = {
                 microphone : microphome,
                 camera     : camera,
                 sound      : sound
                 // sound      : "574c823d189c645f65f6bf51105b970b70e32b37dbc14d45e1ad285d52089bba" //sound
                 // sound      : "514edac8a4f360b1f3afdda7a5f77907e8c2132e2dcb5dc44fde0584433b8978" //sound
             };
         }

         console.log("DEVICES:" + JSON.stringify(devices));
     };

     this.setMirror     = function (target){
         mirrorContainer = target;
     };

     this.setCallee     = function (target){
         if (Object.prototype.toString.call( target ) === '[object String]'){
             calleeContainer = document.getElementById(target);
         } else {
             calleeContainer = target;
         }

         if (['[object HTMLVideoElement]', '[object HTMLAudioElement]'].indexOf(Object.prototype.toString.call( calleeContainer )) === -1){
             Self.hangUp('\"calleeContainer\" is not a HTML element!');
         }
     };

     this.recvAnswer    = function (data){
         console.log('RECEIVE ANSWER');

         if (!isObject(data.SDP)){
             data.SDP = JSON.parse(data.SDP);
         }

         if (pcList[data.UIN] && pcList[data.UIN].setRemoteDescription){
             pcList[data.UIN].setRemoteDescription(new RTCSessionDescription(data.SDP));
         } else {
             Self.hangUp('error in "RECEIVE ANSWER"');
         }
     };

     this.recvOffer     = function (data){
         console.log('RECEIVE OFFER');

         isMyCall = false;

         GetMedia(function(){
             startCall(data.UIN, data.SDP);
         });
     };

     this.recvCandidate = function (data){
         console.log('RECEIVE CANDIDATE');

         if (pcList[data.UIN] && pcList[data.UIN].addIceCandidate) {
             if (!isObject(data.CANDIDATE)){
                 data.CANDIDATE = JSON.parse(data.CANDIDATE);
             }

             if (configuration.iceTransportPolicy === "all" || data.CANDIDATE.candidate.indexOf("typ relay") > 0) {
                 pcList[data.UIN].addIceCandidate(new RTCIceCandidate(data.CANDIDATE));

                 console.log("INCOMING ICE:" + JSON.stringify(data.CANDIDATE));
             } else {
                 console.log("IGNORING LOCAL ICE:" + JSON.stringify(data.CANDIDATE));
             }
         }
     };

     // === START CALL ===
     this.recvReady     = function (list){
         console.log('RECEIVE READY');

         pc = null;

         calleeUIN = list || calleeUIN;

         if (calleeUIN){
             isMyCall = true;

             GetMedia(function(){
                 if (calleeUIN){
                     if (!isArray(calleeUIN)){
                         calleeUIN = calleeUIN.toString().split(',');
                     }

                     console.log('Call to: ' + calleeUIN);
                     
                     startCall(calleeUIN[0]);
                 }
             });
         }
     };

     this.restartIceNow = function () {
         iceRestart = true;

         startCall(isArray(calleeUIN) ? calleeUIN[0] : calleeUIN);
     };

     this.CallToUser    = function (uin){
         calleeUIN = uin;
     };

     this.switchCamera  = function () {
         var stream = myStream ? myStream.getVideoTracks() : [];

         if (stream.length) {
             stream[0].enabled = !stream[0].enabled;
         }

         return stream && stream[0] ? stream[0].enabled : false;
     };

     this.switchMicrophone = function () {
         var stream = myStream ? myStream.getAudioTracks() : [];

         if (stream.length) {
             stream[0].enabled = !stream[0].enabled;
         }

         return stream && stream[0] ? stream[0].enabled : false;
     };

     this.getCallType   = function (){
         return callType;
     };

     this.getCallStreams = function (){
         return callStreams.get();
     }

     this.hangUp        = function (error){
         console.log('Hang Up Call');

         if (myStream) {
             try {
                 mirrorContainer.pause();
                 mirrorContainer.srcObject = null;
             } catch (e){}

             if (myStream.getTracks) {
                 myStream.getTracks().forEach(function(track){
                     track.stop();
                 });

                 try {
                     myStream.stop();
                 } catch (e) {}
             }
         } else {
             if (Self.iceClosed) {
                 Self.iceClosed();
             }
         }

         for (var i in pcStreams){
         }

         try {
             pcStreams[i].pause();
             pcStreams[i].srcObject.getTracks().forEach(function(track){
                 track.stop();

             });
         } catch (e){}

         for (var i in pcList){
             pcList[i].close();
             pcList[i] = null;
         }

         pcList    = {};
         pcStreams = {};
         myStream  = null;
         calleeUIN = null;
         Self.MID  = -1;

         try {
             pc && pc.close();
         } catch (e){}

         streamBackup   = null;
         streamAssigned = false;
         iceRestart     = false;

         if (calleeContainer){
             calleeContainer.srcObject = null;
         }

         clearTimeout(failTimer);
         failTimer = null;

         if (error){
             runError(error);
         }

         isMyCall = false;

         configuration = getDefaultConfig();
     };
 }
