// <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"/>
/// <reference path="../../Scripts/Mediasite.Player.UserActivation.js"/>
/*global Mediasite OnDSPlayStateChangeEvt : true, OnDSPositionChangeEvt : true, OnDSOpenStateChangeEvt : true, OnDSScriptCommandEvt : true, OnDSMediaErrorEvt : true */

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

Mediasite.MediaPlayer.ModelWindowsMedia = function (viewOptions) {
    /// <summary>Media player model for HTML5 media element</summary>
    /// <param name="options" type="object">{ mediaPlayerType: Mediasite.Player.MediaPlayerType, playbackRates: (array) }</param>
    var $ = jQuery;
    var self = this;

    var element;
    var mediaElement;
    var mediaSource;

    var defaultPlaybackRate = 1;
    var playbackRates = viewOptions.playbackRates;
    var skipBackAmount = 3000;
    var skipForwardAmount = 10000;
    var lastSeekTime = 0;
    var manualSeekFlag = false;
    var _manualSeekThreshold = -2000;
    var wmsTimeCorrectionThreshold = 500; // milliseconds
    var inactivityExceeded = false;
    var volume = 100;
    var liveStartTime;
    var liveStartOffset = 0;
    var liveMediaOpenReconnector = new Mediasite.MediaPlayer.LiveMediaOpenReconnector();
    var mediaPlayerView;
    var mediaWrapper;
    var currentPlaybackRate = 1.0;
    var wmsTimeCorrectionDisabled = Mediasite.GetQueryParam('wmstimecorrection') === 'false';

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

    var positionUpdateTimer = null;
    var positionUpdateInterval = 200;

    // TODO: Use one timer for rate and position updates.
    var _rateCheckTimer = null;
    var _rateCheckInterval = 100; // milliseconds

    var _playbackRate = 1.0;
    var _lastStopMediaEventTime = 0; // milliseconds
    var _mediaStopRateCorrectionWindow = 300; // milliseconds

    var clientBookmark = { position: 0, savedAt: 0 };
    var serverBookmark = { position: 0, savedAt: 0 };

    var _events = new Mediasite.Player.EventBundle();
    var _eventNames = Mediasite.MediaPlayer.ModelEvent;

    var disableVolume = viewOptions.disableVolume == undefined ? false : viewOptions.disableVolume;

    this.GetMediaWrapper = function () {
        return mediaWrapper;
    };

    this.SupportsPlayAsVideo = function () {
        return false;
    };

    this.AddEventHandler = function (eventName, handler) {
        _events.addHandler(eventName, handler);
    };

    // Parse options
    var mediaPlayerType = viewOptions.mediaPlayerType;

    _rateCheckTimer = setInterval(rateCheckTimer_tick, _rateCheckInterval);

    function rateCheckTimer_tick() {
        if (mediaElement && mediaElement.settings && mediaElement.settings.rate) {
            if (mediaElement.settings.rate != _playbackRate) {

                // Check to see if IE/WMP is automatically set the rate to 1.0
                // on media stop. If so, try to set it back to the desired rate.
                var now = new Date().getTime();
                if (now < _lastStopMediaEventTime + _mediaStopRateCorrectionWindow) {
                    mediaElement.settings.rate = _playbackRate;
                    return;
                }


                _playbackRate = mediaElement.settings.rate;
                raisePlaybackRateChanged(_playbackRate);
            }
        }
    }

    function raisePlaybackRateChanged(rate) {
        var playbackRateInfo = {
            Min: playbackRates[0],
            Max: playbackRates[playbackRates.length - 1],
            Current: Math.round(rate * 100) / 100,
            Rates: $.extend([], playbackRates)
        };

        logger.Debug("Playback rate now " + playbackRateInfo.Current);
        _events.fire(_eventNames.PlaybackRateChanged, playbackRateInfo);
    }


    function getNextPlaybackRate(faster, loop) {
        /// <summary>Select the playback rate which is after (faster or slower) than the current playback rate</summary>
        /// <param name="faster" type="bool">True for faster playrate (default), false for a slower playrate</param>
        /// <param name="loop" type="bool">True for loop-around (speed > max ==> min and vice versa) (default), false for stopping at the min/max</param>

        // Sanitize parameters
        faster = faster !== false; // cast to boolean, undefined = true
        loop = loop !== false;  // cast to boolean, undefined = true

        // Get current playback rate
        var speed = Math.round(self.GetPlaybackRate() * 100) / 100;

        // Fetch and sanitize available playback rates (non-destructively)
        var toggleSteps = $.extend([], playbackRates);
        if (toggleSteps.length == 0) {
            toggleSteps = [1];
        }

        // If selecting a slower speed, scan the list in reverse.  (Mentally flip "min" and "max")
        var sortCoefficient = (faster ? 1 : -1);
        toggleSteps.sort(function (a, b) { return sortCoefficient * (a - b); });

        // Select the next playback rate
        if (loop && 0 >= sortCoefficient * (toggleSteps[toggleSteps.length - 1] - speed)) { // speed > max, accounting for faster/slower
            // Speed > max, so loop around to min 
            speed = toggleSteps[0];
        } else {
            // Select the speed faster/slower than the current speed
            $.each(toggleSteps, function (i, thisToggleStep) {
                if (0 >= sortCoefficient * (thisToggleStep - speed) // speed >= thisToggleSpeed, accounting for faster/slower
                    && 0 < sortCoefficient * (toggleSteps[i + 1] - speed)) { // speed < toggleSteps[i + 1], same
                    speed = toggleSteps[i + 1];
                    return false;
                }
            });
        }

        return speed;
    };

    function positionUpdateTimerCallback() {
        var currentPosition = self.GetPosition();
        _events.fire(_eventNames.PositionChanged, currentPosition, true);
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

    function startPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
        positionUpdateTimer = setTimeout(positionUpdateTimerCallback, positionUpdateInterval);
    }

    function stopPositionUpdateTimer() {
        clearTimeout(positionUpdateTimer);
    }

    function reconnectOnLiveMediaOpenFailure() {
        mediaElement.controls.play();
    }

    function handleMediaError() {
        if (liveMediaOpenReconnector.HandleLiveMediaOpenFailure(reconnectOnLiveMediaOpenFailure)) {
            return; // The error is being handled by the LiveMediaOpenReconnector.
        }

        var errorDescription;
        switch (mediaElement.currentMedia.error.errorCode) {
            case -1072885294: //C00D11D2
            case -1072885353: //C00D1197			
                errorDescription = Mediasite.Player.Localization.MediaPlayer.FileNotFoundError;
                break;
            case -1072885352: //C00D1198
                errorDescription = Mediasite.Player.Localization.MediaPlayer.ServerConnectionError;
                break;
            case -1072885328: //C00D11B0 
                errorDescription = Mediasite.Player.Localization.MediaPlayer.ServerNotAvailable;
                break;
            default:
                errorDescription = mediaElement.currentMedia.error.errorDescription + " (" + mediaElement.currentMedia.error.errorCode + ")";
                break;
        }
        _events.fire(_eventNames.MediaError, errorDescription);
    }

    function UseDefaultAudioLanguage() {
        mediaElement.controls.currentAudioLanguageIndex = mediaElement.controls.audioLanguageCount;
    }

    function handleMediaEvent(event, param) {
        if (!window.Mediasite) return; // guard against Windows Media plugin calling event handlers after window globals are cleaned up

        switch (event) {
            case "OpenStateChange":
                if (param === 12) {
                    mediaPlayerView.MediaLoadStart();
                    _events.fire(_eventNames.MediaLoadStart);
                } else if (param === 13) {

                    if (!liveStartTime) {
                        self.Pause();
                    }

                    var aspectRatio = 0;
                    if (mediaElement.currentMedia.imageSourceHeight > 0 && mediaElement.currentMedia.imageSourceWidth > 0) {
                        aspectRatio = mediaElement.currentMedia.imageSourceWidth / mediaElement.currentMedia.imageSourceHeight;
                    }

                    var mediaInfo = {
                        AspectRatio: aspectRatio,
                        Duration: self.GetDuration(),
                        AudioOnly: (mediaElement.currentMedia.getItemInfo("MediaType") === "audio"),
                        CanChangeSpeed: !isLive(),
                        CanFullScreen: true,
                        CurrentLanguageIndex: self.GetCurrentAudioLanguageIndex(),
                        Languages: self.GetAudioLanguages(),
                        StreamType: viewOptions.streamType,
                        IsMainStream: viewOptions.isMainStream
                    };

                    mediaPlayerView.UpdateMediaInfo(mediaInfo);
                    _events.fire(_eventNames.MediaLoaded, mediaInfo);
                }
                break;
            case "PlayStateChange":
                switch (param) {
                    case Mediasite.Player.MediaState.Playing:
                        self.SetPlaybackRate(currentPlaybackRate);
                        if (self.GetPosition() == 0) self.SetPosition(1);
                        if (liveStartTime && mediaElement.controls.currentPosition < 2) {
                            // This operation expects that the media element's position == 0, which happens on _scriptApi.Stop()
                            // However, during buffering/playing hiccups, the media element's position does not reset
                            liveStartOffset = (new Date().getTime() - liveStartTime.getTime() - 10000);
                        }
                        if (mediaElement.controls.audioLanguageCount > 1) {
                            UseDefaultAudioLanguage();
                        }
                        startPositionUpdateTimer();
                        break;
                    case Mediasite.Player.MediaState.MediaEnded:
                        _events.fire(_eventNames.PositionChanged, self.GetDuration());
                    case Mediasite.Player.MediaState.Stopped:
                        stopPositionUpdateTimer();
                        _events.fire(_eventNames.PositionChanged, self.GetPosition());
                        lastSeekTime = 0;
                        _lastStopMediaEventTime = new Date().getTime();
                        _events.fire(_eventNames.PositionChanged, 0);
                        break;
                    case Mediasite.Player.MediaState.Paused:
                        stopPositionUpdateTimer();
                        _events.fire(_eventNames.PositionChanged, self.GetPosition());
                        break;
                    default:
                        stopPositionUpdateTimer();
                        break;
                }
                liveMediaOpenReconnector.setMediaState(param);
                _events.fire(_eventNames.StateChanged, param);
                break;
            case "PositionChange": // seeked
                if (manualSeekFlag) {
                    manualSeekFlag = false;
                }
                else {
                    lastSeekTime = self.GetPosition();
                }
                break;
            case "ScriptEvent":
                if (param.Type === "MS6") {
                    var actualMediaPosition = parseInt(param.Command, 10)
                    var newOffset, offsetDiff;
                    if (!isNaN(actualMediaPosition)) {
                        liveStartOffset = liveStartOffset || 0;
                        newOffset = actualMediaPosition - (mediaElement.controls.currentPosition * 1000);
                        offsetDiff = Math.abs(newOffset - liveStartOffset);
                        if (offsetDiff >= wmsTimeCorrectionThreshold) {
                            logger.Debug('Correcting WMS Live Offset -- old: ' + liveStartOffset + ', new: ' + newOffset);
                            liveStartOffset = newOffset;
                        }
                    }
                }
                _events.fire(_eventNames.MediaEvent, param);
                break;
        }
    }

    function waitForPlayerInit() {

        if (!mediaElement.settings) {
            setTimeout(waitForPlayerInit, 200);
            return;
        }

        mediaElement.settings.invokeURLs = false;

        if (mediaPlayerType === Mediasite.Player.MediaPlayerType.Port25) {
            // Port25 Global Events
            OnDSPlayStateChangeEvt = function (newState) {
                handleMediaEvent("PlayStateChange", newState);
            };
            OnDSPositionChangeEvt = function (newPosition, oldPosition) {
                handleMediaEvent("PositionChange", { newPosition: newPosition, oldPosition: oldPosition });
            };
            OnDSOpenStateChangeEvt = function (newState) {
                handleMediaEvent("OpenStateChange", newState);
            };
            OnDSScriptCommandEvt = function (scriptType, scriptParam) {
                handleMediaEvent("ScriptEvent", { Type: scriptType, Command: scriptParam });
            };
            OnDSMediaErrorEvt = function () {
                handleMediaError();
            };
        }
        else if (mediaPlayerType === Mediasite.Player.MediaPlayerType.WindowsMedia && mediaElement.attachWmpEvent) {
            // ActiveX in IE
            mediaElement.attachWmpEvent("OpenStateChange", function (state) { handleMediaEvent("OpenStateChange", state); });
            mediaElement.attachWmpEvent("PlayStateChange", function (state) { handleMediaEvent("PlayStateChange", state); });
            mediaElement.attachWmpEvent("PositionChange", function (position) { handleMediaEvent("PositionChange", position); });
            mediaElement.attachWmpEvent("ScriptCommand", function (scriptType, scriptParam) { handleMediaEvent("ScriptEvent", { Type: scriptType, Command: scriptParam }); });
            mediaElement.attachWmpEvent("MediaError", function () { handleMediaError(); });
        }

        mediaElement.URL = mediaSource;

        liveMediaOpenReconnector.setLiveStartGraceSeconds(60); // Live WMS is non-adaptive media and requires less time.

        self.SetVolume(volume);

        if (volume === 0) {
            mediaElement.settings.mute = true;
        }
    }

    this.SetupMediaElement = function (sources) {

        mediaPlayerView = $.extend(new Mediasite.MediaPlayer.BaseView(), new WindowsMediaView(viewOptions));
        element = mediaPlayerView.CreateMediaElement();
        mediaWrapper = mediaPlayerView.Wrapper;
        mediaElement = element.get(0);

        var source = viewOptions.selectedVideoSource;

        mediaSource = source.Location;
        logger.Debug("Media element source added", source.MimeType, source.Location);

        if (typeof (mediaSource) === 'undefined') {
            _events.fire(_eventNames.MediaError, Mediasite.Player.Localization.MediaPlayer.NoCompatibleMediaError);
            logger.Error("Media - no compatible media source");
            return;
        }

        setTimeout(waitForPlayerInit, 200);

        if (disableVolume) {
            this.Mute();
        }
    };

    this.UpdateOptions = function (options) {
        volume = options.Volume;
    };

    this.LiveStatusChange = function (status) {
        liveMediaOpenReconnector.setLiveStatus(status);
        if (status === Mediasite.Player.PresentationStatus.LiveEnded) {
            self.Stop();
            handleMediaEvent = function () { };
            handleMediaError = function () { };
        }
    };

    this.InactivityExceeded = function () {
        inactivityExceeded = true;
    };

    this.SetPosition = function (positionInMS) {
        if (inactivityExceeded === true) {
            window.location.reload();
            return;
        }

        if (positionInMS >= 0 && positionInMS <= self.GetDuration()) {
            manualSeekFlag = true;
            lastSeekTime = positionInMS;
            mediaElement.controls.currentPosition = positionInMS / 1000;
            _events.fire(_eventNames.PositionChanged, positionInMS);
        }
    };

    this.PlayDefaultRate = function () {
        var playbackRate = defaultPlaybackRate;
        self.SetPlaybackRate(playbackRate);
    };

    this.PlayFaster = function (doLoop) {
        doLoop = doLoop === true; // Default behavior: do not loop
        var playbackRate = getNextPlaybackRate(true, doLoop);
        self.SetPlaybackRate(playbackRate);
    };

    this.PlaySlower = function (doLoop) {
        doLoop = doLoop === true; // Default behavior: do not loop
        var playbackRate = getNextPlaybackRate(false, doLoop);
        self.SetPlaybackRate(playbackRate);
    };
    this.SkipBack = function () {
        var newPosition = self.GetPosition() - skipBackAmount;

        if (newPosition < 0) {
            newPosition = 0;
        }

        self.SetPosition(newPosition);
    };

    this.SkipForward = function () {
        var newPosition = self.GetPosition() + skipForwardAmount;

        if (newPosition < self.GetDuration()) {
            self.SetPosition(newPosition);
        }
    };

    this.FullScreen = function () {
        mediaElement.fullScreen = true;
    };

    this.Pause = function () {
        if (self.GetDuration() > 0) {
            mediaElement.controls.pause();
        } else {
            self.Stop();
        }
    };

    this.Play = function () {
        if (inactivityExceeded === true) {
            window.location.reload();
        }
        else {
            mediaElement.controls.play();

            if (mediaElement.settings.rate != _playbackRate) {
                // IE9 sets the rate to 1.0 whenever play() is called, 
                // but the correct behavior for our player is to maintain 
                // playback rate. Silly IE!
                mediaElement.settings.rate = _playbackRate;
            }
        }
    };

    this.Stop = function () {
        mediaElement.controls.stop();
    };

    this.GetPosition = function () {
        var position = mediaElement.controls.currentPosition * 1000 + liveStartOffset;

        if (lastSeekTime > 0) {
            var variance = position - lastSeekTime;
            if (_manualSeekThreshold < variance && variance < 0) {
                // The media element was recently instructed to seek to a given position,
                // but the media element is reporting a time slightly before that.
                // However, for consistency, the model should report the requested position
                // and assume the media will catch up (or seek elsewhere) soon.

                return lastSeekTime;
            }
        }

        return position;
    };

    this.GetDuration = function () {
        return mediaElement.currentMedia.duration * 1000;
    };

    this.SetVolume = function (val) {
        if (disableVolume) {
            mediaElement.settings.volume = 0;
            mediaElement.settings.mute = (val == 0);
        } else {
            mediaElement.settings.volume = Math.round(val);
            mediaElement.settings.mute = (val == 0);
            _events.fire(_eventNames.VolumeChanged, Math.round(self.GetVolume()));
        }
    };

    this.GetVolume = function () {
        return mediaElement.settings.volume;
    };

    this.StepVolumeUp = function () {

        if (!disableVolume) {
            if (mediaElement.settings.volume <= 80) {
                this.SetVolume(this.GetVolume() + 20);
            } else {
                this.SetVolume(100);
            }
        }
    };

    this.StepVolumeDown = function () {

        if (!disableVolume) {
            if (mediaElement.settings.volume > 0.2) {
                this.SetVolume(this.GetVolume() - 20);
            } else {
                this.SetVolume(0);
            }
        }
    };

    this.ToggleMute = function () {
        if (!disableVolume) {
            if (self.IsMuted()) {
                self.UnMute();
            } else {
                self.Mute();
            }
        }
    };

    this.Mute = function () {
        mediaElement.settings.mute = true;

        if (self.IsMuted()) {
            _events.fire(_eventNames.VolumeChanged, 0);
        }
    };

    this.UnMute = function () {

        if (!disableVolume) {
            mediaElement.settings.mute = false;

            if (self.GetVolume() === 0) {
                this.SetVolume(50);
            } else {
                _events.fire(_eventNames.VolumeChanged, Math.round(self.GetVolume()));
            }
        }
    };

    this.IsMuted = function () {
        return mediaElement.settings.mute;
    };

    this.TogglePlayPause = function () {
        if (mediaElement.playState === Mediasite.Player.MediaState.Playing) {
            self.Pause();
        } else {
            self.Play();
        }
    };

    this.SetPlaybackRate = function (speed) {
        currentPlaybackRate = speed;

        if (mediaElement) {
            mediaElement.settings.rate = speed;
        }
    };

    this.GetPlaybackRate = function () {
        return mediaElement.settings.rate;
    };

    this.GetCurrentAudioLanguageIndex = function () {
        try {
            return mediaElement.controls.currentAudioLanguageIndex;
        }
        catch (ex) {
            return 0;
        }

    };

    this.GetAudioLanguages = function () {
        var i, languageCount = 1;
        try {
            languageCount = mediaElement.controls.audioLanguageCount;
        }
        catch (ex) {
        }

        if (languageCount > 1) {
            var languages = [];
            for (i = 0; i < languageCount; ++i) {
                var lcid = mediaElement.controls.getAudioLanguageID(i + 1);
                languages[i] = { Index: i + 1, Locale: lcid, DisplayName: mediaElement.controls.getLanguageName(lcid) };
            }
            return languages;
        }
        return {};
    };

    this.SetAudioLanguage = function (language) {
        try {
            mediaElement.controls.currentAudioLanguageIndex = language.Index;
        } catch (ex) { }

    };

    this.SetLiveStartTime = function (time) {
        liveStartTime = time;
        liveMediaOpenReconnector.setLiveStartTime(time);
    };

    function isLive() {
        return !!liveStartTime;
    }

    this.RetrieveBookmark = function (bookmark) {
        clientBookmark = bookmark;
    };

    //*****************************************************************
    //* WindowsMediaView
    //*
    //* options 
    //*  mediaContainer
    //*  mediaPlayerType
    //*  localizedStrings
    //*  allowFullScreenVideo
    //*  playerLayoutOptions
    //*  enableContextMenu
    //*
    //*****************************************************************
    var WindowsMediaView = function (options) {

        var elementId = "MediaElement"
        var element;
        this.Wrapper = undefined;

        this.CreateMediaElement = function () {

            options.mediaContainer.find('.MediaWrapper').remove();

            var mediaWrapper = $('<div id="MediaWrapper" class="MediaWrapper"></div>').appendTo(options.mediaContainer);
            this.Wrapper = mediaWrapper;

            if (options.mediaPlayerType === Mediasite.Player.MediaPlayerType.Port25) {
                // using windowless video breaks the layout, just use normal windowed and realize overlays will not work...
                return $('<object id="MediaElement" type="application/x-ms-wmp"><param name="enableContextMenu" value="' + options.enableContextMenu + '"/><param name="stretchToFit" value="true"/><param name="windowlessVideo" value="false"/><param name="uiMode" value="none"/></object>').css('width', options.width).css('height', options.height).appendTo(mediaWrapper);
            }
            else {
                // using windowless = true here

                // Add the funky script elements needed to bind to WMP events.
                // Before IE11, we didn't need this ridiculousness.
                mediaWrapper.append(makeWmpScripts(elementId));


                // Now add the WMP object element.
                var wm7Player = document.createElement("OBJECT");

                // Add the param elements before setting the classid attribute. This seems to make IE7 through IE9 happy.
                wm7Player.appendChild(makeParam('windowlessVideo', 'true'));
                wm7Player.appendChild(makeParam('uiMode', 'none'));
                wm7Player.appendChild(makeParam('enableContextMenu', options.enableContextMenu));
                wm7Player.appendChild(makeParam('stretchToFit', 'true'));

                if (options.height && options.width) {
                    wm7Player.setAttribute("height", options.height);
                    wm7Player.setAttribute("width", options.width);
                }

                wm7Player.setAttribute("id", elementId);
                wm7Player.setAttribute("classid", "clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6");

                mediaWrapper.get(0).appendChild(wm7Player); // Use native JavaScript, not jQuery, to make IE7-8 happy. (Probably some issue with plugins overriding property-setters which jQuery isn't handling correctly.)


                // Add attachWmpEvent and fireWmpEvent methods to the media element 
                // to make use of the funky script elements added earlier.
                addWmpEventing(wm7Player);


                element = $(wm7Player);

                return $(wm7Player);
            }
        };

        this.UpdateMediaInfo = function (mediaInfo) {
            if (mediaInfo.AudioOnly) {
                element.css("height", "0px");
            }
        };


        function makeParam(name, value) {
            var p = document.createElement('param');
            p.setAttribute('name', name);
            p.setAttribute('value', value);
            return p;
        }

        function makeWmpScripts(elementId) {
            return '' +
            makeWmpEventScript(elementId, 'OpenStateChange') +
            makeWmpEventScript(elementId, 'PlayStateChange') +
            makeWmpEventScript(elementId, 'PositionChange') +
            makeWmpEventScript(elementId, 'ScriptCommand') +
            makeWmpEventScript(elementId, 'MediaError');
        }

        function makeWmpEventScript(objectId, eventName) {
            return '' +
            '<script for="' + objectId + '" event="' + eventName + '">' +
                'if (typeof arguments !== "undefined") {' +
                    'document.getElementById("' + objectId + '").fireWmpEvent("' + eventName + '", arguments);' +
                '}' +
            '</script>';
        }



        //
        // Define a mix-in to support our WMP eventing.
        //
        var addWmpEventing = function (wmp) {
            var events = {};
            wmp = $(wmp).get(0);

            wmp.attachWmpEvent = function (eventName, handler) {
                events[eventName] = events[eventName] || $.Callbacks();
                events[eventName].add(handler);
            };

            wmp.fireWmpEvent = function (eventName, args) {
                if (events[eventName] && events[eventName].fire) {
                    events[eventName].fire.apply(window, args);
                }
            };
        };

    };

};
