//  HULU CONFIDENTIAL MATERIAL. DO NOT DISTRIBUTE.
//  Copyright (C) 2009-2010 Hulu, LLC
//  All Rights Reserved
/**
 * htvController.js
 * 
 * Contains the controller for the application.   Maintains
 * the layout and state of the controls on the screen.   Also
 * controls navigation between controls.
 */
/*jslint maxerr: 1000, nomen: false, evil: false, immed: true, plusplus: false */
/*global LOG, describe, setTimeout, _performGeoCheck, _geoCheckPassed, _geoCheckFailed, _showIsASubscriberDialog */
/*global _resizePlayer, onLoginResult, _handleAuthenticationCodeCheck, _fireEvent, _showExitDialog */
/*global _restoreView, _saveCurrentView, onEndpointKeyExpired, onEndpointsReceived, onEndpointsRequestFailure, onEndpointsError */
/*global _loadEndpoints, _handleInput, _popView, _pushView, _popAllAndPushView, _makeBanyaRequest, _playVideo */
var $htv;

$htv.Controller = (function () {
    var _platformCanvasHandle,
        _root,
        _player,
        _state,
        _viewStack,
        _keyTimer,
        _volumeIndicator,
        _isHeld,
        _platformExitHandler,
        _exitActivityStatus,
        _currentViewHasNotReceivedInput,
        _listeningControls,
        _currentlyFiringEvent,
        _eventListenerAdditionQueue,
        _eventListenerRemovalQueue,
        _lastInputTime;
        
    _isHeld = false;
    _keyTimer = null;
    _platformCanvasHandle = null;
    _listeningControls = {};
    // state has an active Control pointer, which points to a node
    // in the tree.  That node contains a pointer to the actual control, 
    // which the controller passes events to.
    _state = {activeControl: null, logged_in: false, token: null, activeView: null};
    
    _currentViewHasNotReceivedInput = true;
    _viewStack = [];
    
    _lastInputTime = new Date();
    
    _currentlyFiringEvent = null;
    _eventListenerAdditionQueue = {};
    _eventListenerRemovalQueue = {};
    
    _platformExitHandler = null;
    _exitActivityStatus = {};
    
    function _onExitActivityEvent(key) {
        var activity;
        if (_platformExitHandler) {
            _exitActivityStatus[key] = true;
            for (activity in _exitActivityStatus) {
                if (_exitActivityStatus.hasOwnProperty(activity)) {
                    if (_exitActivityStatus[activity] === false) {
                        return;
                    }
                }
            }
            // we're done with all activities
            
            _platformExitHandler();
        }
    }
    
    // deinitializes the platform shim, conducting any necessary cleanup
    function _deinitialize(options) {
        if (options.exitReadyHandler) {
            _platformExitHandler = options.exitReadyHandler;
        }
        _exitActivityStatus.beacons = false;
        $htv.Beacons.trackSessionAppClose(options.closedByApp, function () {
            _onExitActivityEvent("beacons");
        });
        if ($htv.Player.isActive()) {
            if ($htv.Player.getViewToken()) {
                // send a type 1 banya request
                
                _exitActivityStatus.banya = false;
                _makeBanyaRequest(1, $htv.Player.getViewToken(), function () {
                    _onExitActivityEvent("banya");
                });
            }
            _exitActivityStatus.playbackprogress = false;
            $htv.Player.updatePlaybackProgress(true, function () {
                _onExitActivityEvent("playbackprogress");
            });
        }
        
        $htv.Platform.deinitialize(options);
    }
   
    function _exit() {
        
        $htv.Platform.exit();
    }
    
    function _readyForPostInitialize() {
        var lastRanVersion = $htv.Platform.readLocalData("profile", "last_ran_version");
        if (lastRanVersion !== $htv.Platform.properties.marketing_version) {
            $htv.Platform.writeLocalData("profile", "last_ran_version", $htv.Platform.properties.marketing_version);
        }
        
        $htv.Platform.postInitialize($htv.Constants.Endpoints.platformstatus, $htv.Constants.Endpoints.model_default_bitrates, {
            previous: lastRanVersion,
            current: $htv.Platform.properties.marketing_version
        });
    }

    function _handleEvent(eventType, eventData) {
        switch (eventType) {
        case "USER_INPUT":
            _lastInputTime = new Date();
            _handleInput(eventData);
            break;
        case "INITIALIZE_COMPLETE":
            
            // all users start out unauthenticated.
            // this must be invoked only after document.body is created!
            $htv.Platform.recordEvent("unauthenticated user");
            $htv.Beacons.initialize();
            $htv.Beacons.trackSessionAppOpen();
            _loadEndpoints(this, _performGeoCheck);
            break;
        case "PLATFORM_STATUS_CLEAR":
            if (_state.activeView === $htv.Views.NamedViews.ModalPlatformErrorView) {
                _state.activeView.handleEvent(eventType, eventData);
            }
            break;
        case "PLATFORM_STATUS_ERROR":
            if (_state.activeView === $htv.Views.NamedViews.ModalPlatformErrorView) {
                _state.activeView.handleEvent(eventType, eventData);
            }
            else {
                if ($htv.Player.isActive()) {
                    $htv.Player.stopVideo({force_stop: true});
                }
                _pushView("ModalPlatformErrorView", eventData);
            }
            break;
        default:
            
            break;
        }
    }
    
    

    // forward input to the active control
    function _handleInput(eventData) {
        
        var volumeChanged, i, changeRate;
        
        // ignore if first even to new view is up event.
        if (_currentViewHasNotReceivedInput === true) {
            if (eventData.is_down === false) {
                
                return;
            }
            _currentViewHasNotReceivedInput = false;
        }

        if (eventData.action === "KEY_EXIT" &&
            _state.activeView !== $htv.Views.NamedViews.DialogBoxView &&
            _state.activeView !== $htv.Views.NamedViews.ModalPlatformErrorView) {
/////            eventData.is_down === false) {
                
            _showExitDialog();
            
            return;
        }

        // Handle player-relevant buttons here if not in the PlayerView (which handles it itself),
        // not in the ModalPlatformErrorView (since don't want to honor these buttons), and if
        // the player is active
        if (eventData.is_down === false &&
            _state.activeView.viewName !== "PlayerView" &&
            _state.activeView.viewName !== "ModalPlatformErrorView" &&
            $htv.Player.isActive()) {
            
            if (_state.activeView.viewName !== "EndCreditsView" && eventData.action === "PLAYER_TOGGLE") {
                if (_state.activeView.viewName === "DialogBoxView") {
                    _popView();
                }
                if ($htv.Player.shouldShowEndCredits() === true) {
                    _resizePlayer($htv.Player.STATE_END_CREDITS);    
                }
                else {
                    _resizePlayer($htv.Player.STATE_MAXIMIZED);
                }
                return;
            }
            if (eventData.action === "STOP") {
                $htv.Player.stopVideo({force_stop: true});
                return;
            }
            if (eventData.action === "PAUSE") {
                if (!$htv.Player.isPaused()) {
                    $htv.Player.pauseVideo();
                    return;
                }
            }
            if (eventData.action === "PLAY") {
                if ($htv.Player.isPaused()) {
                    $htv.Player.resumeVideo();
                    return;
                }
            }
            if (eventData.action === "PLAY_PAUSE") {
                if ($htv.Player.isPaused()) {
                    $htv.Player.resumeVideo();
                } else {
                    $htv.Player.pauseVideo();
                }
                return;
            }
        }
        if (eventData.is_down === true) {
            volumeChanged = false;
            
            if (eventData.is_hold === true && _isHeld === false) {
                _isHeld = true;
            }
            
            changeRate = (_isHeld) ? 2 : 1;
            
            if (eventData.action === "VOLUME_UP") {
                for (i = 0; i < changeRate; i++) {
                    $htv.Platform.increaseVolume();
                }
                volumeChanged = true;
            }
            else if (eventData.action === "VOLUME_DOWN") {
                for (i = 0; i < changeRate; i++) {
                    $htv.Platform.decreaseVolume();
                }
                volumeChanged = true;
            }
            else if (eventData.action === "VOLUME_MUTE") {
                $htv.Platform.toggleMuteState();
                volumeChanged = true;
            }
            
            if (volumeChanged === true) {
                _fireEvent("VOLUME_CHANGED", eventData);
                return;
            }
        }
        else {
            if (eventData.is_hold === true && _isHeld === true) {
                _isHeld = false;
            }
        }
        
        _state.activeView.handleEvent("USER_INPUT", eventData);
    }
    
    function _showExitDialog() {
        var inPlayerView, inEndCreditsView, isPlaying, items;
        
        if ($htv.Platform.properties.has_in_app_exit === false) {
            return;
        }
        
        inPlayerView = _state.activeView && _state.activeView.viewName === "PlayerView";
        inEndCreditsView = _state.activeView && _state.activeView.viewName === "EndCreditsView";
 
        if (inPlayerView || inEndCreditsView) {
            _resizePlayer($htv.Player.STATE_MINIMIZED);
        }
        
        isPlaying = !$htv.Player.isPaused();
        
        if (isPlaying) {
            $htv.Player.pauseVideo();
        }
        
        items = [{
            text: "Yes",
            callback: function () {
                _exit();
            },
            receiver: this
        }, {
            text: "No",
            callback: function () {
                if (inPlayerView) {
                    _resizePlayer($htv.Player.STATE_MAXIMIZED);
                }
                else if (inEndCreditsView) {
                    _resizePlayer($htv.Player.STATE_END_CREDITS);
                }
                if (isPlaying) {
                    $htv.Player.resumeVideo();
                }
                
            },
            receiver: this
        }];
        
        $htv.Controller.pushView("DialogBoxView", {
            text: "Are you sure you want to quit?",
            items: items,
            can_close_with_exit: false,
            return_closure: {
                callback: function () {
                    if (inPlayerView) {
                        _resizePlayer($htv.Player.STATE_MAXIMIZED);
                    }
                    else if (inEndCreditsView) {
                        _resizePlayer($htv.Player.STATE_END_CREDITS);
                    }
                    if (isPlaying) {
                        $htv.Player.resumeVideo();
                    }
                },
                receiver: this
            }
        });
    }


    // TODO: very concerned about endpoints loadURL failing
    function _performGeoCheck() {
        if ($htv.Constants.Endpoints.geok_response === "allowed" || $htv.Constants.Endpoints.disable_geocheck === true) {
            _geoCheckPassed();
        }
        else {
            _geoCheckFailed();
        }
    }
    
    function _geoCheckPassed() {
        var authentication_code, device_token;
        
        $htv.Beacons.trackDataloadGeocheck("ok");
        
        device_token = $htv.Platform.readLocalData("profile", "device_token");
        if ($htv.Utils.stringIsNullOrEmpty(device_token)) {
            authentication_code = $htv.Platform.readLocalData("profile", "authentication_code");

            if ($htv.Utils.stringIsNullOrEmpty(authentication_code)) {
                _showIsASubscriberDialog();
            } 
            else {
                $htv.Profile.checkAuthenticationCode(authentication_code, this, _handleAuthenticationCodeCheck);
            }
        }
        else {
            $htv.Profile.authenticateDeviceToken(device_token, this, onLoginResult);
        }
    }
   
    function _showIsASubscriberDialog(msg) {
        if (msg === undefined) {
            msg = "Are you already a Hulu Plus subscriber?";
        }

        var items = [{
            text: "Yes",
            callback: function () {
                $htv.Controller.pushView("ActivationView", {});
            },
            receiver: this
        }, {
            text: "No",
            callback: function () {
                $htv.Controller.pushView("LearnMoreView", {});
            },
            receiver: this
        }];
            
        $htv.Controller.popAllAndPushView("DialogBoxView", {
            text: msg,
            items: items,
            override_default_pop: true
        });       
        _readyForPostInitialize();
    }

    function _showAccountOnHoldDialog() {
        var items, device_token, cb;
        items = [{
            text: "Information Updated, Retry Now",
            callback: function () {
                _pushView("LoadingView");
                device_token = $htv.Platform.readLocalData("profile", "device_token");
                cb = function (result) {
                    // pop the loading view to avoid unbounded view stack growth
                    _popView();
                    onLoginResult(result);
                };
                $htv.Profile.authenticateDeviceToken(device_token, this, cb);
            },
            receiver: this
        }, {
            text: "Close",
            callback: function () {
                $htv.Controller.popAllAndPushView("ActivationView", {});
            },
            receiver: this
        }];
        
        $htv.Controller.pushView("DialogBoxView", {
            text: "We're having a problem charging your credit card.  To reactivate your subscriptions please go to www.hulu.com/account.",
            textStyle: $htv.Styles.DialogBoxMessageSmall,
            textWidth: 550,
            textXPadding: 0,
            textYPadding: 64,
            titleText: "Your Hulu Plus Subscription Is On Hold",
            titleStyle: $htv.Styles.DialogBoxTitle,
            items: items,
            background: "images/medium-dialog-background.png",
            buttonYInitial: 310,
            buttonWidth: 315,
            horizontalButtons: false,
            useHorizons: false
        });
    }
            

    function _geoCheckFailed() {
        var reportUrl, items;
        $htv.Beacons.trackDataloadGeocheck("blocked");
        
        reportUrl = "http://www.hulu.com/report-geocheck";
        if (!$htv.Utils.stringIsNullOrEmpty($htv.Constants.Endpoints.reportGeocheckURL)) {
            reportUrl = $htv.Constants.Endpoints.reportGeocheckURL;
        }
        reportUrl = reportUrl.replace("http://", ""); // make user friendlier
        
        items = [];
        if ($htv.Platform.properties.has_in_app_exit !== false) {
            items.push({
                text: "Exit",
                callback: function () {
                    _pushView("LoadingView");
                    _exit();
                },
                receiver: this
            });
        }
        
        $htv.Controller.pushView("DialogBoxView", {
            text: "We're sorry, currently our video library is only available within the United States. If you received this message in error, please go to " + reportUrl + " to report it.",
            textStyle: $htv.Styles.InternationalDescription,
            textWidth: 456,
            textXPadding: 52,
            textYPadding: 64,
            titleText: "International",
            titleStyle: $htv.Styles.InternationalTitle,
            items: items,
            buttonYInitial: 340,
            buttonWidth: 186,
            horizontalButtons: true,
            useHorizons: false
        });
    }
    
    function _handleAuthenticationCodeCheck(result) {
        if (result.success) {
            
            _popAllAndPushView("MainView", {});
        }
        else {
            
            _showIsASubscriberDialog();
        }
        
    }
    
    /*jslint bitwise: false */
    function _generateNonce() {
        var nonce = (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
        nonce += (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
        nonce += (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
        nonce += (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
        return nonce;
    }
    /*jslint bitwise: true */
   
    function _makeBanyaRequest(type, view_token, callback) {
        var auth, csel_s, token, d, timestamp, n, url, deviceId;
        
        csel_s = $htv.Constants.CSEL_S;
        d = new Date();
        timestamp = Math.round(d / 1000);
        n = _generateNonce();
        if (type === 1) {
            auth = $htv.Libs.SHA1.SHA1(csel_s + view_token + timestamp + n);
            token = "&view_token=" + view_token;
        } else if (type === 2) {
            deviceId = $htv.Platform.properties.deviceid;
            auth = $htv.Libs.SHA1.SHA1(csel_s + deviceId + timestamp + n);
            
            deviceId = $htv.Utils.urlEncode(deviceId);
            
            token = "&device_id=" + deviceId + "&device=" + $htv.Platform.properties.platformCode;
        } else {
            
            return;
        }
        
        // url = $htv.Constants.Endpoints.csel + "/sterm?v=1&token=" + $htv.Profile.getUserToken() + token + "&time_stamp=" + timestamp + "&auth=" + auth;
        url = "http://s.hulu.com/sterm?v=1&token=" + $htv.Utils.urlEncode($htv.Profile.getUserToken()) + token + "&time_stamp=" + timestamp + "&n=" + n + "&auth=" + auth;
        
        $htv.Platform.loadURL(url, this, function () {
            
            if (callback) {
                callback();
            }
        }, {
            errorCallback: function () {
                
                if (callback) {
                    callback();
                }
            },
            xmlparse: false
        });
    }
    
    function _isKeyHeld() {
        return _isHeld;
    }
    
    function _loadEndpoints(receiver, callback, forceExpiration) {
        // TODO: add timeout (hook up to geocheck/timeout beacon?)
        
        var device, rv, version, encrypted_nonce, endpointsURL;
        if (forceExpiration !== true && $htv.Constants.Endpoints.expired === false) {
            if (callback !== undefined && receiver !== undefined) {
                callback.call(receiver, $htv.Constants.Endpoints);
            }
        }
        
        device = $htv.Platform.properties.platformCode;
        if (device === undefined) {
            
            device = 2;
        }
        version = $htv.Platform.properties.platformVersion;
        if (version === undefined) {
            
            version = 1;
        }
        rv = Math.floor(Math.random() * Math.pow(2, 31) - 1);
        encrypted_nonce = $htv.Libs.md5.hex_md5([$htv.Platform.properties.platformKey, device, version, rv].join(','));
        endpointsURL = $htv.Constants.CONFIG_PATH + "?device=" + device + 
                           "&rv=" + rv + 
                           "&version=" + version +
                           "&encrypted_nonce=" + encrypted_nonce;
        $htv.Platform.loadURL(endpointsURL, this, onEndpointsReceived, {
            xmlparse: false,
            method: "POST",
            userCallback: callback,
            userReceiver: receiver,
            errorCallback: onEndpointsRequestFailure,
            compress: $htv.Platform.properties.has_gzip
        });
    }
    
    function onEndpointsRequestFailure(status, errorObj) {
        // process the error based on the response body
        
        onEndpointsError();
    }
    
    function onEndpointsError(msg) {
        if (!$htv.Utils.stringIsNullOrEmpty($htv.Constants.Endpoints.key)) {
            return;
        }
    
        if ($htv.Utils.stringIsNullOrEmpty(msg)) {
            msg = "Sorry, there was an issue contacting the Hulu Plus service. Please check your network settings and make sure that your device's date and time are set correctly.";
        }
        
        var items = [];
        if ($htv.Platform.properties.has_in_app_exit !== false) {
            items.push({
                text: "Exit",
                callback: function () {
                    _pushView("LoadingView");
                    _exit();
                },
                receiver: this
            });
        }
        
        $htv.Controller.pushView("DialogBoxView", {
            text: msg,
            textWidth: 470,
            textXPadding: 52,
            textYPadding: 7,
            items: items,
            buttonYInitial: 340
        });
    }
    
    function onEndpointsReceived(responseText, options) {
        var result, msg = null; // default error msg: onEndpointsError(null) ?
        // If the 200 starts with '*', use the response as an error
        if (responseText.indexOf("*") === 0) {
            
            switch (responseText.substring(1)) {
            case "Device not found":
                msg = "Hulu Plus is no longer supported on this device. Sorry for the inconvenience.";
                break;
            case "Invalid version":
            case "Invalid nonce":
                msg = "A required update to this application is available. Please install this update to continue to use this application. Sorry for the inconvenience.";
                break;
            default:
                break;
            }
            onEndpointsError(msg);
            return;
        }
        
        
        try {
            result = $htv.Utils.parseDeejayResponse({
                cipher_text: responseText,
                device_key: $htv.Platform.properties.platformKey
            });
            describe(result);
        } 
        catch (e) {
            
            onEndpointsError();
            return;
        }
        // populate constants.
        $htv.Constants.Endpoints = result;
        
        if ($htv.Constants.Endpoints.inactivity_hours === undefined) {
            $htv.Constants.Endpoints.inactivity_hours = 4;
        }
        
        if ($htv.Constants.Endpoints.autoplay_default === true) {
            if ($htv.Profile.getPreference("autoplay_enabled") === undefined || $htv.Profile.getPreference("autoplay_enabled") === null) {
                $htv.Profile.setPreference("autoplay_enabled", true);
            }
        }
        
        $htv.Constants.Endpoints.expired = false;
        if ($htv.Constants.Endpoints.expiration_notice_hours === undefined) {
            $htv.Constants.Endpoints.expiration_notice_hours = 672;
        }
        
        
        // try to refresh key after half of the expiration time.
        _keyTimer = $htv.Platform.createTimer($htv.Constants.Endpoints.key_expiration * 500, this, onEndpointKeyExpired);
        _keyTimer.start();
        if (options.userCallback !== undefined && options.userReceiver !== undefined) {
            options.userCallback.call(options.userReceiver, $htv.Constants.Endpoints);
        }
    }
    
    function onEndpointKeyExpired() {
        
        _keyTimer.stop();
        $htv.Constants.Endpoints.expired = true;
        _loadEndpoints();
    }
    
    // initializes the platform shim to begin receiving input,
    // and begins load of the main menu.
    function _initialize(options) {
        _platformCanvasHandle = $htv.Platform.bootstrap(options);
        $htv.Platform.attachEventHandlers($htv.Controller, _handleEvent, $htv.Player, $htv.Player.handlePlayerEvent);
        _pushView("LoadingView");
        // todo: delay popping loading view till later - after xapi etc 
        
        // will fire init complete.
        $htv.Platform.initialize(options);
        
        _volumeIndicator = {
            type: "VolumeIndicator",
            position: {
                x: 0,
                y: 0,
                z: 2,
                width: 960,
                height: 100
            },
            control: null
        };
        _volumeIndicator.control = $htv.ControlPool.getObject("VolumeIndicator");
        _volumeIndicator.control.initialize(_volumeIndicator.position, _volumeIndicator, null, options);
        // do this once, instead of on playVideo
        $htv.Controller.addEventListener("PROFILE_STATE_CHANGED", $htv.Player);
    }
    
    function onLoginResult(result) {
        
        describe(result);
        if (result.success) {
            if ($htv.Profile.isPlusUser()) {
                if ($htv.Profile.accountOnHold()) {
                    $htv.Platform.recordEvent("plus-account-hold user logged in");
                    _showAccountOnHoldDialog();
                } else {
                    $htv.Platform.recordEvent("plus user logged in");
                    _popAllAndPushView("MainView", {});
                }
            } else {
                $htv.Platform.recordEvent("non-plus user logged in");
                _showIsASubscriberDialog();
            }
        } else {
            // todo: \n's may not work on some platforms
            _showIsASubscriberDialog("Your user session has expired.\nAre you already a Hulu Plus subscriber?");
        }
    }

    function _addEventListenerInternal(event_name, control) {
        if (_listeningControls[event_name] === undefined) {
            _listeningControls[event_name] = [];
        }
        //
        /* for debugging
        if (_listeningControls[event_name].indexOf(control) !== -1) {
            
        }
        */
        _listeningControls[event_name].push(control);
    }

    function _removeEventListenerInternal(event_name, control) {
        var i, index = -1;
        for (i = 0; i < _listeningControls[event_name].length; i++) {
            if (_listeningControls[event_name][i] === control) {
                index = i;
            }
        }
        if (index !== -1) {
            _listeningControls[event_name].splice(index, 1);
        }
        //
    }
   
    function _addEventListener(event_name, control) {
        if (_currentlyFiringEvent === event_name && _currentlyFiringEvent !== null) {
            if (!_eventListenerAdditionQueue[event_name]) {
                _eventListenerAdditionQueue[event_name] = [];
            }
            _eventListenerAdditionQueue[event_name].push(control);
        }
        else {
            _addEventListenerInternal(event_name, control);
        }
    }
    
    function _removeEventListener(event_name, control) {
        if (_currentlyFiringEvent === event_name && _currentlyFiringEvent !== null) {
            if (!_eventListenerRemovalQueue[event_name]) {
                _eventListenerRemovalQueue[event_name] = [];
            }
            _eventListenerRemovalQueue[event_name].push(control);
        }
        else {
            _removeEventListenerInternal(event_name, control);
        }
    }
    
    function _fireEvent(event_name, eventData) {
        var i;
        _currentlyFiringEvent = event_name;
        // 
        
        if (event_name === "MAIN_VIEW_INITIALIZE_COMPLETE") {
            _readyForPostInitialize();
            // todo: MAKE BANYA START request..  type2
            _makeBanyaRequest(2);
        }
        if (_state.activeView !== null) {
            _state.activeView.handleEvent(event_name, eventData);
            // 
        }
        // incerpting after handing event to active views. this lets the views that need to pop themselves.
        if (event_name === "PLAYER_PLAYBACK_FINISHED") {
            // force stop > force auto play
            // use truthiness since we only care when force_stop is explicitly true
            if (eventData.next_video.content_id !== 0 &&
                ($htv.Profile.getPreference("autoplay_enabled") === true || eventData.force_autoplay === true) &&
                ((new Date().getTime() - _lastInputTime.getTime()) / (1000 * 60 * 60) < $htv.Constants.Endpoints.inactivity_hours) &&
                !eventData.force_stop) {
                
                setTimeout(function () {
                    
                    if (eventData.final_size === $htv.Player.STATE_MINIMIZED) {
                        _playVideo(eventData.next_video.content_id, {next_video_size: $htv.Player.STATE_MINIMIZED, next_video_type: eventData.next_video_type});    
                    }
                    else {
                        _playVideo(eventData.next_video.content_id, {next_video_size: $htv.Player.STATE_MAXIMIZED, next_video_type: eventData.next_video_type});
                    }
                }, 0);
            }
        }                
        if (_listeningControls[event_name] === undefined || _listeningControls[event_name].length === 0) {
            // 
            _currentlyFiringEvent = null;
            return; // no listeners here!
        }
        for (i = 0; i < _listeningControls[event_name].length; i++) {
            _listeningControls[event_name][i].handleEvent(event_name, eventData);
            // 
        }
        
        // 
        _currentlyFiringEvent = null;
        
        if (_eventListenerAdditionQueue[event_name]) {
            for (i = 0; i < _eventListenerAdditionQueue[event_name].length; i++) {
                _addEventListenerInternal(event_name, _eventListenerAdditionQueue[event_name][i]);               
            }
        }
        _eventListenerAdditionQueue[event_name] = null;
        
        if (_eventListenerRemovalQueue[event_name]) {
            for (i = 0; i < _eventListenerRemovalQueue[event_name].length; i++) {
                _removeEventListenerInternal(event_name, _eventListenerRemovalQueue[event_name][i]);               
            }
        }
        _eventListenerRemovalQueue[event_name] = null;
        
        // 
    }
    
    function _popView(preventExit) {
        
        
        // Don't allow exit conditions here
        if (_viewStack.length <= 0 && $htv.Platform.properties.has_in_app_exit === false) {
            return;
        }
        $htv.Platform.hideSpotlight();
        _fireEvent("VIEW_WILL_DISAPPEAR", {});
        _state.activeView.discard();
        _fireEvent("VIEW_POPPED", {});
        _restoreView();
        if (_state.activeView === null) {
            if (!preventExit) { // true for undefined, false.
                
                _exit();
            }
        }
    }
    
    function _popToHome() {
        while (_state.activeView.viewName !== "MainView") {
            $htv.Platform.hideSpotlight();
            _fireEvent("VIEW_WILL_DISAPPEAR", {});
            _state.activeView.discard();
            _fireEvent("VIEW_POPPED", {});
            _restoreView();
        }
    }
    
    function _popAllAndPushView(viewName, options) {
        
        while (_state.activeView !== null) {
            $htv.Platform.hideSpotlight();
            _fireEvent("VIEW_WILL_DISAPPEAR", {});
            _state.activeView.discard();
            _fireEvent("VIEW_POPPED", {});
            _restoreView();
        }
        _pushView(viewName, options);
    }
    
    // play video with given content id
    // TODO,  purpose of this was to detach controls from the player.
    // either we proxy stuff to the player through controller, or just let
    // controls/views talk to the player directly.
    // may update: i think this is the right abstraction, as we now have a PlayerView.
    function _playVideo(contentid, options) {
        if (!options) {
            options = {};
        }
        if (_state.activeView.viewName === "SampleVideosView") {
            
            options.disable_minimize = true;
            options.disable_autoplay = true;
            options.dismiss_end_credits = true;
        }
        // if none specified, maximized is default
        if (!options.hasOwnProperty("next_video_size")) {
            options.next_video_size = $htv.Player.STATE_MAXIMIZED;
        }
        if (options.next_video_size === $htv.Player.STATE_MAXIMIZED) {
            _pushView("PlayerView", options);    
        }
        $htv.Player.playVideo(contentid, options);
    }
    
    function _pushView(viewName, options) {
        
        var overrideSpotlightAnimation = _state.activeView !== null && _state.activeView._overrideLastSpotlightAnimation;
        _fireEvent("VIEW_WILL_DISAPPEAR", {});
        _saveCurrentView();
        if ($htv.Views.NamedViews[viewName]) {
            _state.activeView = $htv.Views.NamedViews[viewName];
            $htv.Platform.hideSpotlight(overrideSpotlightAnimation || _state.activeView._overrideFirstSpotlightAnimation);
            _state.activeView.initialize(options);
        } else {
            
            _restoreView();
        }
        if (options === undefined) {
            options = {};
        }
        _fireEvent("VIEW_PUSHED", {view_name: viewName, view_title: options.view_title, previous_view_title: options.previous_view_title});
        _currentViewHasNotReceivedInput = true;
    }
    
    function _resizePlayer(nextSize, options) {
        var pushOptions, shouldResize = false;
        if (!$htv.Player.isActive()) {
            
            return;
        }
        switch (nextSize) {
        case $htv.Player.STATE_MAXIMIZED:
            pushOptions = (options) ? options : {};
            pushOptions.maximize = true;
            if (_state.activeView.viewName === "EndCreditsView") {
                _popView();
                
            }
            else if (_state.activeView.viewName === "SampleVideosView") {
                
                pushOptions.disable_minimize = true;
                pushOptions.disable_autoplay = true;
                pushOptions.dismiss_end_credits = true;
            }
            _pushView("PlayerView", pushOptions);
            shouldResize = true;
            break;
        case $htv.Player.STATE_MINIMIZED:
            if (_state.activeView.viewName === "PlayerView" || _state.activeView.viewName === "EndCreditsView") {
                _popView();
                
            }
            shouldResize = true;
            break;
        case $htv.Player.STATE_END_CREDITS:
            // TODO: is should showendcredits checked elsewhere. do we want to check this?
            if ($htv.Player.shouldShowEndCredits() === false) {
                
                return;
            }
            if (_state.activeView.viewName === "PlayerView") {
                _popView();
                
            }
            _pushView("EndCreditsView", options);
            shouldResize = true;
            break;
        }
        // makes sure that the nextSize is a valid value
        if (shouldResize === true) {
            
            $htv.Player.resize(nextSize);    
        }
    }

    // view control methods
    function _restoreView() {
        var view, travNode, node, i;
        // pops view and restores state.
        if (_viewStack.length === 0) {
            _state.activeView = null;
            
            return;
        }
        view = _viewStack.pop();
        
        $htv.Platform.hideSpotlight((_state.activeView !== null && _state.activeView._overrideLastSpotlightAnimation) ||
                $htv.Views.NamedViews[view.viewName]._overrideFirstSpotlightAnimation);
        $htv.Views.NamedViews[view.viewName].handleEvent("VIEW_WILL_REAPPEAR", {});
        $htv.Views.NamedViews[view.viewName].restoreState(view);
        $htv.Views.NamedViews[view.viewName].handleEvent("VIEW_DID_REAPPEAR", {});
        
        _state.activeView = $htv.Views.NamedViews[view.viewName];
        _currentViewHasNotReceivedInput = true;
    }
    
    function _saveCurrentView() {
        if (_state.activeView !== null) {
            // recurse through children that we're switching away from, and release them into the pool.
            var view = _state.activeView.saveState();
            _viewStack.push(view);
        }
    }
    
    return {
        // public interface
        addEventListener: _addEventListener,
        removeEventListener: _removeEventListener,
        deinitialize: _deinitialize,
        exit: _exit,
        fireEvent: _fireEvent,
        initialize: _initialize,
        isKeyHeld: _isKeyHeld,
        loadEndPoints: _loadEndpoints,
        resizePlayer: _resizePlayer,
        makeBanyaRequest: _makeBanyaRequest, 
        playVideo: _playVideo,
        popToHome: _popToHome,
        popAllAndPushView: _popAllAndPushView,
        popView: _popView,
        pushView: _pushView,
        showAccountOnHoldDialog: _showAccountOnHoldDialog,
        showExitDialog: _showExitDialog
    };
}());
