'use strict';

// Last time updated: 2019-02-20 3:31:29 PM UTC

// _______________
// getStats v1.2.0

// Open-Sourced: https://github.com/muaz-khan/getStats

// --------------------------------------------------
// Muaz Khan     - www.MuazKhan.com
// MIT License   - www.WebRTC-Experiment.com/licence
// --------------------------------------------------

var getStats = function(mediaStreamTrack, callback, interval) {

    var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45';

    (function(that) {
        if (!that) {
            return;
        }

        if (typeof window !== 'undefined') {
            return;
        }

        if (typeof global === 'undefined') {
            return;
        }

        global.navigator = {
            userAgent: browserFakeUserAgent,
            getUserMedia: function() {}
        };

        if (!global.console) {
            global.console = {};
        }

        if (typeof global.console.log === 'undefined' || typeof global.console.error === 'undefined') {
            global.console.error = global.console.log = global.console.log || function() {
                console.log(arguments);
            };
        }

        if (typeof document === 'undefined') {
            /*global document:true */
            that.document = {
                documentElement: {
                    appendChild: function() {
                        return '';
                    }
                }
            };

            document.createElement = document.captureStream = document.mozCaptureStream = function() {
                var obj = {
                    getContext: function() {
                        return obj;
                    },
                    play: function() {},
                    pause: function() {},
                    drawImage: function() {},
                    toDataURL: function() {
                        return '';
                    }
                };
                return obj;
            };

            that.HTMLVideoElement = function() {};
        }

        if (typeof location === 'undefined') {
            /*global location:true */
            that.location = {
                protocol: 'file:',
                href: '',
                hash: ''
            };
        }

        if (typeof screen === 'undefined') {
            /*global screen:true */
            that.screen = {
                width: 0,
                height: 0
            };
        }

        if (typeof URL === 'undefined') {
            /*global screen:true */
            that.URL = {
                createObjectURL: function() {
                    return '';
                },
                revokeObjectURL: function() {
                    return '';
                }
            };
        }

        if (typeof MediaStreamTrack === 'undefined') {
            /*global screen:true */
            that.MediaStreamTrack = function() {};
        }

        if (typeof RTCPeerConnection === 'undefined') {
            /*global screen:true */
            that.RTCPeerConnection = function() {};
        }

        /*global window:true */
        that.window = global;
    })(typeof global !== 'undefined' ? global : null);

    var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;

    if (typeof MediaStreamTrack === 'undefined') {
        MediaStreamTrack = {}; // todo?
    }

    var systemNetworkType = ((navigator.connection || {}).type || 'unknown').toString().toLowerCase();

    var getStatsResult = {
        encryption: 'sha-256',
        audio: {
            send: {
                tracks: [],
                codecs: [],
                availableBandwidth: 0,
                streams: 0,
                framerateMean: 0,
                bitrateMean: 0
            },
            recv: {
                tracks: [],
                codecs: [],
                availableBandwidth: 0,
                streams: 0,
                framerateMean: 0,
                bitrateMean: 0
            },
            bytesSent: 0,
            bytesReceived: 0,
            latency: 0,
            packetsLost: 0
        },
        video: {
            send: {
                tracks: [],
                codecs: [],
                availableBandwidth: 0,
                streams: 0,
                framerateMean: 0,
                bitrateMean: 0
            },
            recv: {
                tracks: [],
                codecs: [],
                availableBandwidth: 0,
                streams: 0,
                framerateMean: 0,
                bitrateMean: 0
            },
            bytesSent: 0,
            bytesReceived: 0,
            latency: 0,
            packetsLost: 0
        },
        bandwidth: {
            systemBandwidth: 0,
            sentPerSecond: 0,
            encodedPerSecond: 0,
            helper: {
                audioBytesSent: 0,
                videoBytestSent: 0
            },
            speed: 0
        },
        results: {},
        connectionType: {
            systemNetworkType: systemNetworkType,
            systemIpAddress: '192.168.1.2',
            local: {
                candidateType: [],
                transport: [],
                ipAddress: [],
                networkType: []
            },
            remote: {
                candidateType: [],
                transport: [],
                ipAddress: [],
                networkType: []
            }
        },
        resolutions: {
            send: {
                width: 0,
                height: 0
            },
            recv: {
                width: 0,
                height: 0
            }
        },
        internal: {
            audio: {
                send: {},
                recv: {}
            },
            video: {
                send: {},
                recv: {}
            },
            candidates: {}
        },
        nomore: function() {
            nomore = true;
        }
    };

    var getStatsParser = {
        checkIfOfferer: function(result) {
            if (result.type === 'googLibjingleSession') {
                getStatsResult.isOfferer = result.googInitiator;
            }
        }
    };

    var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);

    var peer = this;

    if (!(arguments[0] instanceof RTCPeerConnection)) {
        throw '1st argument is not instance of RTCPeerConnection.';
    }

    peer = arguments[0];

    if (arguments[1] instanceof MediaStreamTrack) {
        mediaStreamTrack = arguments[1]; // redundant on non-safari
        callback = arguments[2];
        interval = arguments[3];
    }

    var nomore = false;

    function getStatsLooper() {
        getStatsWrapper(function(results) {
            if (!results || !results.forEach) return;

            results.forEach(function(result) {
                // console.error('result', result);
                Object.keys(getStatsParser).forEach(function(key) {
                    if (typeof getStatsParser[key] === 'function') {
                        try {
                            getStatsParser[key](result);
                        } catch (e) {
                            console.error(e.message, e.stack, e);
                        }
                    }
                });
            });

            try {
                if (peer.iceConnectionState.search(/failed|closed|disconnected/gi) !== -1) {
                    nomore = true;
                }
            } catch (e) {
                nomore = true;
            }

            if (nomore === true) {
                if (getStatsResult.datachannel) {
                    getStatsResult.datachannel.state = 'close';
                }
                getStatsResult.ended = true;
            }

            // allow users to access native results
            getStatsResult.results = results;

            if (getStatsResult.audio && getStatsResult.video) {
                getStatsResult.bandwidth.speed = (getStatsResult.audio.bytesSent - getStatsResult.bandwidth.helper.audioBytesSent) + (getStatsResult.video.bytesSent - getStatsResult.bandwidth.helper.videoBytesSent);
                getStatsResult.bandwidth.helper.audioBytesSent = getStatsResult.audio.bytesSent;
                getStatsResult.bandwidth.helper.videoBytesSent = getStatsResult.video.bytesSent;
            }

            callback(getStatsResult);

            // second argument checks to see, if target-user is still connected.
            if (!nomore) {
                typeof interval != undefined && interval && setTimeout(getStatsLooper, interval || 1000);
            }
        });
    }

    // a wrapper around getStats which hides the differences (where possible)
    // following code-snippet is taken from somewhere on the github
    function getStatsWrapper(cb) {
        // if !peer or peer.signalingState == 'closed' then return;

        if (typeof window.InstallTrigger !== 'undefined' || isSafari) { // maybe "isEdge?"
            peer.getStats((window.mediaStreamTrack || null),function(res) {
                var items = [];
                res.forEach(function(r) {
                    items.push(r);
                });
                cb(items);
            }, cb);
        } else {
            peer.getStats(function(res) {
                var items = [];
                res.result().forEach(function(res) {
                    var item = {};
                    res.names().forEach(function(name) {
                        item[name] = res.stat(name);
                    });
                    item.id = res.id;
                    item.type = res.type;
                    item.timestamp = res.timestamp;
                    items.push(item);
                });
                cb(items);
            });
        }
    };

    getStatsParser.datachannel = function(result) {
        if (result.type !== 'datachannel') return;

        getStatsResult.datachannel = {
            state: result.state // open or connecting
        }
    };

    getStatsParser.googCertificate = function(result) {
        if (result.type == 'googCertificate') {
            getStatsResult.encryption = result.googFingerprintAlgorithm;
        }

        // Safari-11 or higher
        if (result.type == 'certificate') {
            // todo: is it possible to have different encryption methods for senders and receivers?
            // if yes, then we need to set:
            //    getStatsResult.encryption.local = value;
            //    getStatsResult.encryption.remote = value;
            getStatsResult.encryption = result.fingerprintAlgorithm;
        }
    };

    getStatsParser.checkAudioTracks = function(result) {
        if (result.mediaType !== 'audio') return;

        var sendrecvType = result.id.split('_').pop();
        if (result.isRemote === true) {
            sendrecvType = 'recv';
        }
        if (result.isRemote === false) {
            sendrecvType = 'send';
        }

        if (!sendrecvType) return;

        if (getStatsResult.audio[sendrecvType].codecs.indexOf(result.googCodecName || 'opus') === -1) {
            getStatsResult.audio[sendrecvType].codecs.push(result.googCodecName || 'opus');
        }

        if (!!result.bytesSent) {
            var kilobytes = 0;
            if (!getStatsResult.internal.audio[sendrecvType].prevBytesSent) {
                getStatsResult.internal.audio[sendrecvType].prevBytesSent = result.bytesSent;
            }

            var bytes = result.bytesSent - getStatsResult.internal.audio[sendrecvType].prevBytesSent;
            getStatsResult.internal.audio[sendrecvType].prevBytesSent = result.bytesSent;

            kilobytes = bytes / 1024;
            getStatsResult.audio[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
            getStatsResult.audio.bytesSent = kilobytes.toFixed(1);
        }

        if (!!result.bytesReceived) {
            var kilobytes = 0;
            if (!getStatsResult.internal.audio[sendrecvType].prevBytesReceived) {
                getStatsResult.internal.audio[sendrecvType].prevBytesReceived = result.bytesReceived;
            }

            var bytes = result.bytesReceived - getStatsResult.internal.audio[sendrecvType].prevBytesReceived;
            getStatsResult.internal.audio[sendrecvType].prevBytesReceived = result.bytesReceived;

            kilobytes = bytes / 1024;

            // getStatsResult.audio[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
            getStatsResult.audio.bytesReceived = kilobytes.toFixed(1);
        }

        if (result.googTrackId && getStatsResult.audio[sendrecvType].tracks.indexOf(result.googTrackId) === -1) {
            getStatsResult.audio[sendrecvType].tracks.push(result.googTrackId);
        }

        // calculate latency
        if (!!result.googCurrentDelayMs) {
            var kilobytes = 0;
            if (!getStatsResult.internal.audio.prevGoogCurrentDelayMs) {
                getStatsResult.internal.audio.prevGoogCurrentDelayMs = result.googCurrentDelayMs;
            }

            var bytes = result.googCurrentDelayMs - getStatsResult.internal.audio.prevGoogCurrentDelayMs;
            getStatsResult.internal.audio.prevGoogCurrentDelayMs = result.googCurrentDelayMs;

            getStatsResult.audio.latency = bytes.toFixed(1);

            if (getStatsResult.audio.latency < 0) {
                getStatsResult.audio.latency = 0;
            }
        }

        // calculate packetsLost
        if (!!result.packetsLost) {
            var kilobytes = 0;
            if (!getStatsResult.internal.audio.prevPacketsLost) {
                getStatsResult.internal.audio.prevPacketsLost = result.packetsLost;
            }

            var bytes = result.packetsLost - getStatsResult.internal.audio.prevPacketsLost;
            getStatsResult.internal.audio.prevPacketsLost = result.packetsLost;

            getStatsResult.audio.packetsLost = bytes.toFixed(1);

            if (getStatsResult.audio.packetsLost < 0) {
                getStatsResult.audio.packetsLost = 0;
            }
        }
    };

    getStatsParser.checkVideoTracks = function(result) {
        if (result.mediaType !== 'video') return;

        var sendrecvType = result.id.split('_').pop();
        if (result.isRemote === true) {
            sendrecvType = 'recv';
        }
        if (result.isRemote === false) {
            sendrecvType = 'send';
        }

        if (!sendrecvType) return;

        if (getStatsResult.video[sendrecvType].codecs.indexOf(result.googCodecName || 'VP8') === -1) {
            getStatsResult.video[sendrecvType].codecs.push(result.googCodecName || 'VP8');
        }

        if (!!result.bytesSent) {
            var kilobytes = 0;
            if (!getStatsResult.internal.video[sendrecvType].prevBytesSent) {
                getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent;
            }

            var bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevBytesSent;
            getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent;

            kilobytes = bytes / 1024;

            getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
            getStatsResult.video.bytesSent = kilobytes.toFixed(1);
        }

        if (!!result.bytesReceived) {
            var kilobytes = 0;
            if (!getStatsResult.internal.video[sendrecvType].prevBytesReceived) {
                getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived;
            }

            var bytes = result.bytesReceived - getStatsResult.internal.video[sendrecvType].prevBytesReceived;
            getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived;

            kilobytes = bytes / 1024;
            // getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
            getStatsResult.video.bytesReceived = kilobytes.toFixed(1);
        }

        if (result.googFrameHeightReceived && result.googFrameWidthReceived) {
            getStatsResult.resolutions[sendrecvType].width = result.googFrameWidthReceived;
            getStatsResult.resolutions[sendrecvType].height = result.googFrameHeightReceived;
        }

        if (result.googFrameHeightSent && result.googFrameWidthSent) {
            getStatsResult.resolutions[sendrecvType].width = result.googFrameWidthSent;
            getStatsResult.resolutions[sendrecvType].height = result.googFrameHeightSent;
        }

        if (result.googTrackId && getStatsResult.video[sendrecvType].tracks.indexOf(result.googTrackId) === -1) {
            getStatsResult.video[sendrecvType].tracks.push(result.googTrackId);
        }

        if (result.framerateMean) {
            getStatsResult.bandwidth.framerateMean = result.framerateMean;
            var kilobytes = 0;
            if (!getStatsResult.internal.video[sendrecvType].prevFramerateMean) {
                getStatsResult.internal.video[sendrecvType].prevFramerateMean = result.bitrateMean;
            }

            var bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevFramerateMean;
            getStatsResult.internal.video[sendrecvType].prevFramerateMean = result.framerateMean;

            kilobytes = bytes / 1024;
            getStatsResult.video[sendrecvType].framerateMean = bytes.toFixed(1);
        }

        if (result.bitrateMean) {
            getStatsResult.bandwidth.bitrateMean = result.bitrateMean;
            var kilobytes = 0;
            if (!getStatsResult.internal.video[sendrecvType].prevBitrateMean) {
                getStatsResult.internal.video[sendrecvType].prevBitrateMean = result.bitrateMean;
            }

            var bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevBitrateMean;
            getStatsResult.internal.video[sendrecvType].prevBitrateMean = result.bitrateMean;

            kilobytes = bytes / 1024;
            getStatsResult.video[sendrecvType].bitrateMean = bytes.toFixed(1);
        }

        // calculate latency
        if (!!result.googCurrentDelayMs) {
            var kilobytes = 0;
            if (!getStatsResult.internal.video.prevGoogCurrentDelayMs) {
                getStatsResult.internal.video.prevGoogCurrentDelayMs = result.googCurrentDelayMs;
            }

            var bytes = result.googCurrentDelayMs - getStatsResult.internal.video.prevGoogCurrentDelayMs;
            getStatsResult.internal.video.prevGoogCurrentDelayMs = result.googCurrentDelayMs;

            getStatsResult.video.latency = bytes.toFixed(1);

            if (getStatsResult.video.latency < 0) {
                getStatsResult.video.latency = 0;
            }
        }

        // calculate packetsLost
        if (!!result.packetsLost) {
            var kilobytes = 0;
            if (!getStatsResult.internal.video.prevPacketsLost) {
                getStatsResult.internal.video.prevPacketsLost = result.packetsLost;
            }

            var bytes = result.packetsLost - getStatsResult.internal.video.prevPacketsLost;
            getStatsResult.internal.video.prevPacketsLost = result.packetsLost;

            getStatsResult.video.packetsLost = bytes.toFixed(1);

            if (getStatsResult.video.packetsLost < 0) {
                getStatsResult.video.packetsLost = 0;
            }
        }
    };

    getStatsParser.bweforvideo = function(result) {
        if (result.type !== 'VideoBwe') return;

        getStatsResult.bandwidth.availableSendBandwidth = result.googAvailableSendBandwidth;

        getStatsResult.bandwidth.googActualEncBitrate = result.googActualEncBitrate;
        getStatsResult.bandwidth.googAvailableSendBandwidth = result.googAvailableSendBandwidth;
        getStatsResult.bandwidth.googAvailableReceiveBandwidth = result.googAvailableReceiveBandwidth;
        getStatsResult.bandwidth.googRetransmitBitrate = result.googRetransmitBitrate;
        getStatsResult.bandwidth.googTargetEncBitrate = result.googTargetEncBitrate;
        getStatsResult.bandwidth.googBucketDelay = result.googBucketDelay;
        getStatsResult.bandwidth.googTransmitBitrate = result.googTransmitBitrate;
    };

    getStatsParser.candidatePair = function(result) {
        if (result.type !== 'googCandidatePair' && result.type !== 'candidate-pair' && result.type !== 'local-candidate' && result.type !== 'remote-candidate') return;

        // result.googActiveConnection means either STUN or TURN is used.

        if (result.googActiveConnection == 'true') {
            // id === 'Conn-audio-1-0'
            // localCandidateId, remoteCandidateId

            // bytesSent, bytesReceived

            Object.keys(getStatsResult.internal.candidates).forEach(function(cid) {
                var candidate = getStatsResult.internal.candidates[cid];
                if (candidate.ipAddress.indexOf(result.googLocalAddress) !== -1) {
                    getStatsResult.connectionType.local.candidateType = candidate.candidateType;
                    getStatsResult.connectionType.local.ipAddress = candidate.ipAddress;
                    getStatsResult.connectionType.local.networkType = candidate.networkType;
                    getStatsResult.connectionType.local.transport = candidate.transport;
                }
                if (candidate.ipAddress.indexOf(result.googRemoteAddress) !== -1) {
                    getStatsResult.connectionType.remote.candidateType = candidate.candidateType;
                    getStatsResult.connectionType.remote.ipAddress = candidate.ipAddress;
                    getStatsResult.connectionType.remote.networkType = candidate.networkType;
                    getStatsResult.connectionType.remote.transport = candidate.transport;
                }
            });

            getStatsResult.connectionType.transport = result.googTransportType;

            var localCandidate = getStatsResult.internal.candidates[result.localCandidateId];
            if (localCandidate) {
                if (localCandidate.ipAddress) {
                    getStatsResult.connectionType.systemIpAddress = localCandidate.ipAddress;
                }
            }

            var remoteCandidate = getStatsResult.internal.candidates[result.remoteCandidateId];
            if (remoteCandidate) {
                if (remoteCandidate.ipAddress) {
                    getStatsResult.connectionType.systemIpAddress = remoteCandidate.ipAddress;
                }
            }
        }

        if (result.type === 'candidate-pair') {
            if (result.selected === true && result.nominated === true && result.state === 'succeeded') {
                // remoteCandidateId, localCandidateId, componentId
                var localCandidate = getStatsResult.internal.candidates[result.remoteCandidateId];
                var remoteCandidate = getStatsResult.internal.candidates[result.remoteCandidateId];

                // Firefox used above two pairs for connection
            }
        }

        if (result.type === 'local-candidate') {
            getStatsResult.connectionType.local.candidateType = result.candidateType;
            getStatsResult.connectionType.local.ipAddress = result.ipAddress;
            getStatsResult.connectionType.local.networkType = result.networkType;
            getStatsResult.connectionType.local.transport = result.mozLocalTransport || result.transport;
        }

        if (result.type === 'remote-candidate') {
            getStatsResult.connectionType.remote.candidateType = result.candidateType;
            getStatsResult.connectionType.remote.ipAddress = result.ipAddress;
            getStatsResult.connectionType.remote.networkType = result.networkType;
            getStatsResult.connectionType.remote.transport = result.mozRemoteTransport || result.transport;
        }

        if (isSafari) {
            // result.remoteCandidateId
            // todo: below line will always force "send" on Safari; find a solution
            var sendrecvType = result.localCandidateId ? 'send' : 'recv';

            if (!sendrecvType) return;

            if (!!result.bytesSent) {
                var kilobytes = 0;
                if (!getStatsResult.internal.video[sendrecvType].prevBytesSent) {
                    getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent;
                }

                var bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevBytesSent;
                getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent;

                kilobytes = bytes / 1024;

                getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
                getStatsResult.video.bytesSent = kilobytes.toFixed(1);
            }

            if (!!result.bytesReceived) {
                var kilobytes = 0;
                if (!getStatsResult.internal.video[sendrecvType].prevBytesReceived) {
                    getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived;
                }

                var bytes = result.bytesReceived - getStatsResult.internal.video[sendrecvType].prevBytesReceived;
                getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived;

                kilobytes = bytes / 1024;
                // getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
                getStatsResult.video.bytesReceived = kilobytes.toFixed(1);
            }

            if (!!result.availableOutgoingBitrate) {
                var kilobytes = 0;
                if (!getStatsResult.internal.video[sendrecvType].prevAvailableOutgoingBitrate) {
                    getStatsResult.internal.video[sendrecvType].prevAvailableOutgoingBitrate = result.availableOutgoingBitrate;
                }

                var bytes = result.availableOutgoingBitrate - getStatsResult.internal.video[sendrecvType].prevAvailableOutgoingBitrate;
                getStatsResult.internal.video[sendrecvType].prevAvailableOutgoingBitrate = result.availableOutgoingBitrate;

                kilobytes = bytes / 1024;
                // getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
                getStatsResult.video.availableOutgoingBitrate = kilobytes.toFixed(1);
            }

            if (!!result.availableIncomingBitrate) {
                var kilobytes = 0;
                if (!getStatsResult.internal.video[sendrecvType].prevAvailableIncomingBitrate) {
                    getStatsResult.internal.video[sendrecvType].prevAvailableIncomingBitrate = result.availableIncomingBitrate;
                }

                var bytes = result.availableIncomingBitrate - getStatsResult.internal.video[sendrecvType].prevAvailableIncomingBitrate;
                getStatsResult.internal.video[sendrecvType].prevAvailableIncomingBitrate = result.availableIncomingBitrate;

                kilobytes = bytes / 1024;
                // getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
                getStatsResult.video.availableIncomingBitrate = kilobytes.toFixed(1);
            }
        }
    };

    var LOCAL_candidateType = {};
    var LOCAL_transport = {};
    var LOCAL_ipAddress = {};
    var LOCAL_networkType = {};

    getStatsParser.localcandidate = function(result) {
        if (result.type !== 'localcandidate' && result.type !== 'local-candidate') return;
        if (!result.id) return;

        if (!LOCAL_candidateType[result.id]) {
            LOCAL_candidateType[result.id] = [];
        }

        if (!LOCAL_transport[result.id]) {
            LOCAL_transport[result.id] = [];
        }

        if (!LOCAL_ipAddress[result.id]) {
            LOCAL_ipAddress[result.id] = [];
        }

        if (!LOCAL_networkType[result.id]) {
            LOCAL_networkType[result.id] = [];
        }

        if (result.candidateType && LOCAL_candidateType[result.id].indexOf(result.candidateType) === -1) {
            LOCAL_candidateType[result.id].push(result.candidateType);
        }

        if (result.transport && LOCAL_transport[result.id].indexOf(result.transport) === -1) {
            LOCAL_transport[result.id].push(result.transport);
        }

        if (result.ipAddress && LOCAL_ipAddress[result.id].indexOf(result.ipAddress + ':' + result.portNumber) === -1) {
            LOCAL_ipAddress[result.id].push(result.ipAddress + ':' + result.portNumber);
        }

        if (result.networkType && LOCAL_networkType[result.id].indexOf(result.networkType) === -1) {
            LOCAL_networkType[result.id].push(result.networkType);
        }

        getStatsResult.internal.candidates[result.id] = {
            candidateType: LOCAL_candidateType[result.id],
            ipAddress: LOCAL_ipAddress[result.id],
            portNumber: result.portNumber,
            networkType: LOCAL_networkType[result.id],
            priority: result.priority,
            transport: LOCAL_transport[result.id],
            timestamp: result.timestamp,
            id: result.id,
            type: result.type
        };

        getStatsResult.connectionType.local.candidateType = LOCAL_candidateType[result.id];
        getStatsResult.connectionType.local.ipAddress = LOCAL_ipAddress[result.id];
        getStatsResult.connectionType.local.networkType = LOCAL_networkType[result.id];
        getStatsResult.connectionType.local.transport = LOCAL_transport[result.id];
    };

    var REMOTE_candidateType = {};
    var REMOTE_transport = {};
    var REMOTE_ipAddress = {};
    var REMOTE_networkType = {};

    getStatsParser.remotecandidate = function(result) {
        if (result.type !== 'remotecandidate' && result.type !== 'remote-candidate') return;
        if (!result.id) return;

        if (!REMOTE_candidateType[result.id]) {
            REMOTE_candidateType[result.id] = [];
        }

        if (!REMOTE_transport[result.id]) {
            REMOTE_transport[result.id] = [];
        }

        if (!REMOTE_ipAddress[result.id]) {
            REMOTE_ipAddress[result.id] = [];
        }

        if (!REMOTE_networkType[result.id]) {
            REMOTE_networkType[result.id] = [];
        }

        if (result.candidateType && REMOTE_candidateType[result.id].indexOf(result.candidateType) === -1) {
            REMOTE_candidateType[result.id].push(result.candidateType);
        }

        if (result.transport && REMOTE_transport[result.id].indexOf(result.transport) === -1) {
            REMOTE_transport[result.id].push(result.transport);
        }

        if (result.ipAddress && REMOTE_ipAddress[result.id].indexOf(result.ipAddress + ':' + result.portNumber) === -1) {
            REMOTE_ipAddress[result.id].push(result.ipAddress + ':' + result.portNumber);
        }

        if (result.networkType && REMOTE_networkType[result.id].indexOf(result.networkType) === -1) {
            REMOTE_networkType[result.id].push(result.networkType);
        }

        getStatsResult.internal.candidates[result.id] = {
            candidateType: REMOTE_candidateType[result.id],
            ipAddress: REMOTE_ipAddress[result.id],
            portNumber: result.portNumber,
            networkType: REMOTE_networkType[result.id],
            priority: result.priority,
            transport: REMOTE_transport[result.id],
            timestamp: result.timestamp,
            id: result.id,
            type: result.type
        };

        getStatsResult.connectionType.remote.candidateType = REMOTE_candidateType[result.id];
        getStatsResult.connectionType.remote.ipAddress = REMOTE_ipAddress[result.id];
        getStatsResult.connectionType.remote.networkType = REMOTE_networkType[result.id];
        getStatsResult.connectionType.remote.transport = REMOTE_transport[result.id];
    };

    getStatsParser.dataSentReceived = function(result) {
        if (!result.googCodecName || (result.mediaType !== 'video' && result.mediaType !== 'audio')) return;

        if (!!result.bytesSent) {
            getStatsResult[result.mediaType].bytesSent = parseInt(result.bytesSent);
        }

        if (!!result.bytesReceived) {
            getStatsResult[result.mediaType].bytesReceived = parseInt(result.bytesReceived);
        }
    };

    getStatsParser.inboundrtp = function(result) {
        if (!isSafari) return;
        if (result.type !== 'inbound-rtp') return;

        var mediaType = result.mediaType || 'audio';
        var sendrecvType = result.isRemote ? 'recv' : 'send';

        if (!sendrecvType) return;

        if (!!result.bytesSent) {
            var kilobytes = 0;
            if (!getStatsResult.internal[mediaType][sendrecvType].prevBytesSent) {
                getStatsResult.internal[mediaType][sendrecvType].prevBytesSent = result.bytesSent;
            }

            var bytes = result.bytesSent - getStatsResult.internal[mediaType][sendrecvType].prevBytesSent;
            getStatsResult.internal[mediaType][sendrecvType].prevBytesSent = result.bytesSent;

            kilobytes = bytes / 1024;

            getStatsResult[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
            getStatsResult[mediaType].bytesSent = kilobytes.toFixed(1);
        }

        if (!!result.bytesReceived) {
            var kilobytes = 0;
            if (!getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived) {
                getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived = result.bytesReceived;
            }

            var bytes = result.bytesReceived - getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived;
            getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived = result.bytesReceived;

            kilobytes = bytes / 1024;
            // getStatsResult[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
            getStatsResult[mediaType].bytesReceived = kilobytes.toFixed(1);
        }
    };

    getStatsParser.outboundrtp = function(result) {
        if (!isSafari) return;
        if (result.type !== 'outbound-rtp') return;

        var mediaType = result.mediaType || 'audio';
        var sendrecvType = result.isRemote ? 'recv' : 'send';

        if (!sendrecvType) return;

        if (!!result.bytesSent) {
            var kilobytes = 0;
            if (!getStatsResult.internal[mediaType][sendrecvType].prevBytesSent) {
                getStatsResult.internal[mediaType][sendrecvType].prevBytesSent = result.bytesSent;
            }

            var bytes = result.bytesSent - getStatsResult.internal[mediaType][sendrecvType].prevBytesSent;
            getStatsResult.internal[mediaType][sendrecvType].prevBytesSent = result.bytesSent;

            kilobytes = bytes / 1024;

            getStatsResult[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
            getStatsResult[mediaType].bytesSent = kilobytes.toFixed(1);
        }

        if (!!result.bytesReceived) {
            var kilobytes = 0;
            if (!getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived) {
                getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived = result.bytesReceived;
            }

            var bytes = result.bytesReceived - getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived;
            getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived = result.bytesReceived;

            kilobytes = bytes / 1024;
            // getStatsResult[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
            getStatsResult[mediaType].bytesReceived = kilobytes.toFixed(1);
        }
    };

    getStatsParser.track = function(result) {
        if (!isSafari) return;
        if (result.type !== 'track') return;

        var sendrecvType = result.remoteSource === true ? 'send' : 'recv';

        if (result.frameWidth && result.frameHeight) {
            getStatsResult.resolutions[sendrecvType].width = result.frameWidth;
            getStatsResult.resolutions[sendrecvType].height = result.frameHeight;
        }

        // framesSent, framesReceived
    };

    var SSRC = {
        audio: {
            send: [],
            recv: []
        },
        video: {
            send: [],
            recv: []
        }
    };

    getStatsParser.ssrc = function(result) {
        if (!result.googCodecName || (result.mediaType !== 'video' && result.mediaType !== 'audio')) return;
        if (result.type !== 'ssrc') return;
        var sendrecvType = result.id.split('_').pop();

        if (SSRC[result.mediaType][sendrecvType].indexOf(result.ssrc) === -1) {
            SSRC[result.mediaType][sendrecvType].push(result.ssrc)
        }

        getStatsResult[result.mediaType][sendrecvType].streams = SSRC[result.mediaType][sendrecvType].length;
    };

    getStatsLooper();

};

if (typeof module !== 'undefined' /* && !!module.exports*/ ) {
    module.exports = getStats;
}

if (typeof define === 'function' && define.amd) {
    define('getStats', [], function() {
        return getStats;
    });
}