// <reference path="../ScriptReference/jquery-1.7.1-vsdoc.js"/>
/// <reference path="Mediasite.Core.js"/>
/// <reference path="Mediasite.Player.Core.js"/>
/// <reference path="Mediasite.Player.Localization.js"/>
/*global Mediasite OnDSPlayStateChangeEvt : true, OnDSPositionChangeEvt : true, OnDSOpenStateChangeEvt : true, OnDSScriptCommandEvt : true, OnDSMediaErrorEvt : true */

Mediasite.namespace("MediaPlayer");
Mediasite.namespace("MediaPlayer.Views");

Mediasite.MediaPlayer.ModelHtml5Ramp = function (viewOptions) {
    var LIVE_START_UNSAFE_WINDOW = 15 * 1000; // milliseconds

    var result = new Mediasite.MediaPlayer.ModelHtml5(viewOptions);
    var videoElement;
    var hls;
    var receiverLogic;
    var errorLogic;
    var bitrateCheckTimeout;
    var lastReportedBitrate = -1;
    var BITRATE_CHECK_INTERVAL = 1000;
    var BITRATE_CHANGE_THRESHOLD = 0.05; // Fraction of last reported bitrate.
    var liveStartTime = 0;

    var logger = Mediasite.Logging.LoggerFactory("Media Ramp HLS" + ":" + viewOptions.streamType, { separator: "\n" });


    // Error logic instance lifetime must span multiple Hls instances in order 
    // to track errors that may trigger recreation of the video element.
    errorLogic = new Mediasite.MediaPlayer.HlsJsErrorLogic(logger);
    errorLogic.raiseErrorDelegate = function (message) { result.raiseMediaError(message); };

    function shouldEnableHLSjsLogging() {
        return Mediasite.GetQueryParam("logHLSjs", Mediasite.GetQueryParam.AsBool);
    }

    result.setVideoElementSourceFunction = function (element, mediaSource, mediaType) {

        videoElement = element;
        if (receiverLogic) {
            receiverLogic.stop();
        }
        receiverLogic = makeReceiverLogic();
        receiverLogic.start();

    };

    var innerSetLiveStartTime = result.SetLiveStartTime;

    result.SetLiveStartTime = function (time) {
        liveStartTime = time;
        innerSetLiveStartTime.call(result, time);
    };

    result.trustLiveVideoElementPosition = false;

    result.liveStartUnsafeWindow = LIVE_START_UNSAFE_WINDOW;

    result.Stop = function () {
        var video = videoElement.get(0);
        video.pause();
        result.reportStateChanged(Mediasite.Player.MediaState.Stopped);
    };

    // TogglePlayPause is called during live, so Pause=Stop.
    result.Pause = result.Stop;

    result.FinalizeLive = function () {
        result.Stop();
        if (hls) {
            hls.destroy();
        }
    };

    return result;


    function makeReceiverLogic() {
        var logic = new RampReceiverLogic();

        logic.logger = logger;
        logic.receiverAddress = viewOptions.selectedVideoSource.MulticastReceiverUrl;
        logic.receiverKeyStore = viewOptions.selectedVideoSource.MulticastReceiverKeyStore;
        logic.multicastIP = viewOptions.selectedVideoSource.MulticastAddress;
        logic.multicastPort = viewOptions.selectedVideoSource.MulticastPort;
        logic.diagnosticsAddress = viewOptions.selectedVideoSource.MulticastDiagnosticsServer;
        logic.readyDelegate = function (hlsUrl) {
            setVideoSourceParam(videoElement, hlsUrl, viewOptions.selectedVideoSource.MimeType);
        };
        logic.receiverCommErrorDelegate = function () {
            onMulticastLoadFailure(Mediasite.Player.Localization.MediaPlayer.MulticastReceiverCommunicationError);
        };
        logic.progressErrorDelegate = function () {
            onMulticastLoadFailure(Mediasite.Player.Localization.MediaPlayer.MulticastProgressError);
        };

        return logic;
    }

    function onMulticastLoadFailure(errorMessage) {
        if (viewOptions.selectedVideoSource.MulticastOnly) {
            result.raiseMediaError(errorMessage);
        } else {
            logger.Info('Failing over to unicast HLS.');
            setVideoSourceParam(videoElement, viewOptions.selectedVideoSource.Location, viewOptions.selectedVideoSource.MimeType);
        }
    }

    function getCurrentBitrate() {
        // The Ramp Receiver always has just one level. 
        // We will assume that we are always playing level 0.

        var video = videoElement.get(0);

        if (video && video.paused) {
            return 0;
        }

        if (!hls || !hls.levels || hls.levels.length !== 1) {
            return 0;
        }

        // The Ramp Receiver generated manifest doesn't provide bitrate/bandwidth information. Let's try hls.realBitrate.
        return hls.levels[0].realBitrate;
    }

    function isNewBitrate(bitrate) {
        return Math.abs(bitrate - lastReportedBitrate) > (lastReportedBitrate * BITRATE_CHANGE_THRESHOLD);
    }

    function reportBitrate(bitrate) {
        lastReportedBitrate = bitrate;
        result.updateBitrate(bitrate);
        logger.Info('Media quality changed. -- bitrate: ' + bitrate);
    }

    function pollForBitrateChange() {
        var bitrate;

        // Schedule next check.
        clearTimeout(bitrateCheckTimeout);
        bitrateCheckTimeout = setTimeout(pollForBitrateChange, BITRATE_CHECK_INTERVAL);

        // Get the rate of my bits.
        bitrate = getCurrentBitrate();

        // Report the rate, if it is significantly different than what we last reported.
        if (isNewBitrate(bitrate)) {
            reportBitrate(bitrate);
        }
    }

    function setVideoSourceParam(element, mediaSource, mediaType) {
        var video = element.get(0);
        if (hls) {
            hls.destroy();
        }
        hls = new Hls({ debug: shouldEnableHLSjsLogging() });
        errorLogic.attach(hls);

        pollForBitrateChange();

        hls.attachMedia(video);
        hls.loadSource(mediaSource);
    }


    function RampReceiverLogic() {
        var self = this;
        var statusCheckPollingDelay = 0.5 * 1000;
        var keepAlivePollingDelay = 20 * 1000;
        var maxWaitForSegmentProgress = 20 * 1000;
        var maxWaitForPacketProgress = 10 * 1000;
        var liveStartSegmentGraceMilliseconds = 60 * 1000;
        var maxStartRequestFailures = 3;
        var startRequestFailures = 0;
        var lastPacketProgressTime = (new Date()).getTime();
        var lastSegmentProgressTime = (new Date()).getTime();
        var lastPacketProgress = -1;
        var lastSegmentProgress = -1;
        var statusCheckTimeoutObj;
        var keepAliveTimeoutObj;
        var stopped = false;

        this.logger = makeNullLogger();
        this.liveStartTime = 0;
        this.receiverAddress = '';
        this.receiverKeyStore = '';
        this.multicastIP = '';
        this.multicastPort = '';
        this.diagnosticsAddress = '';
        this.readyDelegate = function () { };
        this.receiverCommErrorDelegate = function () { };
        this.progressErrorDelegate = function () { };


        this.start = function () {
            stopped = false;
            initReceiver();
        };

        this.stop = function () {
            stopped = true;
            endStartupStatusPolling();
            endReceiverKeepAlive();
        };


        function getMulticastAddress() {
            return self.multicastIP + ':' + self.multicastPort + '/16';
        }

        function getAddressKey() {
            return self.multicastIP + '~' + self.multicastPort + '~~';
        }

        function getStartUrl() {
            return self.receiverAddress + 'rme/start';
        }

        function getStatusUrl() {
            return self.receiverAddress + 'rme/status?maddr=' + getMulticastAddress() + '&_=' + (new Date()).getTime();
        }

        function initReceiver() {

            if (stopped) {
                return;
            }

            $.ajax({
                url: getStartUrl(),
                type: 'POST',
                data: JSON.stringify({
                    "bufferMs": "1000",
                    "nativeMode": "false",
                    "senderSubjectDNSuffix": "",
                    "maddr": getMulticastAddress(),
                    "diagnosticsServer": self.diagnosticsAddress,
                    "diagnosticsIntervalMs": "15000",
                    "dvrDurationMs": "900000",
                    "dontOutputDiscontinuitySequence": "false",
                    "resetDvrOnStreamSwap": "dash",
                    "receiverKeyStore": self.receiverKeyStore
                }),
                success: function (fooo, textStatus, jqXHR) {
                    if (stopped) {
                        return;
                    }

                    self.logger.Info('Ramp Receiver start request was successful.');
                    checkStatus();
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    if (stopped) {
                        return;
                    }

                    startRequestFailures += 1;
                    if (startRequestFailures < maxStartRequestFailures) {
                        self.logger.Info('Ramp Receiver start request failed ' + startRequestFailures  + ' times.');
                        initReceiver();
                    } else {
                        self.logger.Error('Ramp Receiver failed to start.');
                        self.receiverCommErrorDelegate(); // TODO: Pass error info.
                    }
                }
            });
        }

        function checkStatus() {
            if (stopped) {
                return;
            }

            var addressKey = getAddressKey();

            $.ajax({
                url: getStatusUrl(),
                type: 'GET',
                success: function (data, textStatus, jqXHR) {
                    var addressObject = (data && data.addresses && data.addresses[addressKey]) || {};

                    self.logger.Debug('Ramp Receiver Status  ^_^  segments: ' + addressObject.segmentsInPlaylist + '; packets: ' + addressObject.totalPacketsReceived + '; bytes: ' + addressObject.totalBytesReceived);

                    if (addressObject.readyForPlayer) {
                        self.logger.Info('Receiver is ready for player. Attempting to load multicast media source: ' + addressObject.hlsMulticastUrl);
                        resetProgressTracking();
                        scheduleReceiverKeepAlive(); // Ping the receiver periodically to keep it alive, even when the player is not playing.
                        self.readyDelegate(addressObject.hlsMulticastUrl);
                    } else if (showsEnoughProgress(addressObject.totalPacketsReceived, addressObject.segmentsInPlaylist)) {
                        statusCheckTimeoutObj = window.setTimeout(checkStatus, statusCheckPollingDelay);
                    } else {
                        self.logger.Error('The Ramp receiver did not receive enough media data.');
                        self.progressErrorDelegate(); // TODO: Pass error info.
                    }
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    self.logger.Error('Error communicating with the Ramp receiver.');
                    self.receiverCommErrorDelegate(); // TODO: Pass error info.
                }
            });
        }

        function endStartupStatusPolling() {
            window.clearTimeout(statusCheckTimeoutObj);
        }

        function doReceiverKeepAliveRequest(callback) {
            if (stopped) {
                return;
            }

            $.ajax({
                url: getStatusUrl(),
                type: 'GET',
                complete: function () {
                    callback();
                }
            });
        }

        function scheduleReceiverKeepAlive() {
            keepAliveTimeoutObj = window.setTimeout(function () {
                doReceiverKeepAliveRequest(scheduleReceiverKeepAlive);
            }, keepAlivePollingDelay);
        }

        function endReceiverKeepAlive() {
            window.clearTimeout(keepAliveTimeoutObj);
        }

        function showsEnoughProgress(packets, segments) {
            var currentTime = (new Date()).getTime();
            var timeSincePacketProgress, timeSinceSegmentProgress;

            if (packets > lastPacketProgress) {
                lastPacketProgressTime = currentTime;
                lastPacketProgress = packets;
            }

            if (segments > lastSegmentProgress) {
                lastSegmentProgressTime = currentTime;
                lastSegmentProgress = segments;
            }

            timeSincePacketProgress = currentTime - lastPacketProgressTime;
            timeSinceSegmentProgress = currentTime - lastSegmentProgressTime;

            if (timeSincePacketProgress > maxWaitForPacketProgress) {
                logger.Info('Not enough packet progress.');
                return false;
            }

            if (timeSinceSegmentProgress > maxWaitForSegmentProgress && !isWithinLiveStartSegmentGrace()) {
                logger.Info('Not enough segment progress.');
                return false;
            }

            // Keep waiting.
            return true;
        }

        function resetProgressTracking() {
            lastPacketProgressTime = (new Date()).getTime();
            lastSegmentProgressTime = (new Date()).getTime();
            lastPacketProgress = -1; // Value less than zero ensures that first status check will set both time and bytes values.
            lastSegmentProgress = -1;
        }

        function isWithinLiveStartSegmentGrace() {
            var timeSinceLiveStart = new Date() - self.liveStartTime;
            return (timeSinceLiveStart < liveStartSegmentGraceMilliseconds);
        }

        function makeNullLogger() {
            function noop() { };

            return {
                Debug: noop,
                Info: noop,
                Warning: noop,
                Error: noop,
                Critical: noop
            };
        }
    }
};

