(function () {
    'use strict';

    /**
     * Constants of states for Chromecast device
     **/
    var DEVICE_STATE = {
        'NOT_PRESENT': 0,
        'IDLE': 1,
        'ACTIVE': 2,
        'WARNING': 3,
        'ERROR': 4
    };

    var PLAYER_STATE = {
        'IDLE': 'IDLE',
        'LOADING': 'LOADING',
        'LOADED': 'LOADED',
        'PLAYING': 'PLAYING',
        'PAUSED': 'PAUSED',
        'SEEKING': 'SEEKING'
    };

    /**
     * Cast player object
     * main variables:
     *  - deviceState for Cast mode:
     *    IDLE: Default state indicating that Cast extension is installed, but showing no current activity
     *    ACTIVE: Shown when Chrome has one or more local activities running on a receiver
     *    WARNING: Shown when the device is actively being used, but when one or more issues have occurred
     *    ERROR: Should not normally occur, but shown when there is a failure
     *  - Cast player variables for controlling Cast mode media playback
     *  - Local player variables for controlling local mode media playbacks
     *  - Current media variables for transition between Cast and local modes
     */
    var CastPlayer = function () {

        /* device variables */

        // @type {DEVICE_STATE} A state for device
        this.deviceState = DEVICE_STATE.NOT_PRESENT;

        /* Cast player variables */

        // @type {Object} a chrome.cast.media.Media object
        this.currentMediaSession = null;

        // @type {Number} volume between 0 and 100
        this.currentVolume = 50;

        // @type {Boolean} A flag for autoplay after load
        this.autoplay = true;

        // @type {string} a chrome.cast.Session object
        this.session = null;

        // @type {PLAYER_STATE} A state for Cast media player
        this.castPlayerState = PLAYER_STATE.IDLE;

        /* Local player variables */

        // @type {PLAYER_STATE} A state for local media player
        this.localPlayerState = PLAYER_STATE.IDLE;

        // @type {video} local player
        this.localPlayer = null;

        /* Current media variables */

        // @type {Boolean} Muted audio
        this.muted = false;

        // @type {Number} A number for current media offset
        this.currentMediaOffset = 0;

        // @type {Number} A number for current media time, relative to offset
        this.currentMediaTime = 0;

        // @type {Number} A number for current media duration
        this.currentMediaDuration = model.duration;

        // @type {Boolean} A boolean to stop timer update of progress when triggered by media status event
        this.seekInProgress = false;

        this.updateDurationLabel();
        this.initializeUI();
        this.initializeLocalPlayer();
        this.initializeCastPlayer();
        this.playMediaLocally(0);
    };

    /**
     * Initialize local media player
     */
    CastPlayer.prototype.initializeLocalPlayer = function () {
        this.localPlayer = $('#videoPlayer').get(0);
        this.localPlayer.volume = this.currentVolume/100;
        this.localPlayer.addEventListener("timeupdate", this.updateLocalProgress.bind(this));
        this.localPlayer.addEventListener("play", this.updateLocalState.bind(this));
        this.localPlayer.addEventListener("pause", this.updateLocalState.bind(this));
        this.localPlayer.addEventListener("playing", this.updateLocalState.bind(this));
        this.localPlayer.addEventListener("paused", this.updateLocalState.bind(this));
    };

    CastPlayer.prototype.updateLocalProgress = function () {
        var newTime = Math.round(this.localPlayer.currentTime);
        if (newTime != this.currentMediaTime && !this.seekInProgress) {
            this.currentMediaTime = newTime;
            this.updateProgressBar();
        }
    };

    CastPlayer.prototype.updateLocalState = function () {
        if (this.localPlayer.paused) {
            this.localPlayerState = PLAYER_STATE.PAUSED;
        } else {
            this.localPlayerState = PLAYER_STATE.PLAYING;
        }
        this.updateMediaControlUI();
    };

    /**
     * Initialize Cast media player
     * Initializes the API. Note that either successCallback and errorCallback will be
     * invoked once the API has finished initialization. The sessionListener and
     * receiverListener may be invoked at any time afterwards, and possibly more than once.
     */
    CastPlayer.prototype.initializeCastPlayer = function () {

        if (!window.chrome) {
            return;
        }

        if (!chrome.cast || !chrome.cast.isAvailable) {
            setTimeout(this.initializeCastPlayer.bind(this), 1000);
            return;
        }
        // request session
        var applicationID = "9EAA0B71";
        var sessionRequest = new chrome.cast.SessionRequest(applicationID);
        var apiConfig = new chrome.cast.ApiConfig(sessionRequest,
                this.sessionListener.bind(this),
                this.receiverListener.bind(this));

        chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.onError.bind(this));
        this.timer = setInterval(this.incrementMediaTime.bind(this), 1000);
    };

    /**
     * Callback function for init success
     */
    CastPlayer.prototype.onInitSuccess = function () {
        console.log("init success");
        this.updateMediaControlUI();
    };

    /**
     * Generic error callback function
     */
    CastPlayer.prototype.onError = function () {
        console.log("error");
    };

    /**
     * @param {!Object} e A new session
     * This handles auto-join when a page is reloaded
     * When active session is detected, playback will automatically
     * join existing session and occur in Cast mode and media
     * status gets synced up with current media of the session
     */
    CastPlayer.prototype.sessionListener = function (e) {
        this.session = e;
        if (this.session) {
            this.deviceState = DEVICE_STATE.ACTIVE;
            if (this.session.media[0]) {
                this.onMediaDiscovered('activeSession', this.session.media[0]);
            }
            else {
                this.loadMedia();
            }
            this.session.addUpdateListener(this.sessionUpdateListener.bind(this));
        }
    };

    /**
     * @param {string} e Receiver availability
     * This indicates availability of receivers but
     * does not provide a list of device IDs
     */
    CastPlayer.prototype.receiverListener = function (e) {
        if (e === 'available') {
            console.log("receiver found");
            this.deviceState = DEVICE_STATE.IDLE;
        }
        else {
            console.log("receiver list empty");
            this.deviceState = DEVICE_STATE.NOT_PRESENT;
        }
        this.updateMediaControlUI();
    };

    /**
     * session update listener
     */
    CastPlayer.prototype.sessionUpdateListener = function (isAlive) {
        if (!isAlive) {
            this.session = null;
            this.deviceState = DEVICE_STATE.IDLE;
            this.castPlayerState = PLAYER_STATE.IDLE;
            console.log(this.castPlayerState + " (sessionUpdateListener)");
            this.currentMediaSession = null;

            // continue to play media locally
            this.playMediaLocally(this.currentMediaOffset + this.currentMediaTime);
            this.updateMediaControlUI();
        }
    };

    /**
     * Requests that a receiver application session be created or joined. By default, the SessionRequest
     * passed to the API at initialization time is used; this may be overridden by passing a different
     * session request in opt_sessionRequest.
     */
    CastPlayer.prototype.launchApp = function () {
        console.log("launching app...");
        chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this));
    };

    /**
     * Callback function for request session success
     * @param {Object} e A chrome.cast.Session object
     */
    CastPlayer.prototype.onRequestSessionSuccess = function (e) {
        console.log("session success: " + e.sessionId);
        this.session = e;
        this.deviceState = DEVICE_STATE.ACTIVE;
        this.stopMediaLocally();
        this.loadMedia();
        this.session.addUpdateListener(this.sessionUpdateListener.bind(this));
    };

    /**
     * Callback function for launch error
     */
    CastPlayer.prototype.onLaunchError = function () {
        console.log("launch error");
        this.deviceState = DEVICE_STATE.ERROR;
    };

    /**
     * Stops the running receiver application associated with the session.
     */
    CastPlayer.prototype.stopApp = function () {
        this.session.stop(this.onStopAppSuccess.bind(this, 'Session stopped'),
                this.onError.bind(this));

    };

    /**
     * Callback function for stop app success
     */
    CastPlayer.prototype.onStopAppSuccess = function (message) {
        console.log(message);
        this.deviceState = DEVICE_STATE.IDLE;
        this.castPlayerState = PLAYER_STATE.IDLE;
        console.log(this.castPlayerState + " (onStopAppSuccess)");
        this.currentMediaSession = null;

        // continue to play media locally
        this.playMediaLocally(this.currentMediaOffset + this.currentMediaTime);
        this.updateMediaControlUI();
    };

    /**
     * Loads media into a running receiver application
     */
    CastPlayer.prototype.loadMedia = function () {
        if (!this.session) {
            console.log("no session");
            return;
        }
        var offset = this.currentMediaOffset + this.currentMediaTime;
        this.currentMediaOffset = offset;
        this.currentMediaTime = 0;

        var url = model.remoteStreamUrl + "&maxBitRate=" + this.getBitRate() + "&format=mkv&timeOffset=" + offset;
        console.log("casting " + url);
        var mediaInfo = new chrome.cast.media.MediaInfo(url);
        mediaInfo.contentType = 'video/x-matroska';
        mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED;
        mediaInfo.duration = this.currentMediaDuration;
        mediaInfo.metadata = new chrome.cast.media.MovieMediaMetadata();
        mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.MOVIE;
        mediaInfo.metadata.title = model.video_title;
        mediaInfo.metadata.images = [new chrome.cast.Image(model.remoteCoverArtUrl + "&size=384")];

        var request = new chrome.cast.media.LoadRequest(mediaInfo);
        request.autoplay = this.autoplay;
        request.currentTime = 0;

        this.castPlayerState = PLAYER_STATE.LOADING;
        console.log(this.castPlayerState + " (loadMedia)");

        this.session.loadMedia(request,
                this.onMediaDiscovered.bind(this, 'loadMedia'),
                this.onLoadMediaError.bind(this));
    };

    /**
     * Callback function for loadMedia success
     * @param {Object} mediaSession A new media object.
     * @param {String} how How the session was discovered.
     */
    CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) {
        console.log("new media session ID:" + mediaSession.mediaSessionId + ' (' + how + ')');
        this.currentMediaSession = mediaSession;
        if (how == 'loadMedia') {
            this.castPlayerState = PLAYER_STATE.LOADED;
            console.log(this.castPlayerState + " (onMediaDiscovered-loadMedia)");
        }

        if (how == 'activeSession') {
            // TODO: Use currentMediaSession?
            this.castPlayerState = this.session.media[0].playerState;
            console.log(this.castPlayerState + " (onMediaDiscovered-activeSession)");
            this.currentMediaTime = Math.round(this.session.media[0].currentTime);
        }

        this.currentMediaSession.addUpdateListener(this.onMediaStatusUpdate.bind(this));

        // update UI
        this.updateMediaControlUI();
    };

    /**
     * Callback function when media load returns error
     */
    CastPlayer.prototype.onLoadMediaError = function (e) {
        console.log("media error");
        this.castPlayerState = PLAYER_STATE.IDLE;
        console.log(this.castPlayerState + " (onLoadMediaError)");
        this.updateMediaControlUI();
    };

    /**
     * Callback function for media status update from receiver
     * @param {!Boolean} alive whether the media object is still alive.
     */
    CastPlayer.prototype.onMediaStatusUpdate = function (alive) {
        if (!alive) {
            this.castPlayerState = PLAYER_STATE.IDLE;
            console.log(this.castPlayerState + " (onMediaStatusUpdate-dead)");
        } else {
            this.castPlayerState = this.currentMediaSession.playerState;
            console.log(this.castPlayerState + " (onMediaStatusUpdate)");
        }

        this.updateProgressBar();
        this.updateMediaControlUI();
    };

    /**
     * Helper function
     * Increment media current position by 1 second
     */
    CastPlayer.prototype.incrementMediaTime = function () {
        if (this.castPlayerState == PLAYER_STATE.PLAYING) {
            if (this.currentMediaOffset + this.currentMediaTime < this.currentMediaDuration) {
                this.currentMediaTime += 1;
                this.updateProgressBar();
            }
        }
    };

    /**
     * Updates the duration label.
     */
    CastPlayer.prototype.updateDurationLabel = function () {
        $("#duration").html(this.formatDuration(this.currentMediaDuration));
    };

    CastPlayer.prototype.formatDuration = function (duration) {
        var hours = Math.floor(duration / 3600);
        duration = duration % 3600;
        var minutes = Math.floor(duration / 60);
        var seconds = Math.floor(duration % 60);

        var result = "";
        if (hours > 0) {
            result += hours + ":";
            if (minutes < 10) {
                result += "0";
            }
        }
        result += minutes + ":";
        if (seconds < 10) {
            result += "0";
        }
        result += seconds;

        return result;
    };

    /**
     * Play media in Cast mode
     */
    CastPlayer.prototype.playMedia = function () {
        if (!this.currentMediaSession) {
            this.playMediaLocally(0);
            return;
        }

        switch (this.castPlayerState) {
            case PLAYER_STATE.LOADED:
            case PLAYER_STATE.PAUSED:
                this.currentMediaSession.play(null,
                        this.mediaCommandSuccessCallback.bind(this, "playing started for " + this.currentMediaSession.sessionId),
                        this.onError.bind(this));
                this.currentMediaSession.addUpdateListener(this.onMediaStatusUpdate.bind(this));
                this.castPlayerState = PLAYER_STATE.PLAYING;
                console.log(this.castPlayerState + " (playMedia)");
                break;
            case PLAYER_STATE.IDLE:
            case PLAYER_STATE.LOADING:
                this.loadMedia();
                this.currentMediaSession.addUpdateListener(this.onMediaStatusUpdate.bind(this));
                this.castPlayerState = PLAYER_STATE.PLAYING;
                console.log(this.castPlayerState + " (playMedia)");
                break;
            default:
                break;
        }
        this.updateMediaControlUI();
    };

    /**
     * Play media in local player
     * @param {Number} offset A number for media current position
     */
    CastPlayer.prototype.playMediaLocally = function (offset) {
        this.currentMediaDuration = this.localPlayer.duration;

        if (this.localPlayerState == PLAYER_STATE.PLAYING || this.localPlayerState == PLAYER_STATE.PAUSED) {
            this.localPlayer.play();
        } else {
            this.currentMediaOffset = offset;
            this.currentMediaTime = 0;

            var url = model.streamUrl + "&maxBitRate=" + this.getBitRate() + "&timeOffset=" + offset;
            console.log("playing local: " + url);

            this.localPlayer.src = url;
            this.localPlayer.play();
            this.seekInProgress = false;
        }
        this.updateMediaControlUI();
    };

    CastPlayer.prototype.getBitRate = function () {
        return $("#bitrate_menu").val();
    };

    /**
     * Pause media playback in Cast mode
     */
    CastPlayer.prototype.pauseMedia = function () {
        if (!this.currentMediaSession) {
            this.pauseMediaLocally();
            return;
        }

        if (this.castPlayerState == PLAYER_STATE.PLAYING) {
            this.castPlayerState = PLAYER_STATE.PAUSED;
            console.log(this.castPlayerState + " (pauseMedia)");
            this.currentMediaSession.pause(null,
                    this.mediaCommandSuccessCallback.bind(this, "paused " + this.currentMediaSession.sessionId),
                    this.onError.bind(this));
            this.updateMediaControlUI();
        }
    };

    /**
     * Changes the bit rate.
     */
    CastPlayer.prototype.changeBitRate = function () {
        // This effectively restarts streaming with the new bit rate.
        this.seekMedia();
    };

    /**
     * Share the video.
     */
    CastPlayer.prototype.share = function () {
        location.href = "createShare.view?id=" + model.video_id;
    };

    /**
     * Download the video.
     */
    CastPlayer.prototype.download = function () {
        location.href = "download.view?id=" + model.video_id;
    };

    /**
     * Pause media playback in local player
     */
    CastPlayer.prototype.pauseMediaLocally = function () {
        this.localPlayer.pause();
        this.updateMediaControlUI();
    };

    /**
     * Stop media playback in local player
     */
    CastPlayer.prototype.stopMediaLocally = function () {
        this.localPlayer.pause();
        this.localPlayer.currentTime = 0;
        this.updateMediaControlUI();
    };

    /**
     * Set media volume in local or Cast mode
     * @param {Boolean} mute A boolean
     */
    CastPlayer.prototype.setVolume = function (mute) {
        this.currentVolume = parseInt($("#volume_slider").slider("option", "value"));

        if (!this.currentMediaSession) {
            this.localPlayer.muted = mute;
            if (!mute) {
                this.localPlayer.volume = this.currentVolume / 100;
            }
            return;
        }

        if (mute) {
            this.session.setReceiverMuted(true,
                    this.mediaCommandSuccessCallback.bind(this),
                    this.onError.bind(this));
        } else {
            this.session.setReceiverVolumeLevel(this.currentVolume / 100.0,
                    this.mediaCommandSuccessCallback.bind(this),
                    this.onError.bind(this));
        }
        this.updateMediaControlUI();
    };

    /**
     * Toggle mute in either Cast or local mode
     */
    CastPlayer.prototype.muteMedia = function () {
        this.muted = !this.muted;
        this.setVolume(this.muted);
        $('#audio_on').toggle(!this.muted);
        $('#audio_off').toggle(this.muted);
        this.updateMediaControlUI();
    };

    /**
     * media seek function in either Cast or local mode
     */
    CastPlayer.prototype.seekMedia = function () {

        var offset = parseInt($("#progress_slider").slider("option", "value"));
        this.seekInProgress = true;
        this.currentMediaOffset = offset;
        this.currentMediaTime = 0;

        if (this.localPlayerState == PLAYER_STATE.PLAYING || this.localPlayerState == PLAYER_STATE.PAUSED) {
            this.localPlayerState = PLAYER_STATE.SEEKING;
            this.playMediaLocally(offset);
            return;
        }

        if (this.castPlayerState != PLAYER_STATE.PLAYING && this.castPlayerState != PLAYER_STATE.PAUSED) {
            return;
        }

        this.castPlayerState = PLAYER_STATE.SEEKING;
        this.loadMedia();
        this.updateMediaControlUI();
    };

    /**
     * Callback function for media command success
     */
    CastPlayer.prototype.mediaCommandSuccessCallback = function (info, e) {
        this.currentMediaTime = Math.round(this.session.media[0].currentTime);
        if (info) {
            console.log(info);
        }
    };

    /**
     * Update progress bar with the current media time.
     */
    CastPlayer.prototype.updateProgressBar = function () {
        $("#progress_slider").slider("option", "value", this.currentMediaOffset + this.currentMediaTime);
        $("#progress").html(this.formatDuration(this.currentMediaOffset + this.currentMediaTime));
    };

    CastPlayer.prototype.updateDebug = function () {
        var debug = "\n" + this.currentMediaOffset + "\n"
                + "currentMediaTime: " + this.currentMediaTime + "\n"
                + "localPlayerState: " + this.localPlayerState + "\n"
                + "castPlayerState: " + this.castPlayerState;
        $("#debug").text(debug);
    };

    /**
     * Update media control UI components based on localPlayerState or castPlayerState
     */
    CastPlayer.prototype.updateMediaControlUI = function () {

        var playerState = this.localPlayerState;

        if (this.deviceState == DEVICE_STATE.NOT_PRESENT) {
            $("#casticonactive").hide();
            $("#casticonidle").hide();
            $("#overlay_text").hide();
            var loaded = false;
            if(this.localPlayer.src) {
                loaded = true;
            }
            $("#overlay").toggle(!loaded);
        } else if (this.deviceState == DEVICE_STATE.ACTIVE) {
            $("#casticonactive").show();
            $("#casticonidle").hide();
            $("#overlay_text").show();
            $("#overlay").show();
            playerState = this.castPlayerState;
        } else {
            $("#casticonactive").hide();
            $("#casticonidle").show();
            $("#overlay_text").hide();
            var loaded = false;
            if(this.localPlayer.src) {
                loaded = true;
            }
            $("#overlay").toggle(!loaded);
        }

        switch (playerState) {
            case PLAYER_STATE.LOADED:
            case PLAYER_STATE.PLAYING:
                $("#play").hide();
                $("#pause").show();
                break;
            case PLAYER_STATE.PAUSED:
            case PLAYER_STATE.IDLE:
            case PLAYER_STATE.LOADING:
                $("#play").show();
                $("#pause").hide();
                break;
            default:
                break;
        }
    };

    /**
     * Initialize UI components and add event listeners
     */
    CastPlayer.prototype.initializeUI = function () {

        $("#progress_slider").slider({max: this.currentMediaDuration, animate: "fast", range: "min"});
        $("#volume_slider").slider({max: 100, value: 50, animate: "fast", range: "min"});

        // add event handlers to UI components
        $("#casticonidle").on('click', this.launchApp.bind(this));
        $("#casticonactive").on('click', this.stopApp.bind(this));
        $("#progress_slider").on('slidestop', this.seekMedia.bind(this));
        $("#volume_slider").on('slidestop', this.setVolume.bind(this, false));
        $("#audio_on").on('click', this.muteMedia.bind(this));
        $("#audio_off").on('click', this.muteMedia.bind(this));
        $("#play").on('click', this.playMedia.bind(this));
        $("#pause").on('click', this.pauseMedia.bind(this));
        $("#bitrate_menu").on('change', this.changeBitRate.bind(this));
        $("#share").on('click', this.share.bind(this));
        $("#download").on('click', this.download.bind(this));

        if (model.hide_share) {
          $("#share").hide();
        }
        if (model.hide_download) {
          $("#download").hide();
        }

//        setInterval(this.updateDebug.bind(this), 100);
    };

    window.CastPlayer = CastPlayer;
})();
