import { Stream } from './Stream';
import * as adapter from 'webrtc-adapter';

export class WebRtcStats {

    private webRtcStatsEnabled: boolean = false;
    private webRtcStatsIntervalId: number;
    private statsInterval: number = 1;
    private stats: any = {
        "inbound": {
            "audio": {
                "bytesReceived": 0,
                "packetsReceived": 0,
                "packetsLost": 0
            },
            "video": {
                "bytesReceived": 0,
                "packetsReceived": 0,
                "packetsLost": 0,
                "framesDecoded": 0,
                "nackCount": 0
            }
        },
        "outbound": {
            "audio": {
                "bytesSent": 0,
                "packetsSent": 0,
            },
            "video": {
                "bytesSent": 0,
                "packetsSent": 0,
                "framesEncoded": 0,
                "nackCount": 0
            }
        }
    }

    constructor(private stream: Stream) { }

    public isEnabled(): boolean {
        return this.webRtcStatsEnabled;
    }

    public initWebRtcStats(): void {

        let elastestInstrumentation = localStorage.getItem('elastest-instrumentation');

        if (elastestInstrumentation) {
            // ElasTest instrumentation object found in local storage

            console.warn("WebRtc stats enabled for stream " + this.stream.streamId + " of connection " + this.stream.connection.connectionId);

            this.webRtcStatsEnabled = true;

            let instrumentation = JSON.parse(elastestInstrumentation);
            this.statsInterval = instrumentation.webrtc.interval;  // Interval in seconds

            console.warn("localStorage item: " + JSON.stringify(instrumentation));

            this.webRtcStatsIntervalId = setInterval(() => {
                this.sendStatsToHttpEndpoint(instrumentation);
            }, this.statsInterval * 1000);

            return;
        }

        console.debug("WebRtc stats not enabled");
    }

    public stopWebRtcStats() {
        if (this.webRtcStatsEnabled) {
            clearInterval(this.webRtcStatsIntervalId);
            console.warn("WebRtc stats stopped for disposed stream " + this.stream.streamId + " of connection " + this.stream.connection.connectionId);
        }
    }

    private sendStatsToHttpEndpoint(instrumentation): void {

        let sendPost = (json) => {
            let http: XMLHttpRequest = new XMLHttpRequest();
            let url: string = instrumentation.webrtc.httpEndpoint;
            http.open("POST", url, true);

            http.setRequestHeader("Content-type", "application/json");
            
            http.onreadystatechange = () => { // Call a function when the state changes.
                if (http.readyState == 4 && http.status == 200) {
                    console.log("WebRtc stats succesfully sent to " + url + " for stream " + this.stream.streamId + " of connection " + this.stream.connection.connectionId);
                }
            }
            http.send(json);
        }

        let f = (stats) => {

            if (adapter.browserDetails.browser === 'firefox') {
                stats.forEach((stat) => {

                    let json = {};

                    if ((stat.type === 'inbound-rtp') &&
                        (
                            // Avoid firefox empty outbound-rtp statistics
                            stat.nackCount != null &&
                            stat.isRemote === false &&
                            stat.id.startsWith('inbound') &&
                            stat.remoteId.startsWith('inbound')
                        )) {

                        let metricId = 'webrtc_inbound_' + stat.mediaType + '_' + stat.ssrc;
                        let jitter = stat.jitter * 1000;

                        let metrics = {
                            "bytesReceived": (stat.bytesReceived - this.stats.inbound[stat.mediaType].bytesReceived) / this.statsInterval,
                            "jitter": jitter,
                            "packetsReceived": (stat.packetsReceived - this.stats.inbound[stat.mediaType].packetsReceived) / this.statsInterval,
                            "packetsLost": (stat.packetsLost - this.stats.inbound[stat.mediaType].packetsLost) / this.statsInterval
                        };
                        let units = {
                            "bytesReceived": "bytes",
                            "jitter": "ms",
                            "packetsReceived": "packets",
                            "packetsLost": "packets"
                        };
                        if (stat.mediaType === 'video') {
                            metrics['framesDecoded'] = (stat.framesDecoded - this.stats.inbound.video.framesDecoded) / this.statsInterval;
                            metrics['nackCount'] = (stat.nackCount - this.stats.inbound.video.nackCount) / this.statsInterval;
                            units['framesDecoded'] = "frames";
                            units['nackCount'] = "packets";

                            this.stats.inbound.video.framesDecoded = stat.framesDecoded;
                            this.stats.inbound.video.nackCount = stat.nackCount;
                        }

                        this.stats.inbound[stat.mediaType].bytesReceived = stat.bytesReceived;
                        this.stats.inbound[stat.mediaType].packetsReceived = stat.packetsReceived;
                        this.stats.inbound[stat.mediaType].packetsLost = stat.packetsLost;

                        json = {
                            "@timestamp": new Date(stat.timestamp).toISOString(),
                            "exec": instrumentation.exec,
                            "component": instrumentation.component,
                            "stream": "webRtc",
                            "type": metricId,
                            "stream_type": "composed_metrics",
                            "units": units
                        }
                        json[metricId] = metrics;

                        sendPost(JSON.stringify(json));

                    } else if ((stat.type === 'outbound-rtp') &&
                        (
                            // Avoid firefox empty inbound-rtp statistics
                            stat.isRemote === false &&
                            stat.id.toLowerCase().includes('outbound')
                        )) {

                        let metricId = 'webrtc_outbound_' + stat.mediaType + '_' + stat.ssrc;

                        let metrics = {
                            "bytesSent": (stat.bytesSent - this.stats.outbound[stat.mediaType].bytesSent) / this.statsInterval,
                            "packetsSent": (stat.packetsSent - this.stats.outbound[stat.mediaType].packetsSent) / this.statsInterval
                        };
                        let units = {
                            "bytesSent": "bytes",
                            "packetsSent": "packets"
                        };
                        if (stat.mediaType === 'video') {
                            metrics['framesEncoded'] = (stat.framesEncoded - this.stats.outbound.video.framesEncoded) / this.statsInterval;
                            units['framesEncoded'] = "frames";

                            this.stats.outbound.video.framesEncoded = stat.framesEncoded;
                        }

                        this.stats.outbound[stat.mediaType].bytesSent = stat.bytesSent;
                        this.stats.outbound[stat.mediaType].packetsSent = stat.packetsSent;

                        json = {
                            "@timestamp": new Date(stat.timestamp).toISOString(),
                            "exec": instrumentation.exec,
                            "component": instrumentation.component,
                            "stream": "webRtc",
                            "type": metricId,
                            "stream_type": "composed_metrics",
                            "units": units
                        }
                        json[metricId] = metrics;

                        sendPost(JSON.stringify(json));
                    }
                });
            } else if (adapter.browserDetails.browser === 'chrome') {
                for (let key of Object.keys(stats)) {
                    let stat = stats[key];
                    if (stat.type === 'ssrc') {

                        let json = {};

                        if ('bytesReceived' in stat && (
                            (stat.mediaType === 'audio' && 'audioOutputLevel' in stat) ||
                            (stat.mediaType === 'video' && 'qpSum' in stat)
                        )) {
                            // inbound-rtp
                            let metricId = 'webrtc_inbound_' + stat.mediaType + '_' + stat.ssrc;

                            let metrics = {
                                "bytesReceived": (stat.bytesReceived - this.stats.inbound[stat.mediaType].bytesReceived) / this.statsInterval,
                                "jitter": stat.googJitterBufferMs,
                                "packetsReceived": (stat.packetsReceived - this.stats.inbound[stat.mediaType].packetsReceived) / this.statsInterval,
                                "packetsLost": (stat.packetsLost - this.stats.inbound[stat.mediaType].packetsLost) / this.statsInterval
                            };
                            let units = {
                                "bytesReceived": "bytes",
                                "jitter": "ms",
                                "packetsReceived": "packets",
                                "packetsLost": "packets"
                            };
                            if (stat.mediaType === 'video') {
                                metrics['framesDecoded'] = (stat.framesDecoded - this.stats.inbound.video.framesDecoded) / this.statsInterval;
                                metrics['nackCount'] = (stat.googNacksSent - this.stats.inbound.video.nackCount) / this.statsInterval;
                                units['framesDecoded'] = "frames";
                                units['nackCount'] = "packets";

                                this.stats.inbound.video.framesDecoded = stat.framesDecoded;
                                this.stats.inbound.video.nackCount = stat.googNacksSent;
                            }

                            this.stats.inbound[stat.mediaType].bytesReceived = stat.bytesReceived;
                            this.stats.inbound[stat.mediaType].packetsReceived = stat.packetsReceived;
                            this.stats.inbound[stat.mediaType].packetsLost = stat.packetsLost;

                            json = {
                                "@timestamp": new Date(stat.timestamp).toISOString(),
                                "exec": instrumentation.exec,
                                "component": instrumentation.component,
                                "stream": "webRtc",
                                "type": metricId,
                                "stream_type": "composed_metrics",
                                "units": units
                            }
                            json[metricId] = metrics;

                            sendPost(JSON.stringify(json));
                        } else if ('bytesSent' in stat) {
                            // outbound-rtp
                            let metricId = 'webrtc_outbound_' + stat.mediaType + '_' + stat.ssrc;

                            let metrics = {
                                "bytesSent": (stat.bytesSent - this.stats.outbound[stat.mediaType].bytesSent) / this.statsInterval,
                                "packetsSent": (stat.packetsSent - this.stats.outbound[stat.mediaType].packetsSent) / this.statsInterval
                            };
                            let units = {
                                "bytesSent": "bytes",
                                "packetsSent": "packets"
                            };
                            if (stat.mediaType === 'video') {
                                metrics['framesEncoded'] = (stat.framesEncoded - this.stats.outbound.video.framesEncoded) / this.statsInterval;
                                units['framesEncoded'] = "frames";

                                this.stats.outbound.video.framesEncoded = stat.framesEncoded;
                            }

                            this.stats.outbound[stat.mediaType].bytesSent = stat.bytesSent;
                            this.stats.outbound[stat.mediaType].packetsSent = stat.packetsSent;

                            json = {
                                "@timestamp": new Date(stat.timestamp).toISOString(),
                                "exec": instrumentation.exec,
                                "component": instrumentation.component,
                                "stream": "webRtc",
                                "type": metricId,
                                "stream_type": "composed_metrics",
                                "units": units
                            }
                            json[metricId] = metrics;

                            sendPost(JSON.stringify(json));
                        }
                    }
                }
            }
        };

        this.getStatsAgnostic(this.stream.getRTCPeerConnection(), null, f, (error) => { console.log(error) });
    }

    private standardizeReport(response) {
        if (adapter.browserDetails.browser === 'firefox') {
            return response;
        }

        var standardReport = {};
        response.result().forEach(function (report) {
            var standardStats = {
                id: report.id,
                timestamp: report.timestamp,
                type: report.type
            };
            report.names().forEach(function (name) {
                standardStats[name] = report.stat(name);
            });
            standardReport[standardStats.id] = standardStats;
        });

        return standardReport;
    }

    private getStatsAgnostic(pc, selector, successCb, failureCb) {
        if (adapter.browserDetails.browser === 'firefox') {
            // getStats takes args in different order in Chrome and Firefox
            return pc.getStats(selector, (response) => {
                var report = this.standardizeReport(response);
                successCb(report);
            }, failureCb);
        } else if (adapter.browserDetails.browser === 'chrome') {
            // In Chrome, the first two arguments are reversed
            return pc.getStats((response) => {
                var report = this.standardizeReport(response);
                successCb(report);
            }, selector, failureCb);
        }
    }

}