// <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.ModelHlsDotJs = function (viewOptions) {

    var SEGMENT_LENGTH_TO_UNSAFE_SECONDS = {
        2: 9,
        4: 15,
        6: 21,
        8: 27,
        9: 30,
        10: 30
    };
    var segmentLengthSeconds = (viewOptions.selectedVideoSource.SegmentLength || 0) / 1000;
    var liveStartUnsafeSeconds = Mediasite.MediaPlayer.calculateLiveStartUnsafeWindow(SEGMENT_LENGTH_TO_UNSAFE_SECONDS, segmentLengthSeconds);

    var result = new Mediasite.MediaPlayer.ModelHtml5(viewOptions);
    var location = viewOptions.selectedVideoSource.Location;
    var logger = Mediasite.Logging.LoggerFactory("Media HLS.js" + ":" + viewOptions.streamType, { separator: "\n" });
    var video;
    var hls;
    var errorLogic;
    var lastReportedLevel = -1;
    
    var DELIVER_FMP4_TIMESCALE = 10000000;
    var TS_TIMESCALE = 90000;

    var ptsTimeScale = DELIVER_FMP4_TIMESCALE;
    var initPTS = 0;

    // 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);
    }

    function bitrateForLevel(levelIndex) {
        if (!hls || !hls.levels) {
            return 0;
        }

        if (levelIndex < 0 || levelIndex > hls.levels.length - 1) {
            return 0;
        }

        return hls.levels[levelIndex].bitrate;
    }

    function checkForBitrateChange() {
        var level = -1;
        var bitrate = 0;

        if (hls && typeof hls.currentLevel === 'number') {
            level = hls.currentLevel;
        }

        if (video && video.paused) {
            level = -1;
        }

        if (lastReportedLevel != level) {
            lastReportedLevel = level;
            bitrate = bitrateForLevel(level);
            result.updateBitrate(bitrate);
             logger.Info('Media quality changed. -- level: ' + level + ' (bitrate: ' + bitrate + ')');
        }
    }

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

        video = element.get(0);
        if (hls) {
            hls.destroy();
        }
        hls = new Hls({ debug: shouldEnableHLSjsLogging(), liveSyncDurationCount: 3, liveMaxLatencyDurationCount: 6 });
        errorLogic.attach(hls);

        // TODO: In future version of hls.js, we can use LEVEL_SWITCHING and LEVEL_SWITCHED events to recognize bitrate switches.
        //       For now, just check for changes on each timeupdate event.
        element.off('timeupdate', checkForBitrateChange);
        element.on('timeupdate', checkForBitrateChange);

        hls.on(Hls.Events.INIT_PTS_FOUND, function (e, data) {
            initPTS = data.initPTS;
        });

        ptsTimeScale = viewOptions.selectedVideoSource.ServerType === Mediasite.Player.ServerTypes.Deliver ? DELIVER_FMP4_TIMESCALE : TS_TIMESCALE;

        hls.attachMedia(video);
        hls.loadSource(viewOptions.selectedVideoSource.Location);

    };

    result.trustLiveVideoElementPosition = false;

    result.getLiveMediaTimeOffset = function () {
        return (initPTS / ptsTimeScale) * 1000; // Convert to milliseconds.
    };

    result.liveStartUnsafeWindow = liveStartUnsafeSeconds * 1000;

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

    return result;
};

Mediasite.MediaPlayer.HlsJsErrorLogic = function (logger) {
    var self = this;
    var errorMemoryWindow = 1 * 60 * 1000; // ms
    var maxErrorsInWindow = 9;
    var fatalErrorMemoryWindow = 5 * 60 * 1000;
    var maxFatalErrorsInWindow = 2;

    var hls;
    var errorMemory = [];
    var fatalErrorMemory = [];


    function rememberError(errorString) {
        errorMemory.push({ when: (new Date()).getTime(), error: errorString });
    }

    function rememberFatalError(errorString) {
        fatalErrorMemory.push({ when: (new Date()).getTime(), error: errorString });
    }

    function tooManyErrors() {
        return getNumErrorsInWindow(errorMemory, errorMemoryWindow) > maxErrorsInWindow;
    }

    function tooManyFatalErrors() {
        return getNumErrorsInWindow(fatalErrorMemory, fatalErrorMemoryWindow) > maxFatalErrorsInWindow;
    }

    function getNumErrorsInWindow(memory, window) {
        // Remove errors outside window.
        var minTime = (new Date()).getTime() - window;
        trimErrorMemory(memory, minTime);

        // Return a count of the remaining errors.
        return memory.length;
    }

    function trimErrorMemory(memory, minTime) {
        while (memory.length > 0 && memory[0].when < minTime) {
            memory.shift();
        }
    }

    function makeErrorDataString(errorData) {
        return "HLS.JS ERROR -- fatal: " + errorData.fatal + " ; type: " + errorData.type + "; details: " + errorData.details + ";";
    }

    function raiseErrorEvent(errorData) {
        var message = '';
        var locMP = Mediasite.Player.Localization.MediaPlayer;

        if (errorData.type === Hls.ErrorTypes.NETWORK_ERROR) {
            message = locMP.AdaptiveStreamingNetworkError;
        } else if (errorData.type === Hls.ErrorTypes.MEDIA_ERROR) {
            message = locMP.AdaptiveStreamingMediaError;
        } else if (errorData.type === Hls.ErrorTypes.MUX_ERROR) {
            message = locMP.AdaptiveStreamingMuxError;
        } else { // OTHER_ERROR
            message = locMP.GeneralError;
        }

        self.raiseErrorDelegate(message);
    }

    function handleHLSjsError(event, data) {
        var errorString = makeErrorDataString(data);
        logger.Error(errorString);


        //rememberError(errorString);
        //if (tooManyErrors()) {
        //    logger.Error("Too many errors within " + errorMemoryWindow + "ms.");
        //    raiseErrorEvent(data);
        //    return;
        //}

        if (data.fatal) {
            rememberFatalError(errorString);
            if (tooManyFatalErrors()) {
                logger.Error("Too many FATAL errors within " + fatalErrorMemoryWindow + "ms.");
                raiseErrorEvent(data);
                return;
            }

            switch (data.type) {
                case Hls.ErrorTypes.NETWORK_ERROR:
                    logger.Error("Fatal network error encountered. Attempting to recover.");
                    hls.startLoad();
                    break;
                case Hls.ErrorTypes.MEDIA_ERROR:
                    logger.Error("Fatal media error encountered. Attempting to recover.");
                    hls.recoverMediaError();
                    break;
                default:
                    // Cannot recover. Raise the error event, even if first error.
                    raiseErrorEvent(data);
                    break;
            }
        }
    }


    this.raiseErrorDelegate = function () { };

    this.attach = function (hlsObj) {
        hls = hlsObj;
        hls.on(Hls.Events.ERROR, handleHLSjsError);
    };
};