/* Copyright 2003-2008 Emergent Music LLC  All rights reserved.
$Id$
*/

/* Module for the one and only track that is currently playing.  Note that the playback may be audio or video */

/* Strategy: the track display when playing is updated by a timer asking the player for data as needed */
FLYFI.UPDATEINTERVAL_PLAYINGTRACK = 300;    // msec

/* Strategy: We were having concurrency problems with the YouTube player, so now we wait
    for the player to report that it is not playing before starting any new audio or video.
    See FLYFI.PlayingTrackCommandQueue below.
*/

// temporary storage for the parameters for the queued commands
FLYFI.playingTrack_data_play = [];           // playerControls
FLYFI.playingTrack_data_playAtPercent = [];  // percent
FLYFI.playingTrack_data_cueVideo = [];       // playerControls

// queue requires gloabal calls, so the data is saved above and popped here
FLYFI.playingTrack_popQueue_play = function (which) {
    var playerControls = FLYFI.playingTrack_data_play[which];
    FLYFI.playingTrack._play(playerControls);
    delete FLYFI.playingTrack_data_play[which];
};
FLYFI.playingTrack_popQueue_playAtPercent = function (which) {
    var percent = FLYFI.playingTrack_data_playAtPercent[which];
    FLYFI.playingTrack._playAtPercent(percent);
    delete FLYFI.playingTrack_data_playAtPercent[which];
};
FLYFI.playingTrack_popQueue_cueVideo = function (which) {
    var playerControls = FLYFI.playingTrack_data_cueVideo[which];
    FLYFI.playingTrack._cueVideo(playerControls);
    delete FLYFI.playingTrack_data_cueVideo[which];
};


FLYFI.PlayingTrack = function() {   // PlayingTrack Class
    // the class for the one and only track that will play at any time
    // the play() call takes playerControls that should contain the controls to be manipulated

    var self = this;
    
    self.NOTIFYFINISHED_MSEC_MIN = 5000;    // need to have 5 seconds between notifications for track-finished for the same track
    self.lastFinished = null;     // set to {'id': id, 'time', time} for the last call to notifyFinished

    self.playerControls = null;     // set when track loaded
    self.trackDict = null;          // NYI - don't need this separate now that we have self.playerControls and can use self.playerControls.trackDict
    
    self.bytesLoaded = 0;
    self.bytesTotal = 0;
    
    self.position = 0; // in msec
    self.duration = 0; // in msec
    
    self.playing = false;
    self.scrubbing = false;
    self.inThickBox = false;    // thick box obscures the video - need to know to re-cue it
    self.playListeners = [];    // Queue of callbacks called when something is played

    self.init = function () {
        setInterval(FLYFI.onInterval_PlayingTrack, FLYFI.UPDATEINTERVAL_PLAYINGTRACK);
    };
    
    self.addPlayListener = function(listener) {
        for (var i = 0; i < self.playListeners.length; i++) {
            if (self.playListeners[i] === listener) {
                return; // Already on queue
            }
        }
        self.playListeners.push(listener);
    };
    self.removePlayListener = function(listener) {
        for (var i = 0; i < self.playListeners.length; i++) {
            if (self.playListeners[i] === listener) {
                self.playListeners.splice(i, 1);
            }
        }
    };
    
    self.haveTrack = function() {
        return (self.trackDict !== null);
    };
    
    self.haveAudioTrack = function() {
        return (self.trackDict && self.trackDict.free);
    };
    
    self.haveVideoTrack = function() {
        return (self.trackDict && !self.trackDict.free);
    };
    
    self.clearTrack = function() {
        self._setPlaying(false);
        self.scrubbing = false;

        self.playerControls = null;
        self.trackDict = null;
        
        self.bytesLoaded = 0;
        self.bytesTotal = 0;
        self.position = 0;
        self.duration = 0;
            
        self.lastFinished = null;     
    };
    
    self._setPlaying = function(playing) {
        self.playing = playing;
        if (self.playerControls == FLYFI.playerControls) { // If this is the brown player then also record the state in the cookie.
            FLYFI.preferences.setBoolean(FLYFI.IS_PLAYING, playing);
        }
        var currentTitle = window.document.title;
        var newTitle = null;
        var prefix = FLYFI._('Playing') + ' | ';
        if (playing) {
            FLYFI.startOtherWindowMonitor();
            FLYFI.sendOtherWindowMessage("play"); // Always send the play message
            if (currentTitle.indexOf(prefix) !== 0) {
                newTitle = prefix + currentTitle;
            }
        } else {
            FLYFI.stopOtherWindowMonitor();
            if (currentTitle.indexOf(prefix) === 0) {
                newTitle = currentTitle.substr(prefix.length);
            }
        }
        if (newTitle) {
            window.document.title = newTitle;
        }
    };
    
    self._updateDisplay = function() {
        if (!self.playerControls) { return; }
        
        var leftThickBox = false;
        if (self.inThickBox && !$('body').hasClass('thickbox_open')) {
            leftThickBox = self.inThickBox;
            self.inThickBox = false;
            if (FLYFI.playerControls) {
                FLYFI.playerControls.newVideoPlayer();
            }
            if (leftThickBox == "cued" || FLYFI.playerControls != self.playerControls) {
                if (FLYFI.playerControls && FLYFI.playerControls.trackDict && !FLYFI.playerControls.trackDict.free) {
                    var which = FLYFI.playingTrack_data_cueVideo.push(FLYFI.playerControls) - 1;
                    setTimeout('FLYFI.playingTrack_popQueue_cueVideo(' + which + ')', 1500); // not the normal queue since we need more time to allow the thickbox to clear
                }
            }
        }

        if (FLYFI.playingTrack.haveAudioTrack()) {
            FLYFI.audioPlayer.setPlayerStatus();
            if (self.playerControls && self.playerControls.videoSpinner) { self.playerControls.videoSpinner.hide(); }
        } else if (self.playerControls && self.playerControls.videoPlayer) {
            self.playerControls.videoPlayer.setPlayerStatus();
            if (leftThickBox) {
                  if (leftThickBox == "playing" && FLYFI.playerControls == self.playerControls) {
                    var which1 = FLYFI.playingTrack_data_play.push(self.playerControls) - 1;
                    setTimeout('FLYFI.playingTrack_popQueue_play(' + which1 + ')', 1500); // not the normal queue since we need more time to allow the thickbox to clear
                }
            } else {
                if (self.playerControls && self.playerControls.videoSpinner) {
                    if (self.playerControls.videoPlayer.pending) {
                        self.playerControls.videoSpinner.show();
                    } else {
                        self.playerControls.videoSpinner.hide();
                    }
                }
            }
        }
        self._onSoundLoading();
        self._onTrackPlaying();
    };
    
    self.loadedTrackID = function() {
        if (self.trackDict) {
            return self.trackDict.trackID;
        }
        return null;
    };
    
    self.percentLoaded = function() {
        if (self.bytesTotal === 0) {
            return 0;
        }
        var percent = Math.round(self.bytesLoaded / self.bytesTotal * 1000) / 10;
        if (isNaN(percent)) {
            FLYFI.consoleLog_FireFox('NaN for percent in playingtrack.js self.percentLoaded');
            percent = 0;
        } 
        return percent;
    };

    self.percentPlayed = function() {
        if (self.duration <= 0) {
            return 0;
        }
        return Math.round(self.position / self.duration * 1000) / 10;
    };
    
    self.play = function(playerControls) {
        // start the playback of a new track, using the Queue to ensure there are no concurrency problems
        var which = FLYFI.playingTrack_data_play.push(playerControls) - 1;
        FLYFI.playingTrackCommandQueue.push('FLYFI.playingTrack_popQueue_play(' + which + ')');
    };
    
    self._play = function(playerControls) {
        // the implementation for self.play, used when the data is unqueued
        self._startPlay(playerControls);
    };
    
    self.onPlayByClickOnYouTube = function(playerControls) {
        // user clicked on a YouTube track to start it playing - adjust state to match
        self._startPlay(playerControls, true);
    };
    
    self._startPlay = function(playerControls, externalStart) {
        var widget = playerControls.targetTrackWidget();
        if (widget) {
            widget.removeClass("unavailable");
        }
        self.playerControls = playerControls;
        self.trackDict = playerControls.trackDict;
        
        self._setPlaying(true);
        if (!externalStart) {
            if (self.trackDict.free) {
                FLYFI.audioPlayer.play(self.trackDict);
            } else {
                if (FLYFI.isContest() && !(self.trackDict && self.trackDict.videoYouTubeIDs && self.trackDict.videoYouTubeIDs.length > 0)) {
                    self._setPlaying(false);
                    return; // can't play if don't have content, but don't skip forward either
                }
                if (self.playerControls.videoPlayer) {
                    self.playerControls.videoPlayer.play(playerControls);
                } else {
                    FLYFI.consoleLog_FireFox('not self.playerControls.videoPlayer in FLYFI.playingtrack._play');
                }
            }
        }
        FLYFI.server_ReportPlay(self.trackDict.trackID, self.trackDict.library_id);
        if (self.playerControls == FLYFI.playerControls && FLYFI.my_currentLibraryDict) { // If this is the brown radio then remember the track for the library.
            FLYFI.preferences.setTrackForLibrary(FLYFI.my_currentLibraryDict.libraryID, self.trackDict.trackID);
        }
        
        for (var i = 0; i < self.playListeners.length; i++) {
            self.playListeners[i](playerControls);
        }
    };
    
    self.playAtPercent = function(percent) {
        // move the playback to a new location, using the Queue to ensure there are no concurrency problems
        var which = FLYFI.playingTrack_data_playAtPercent.push(percent) - 1;
        FLYFI.playingTrackCommandQueue.push('FLYFI.playingTrack_popQueue_playAtPercent(' + which + ')');
    };
    
    self._playAtPercent = function(percent) {
        // the implementation for self.playAtPercent, used when the data is unqueued
        if (!self.haveTrack()) {
            return;
        }
        if (percent >= self.percentLoaded()) {
            percent = Math.max(0, self.percentLoaded() - 2); // play near the end of what is already loaded
        }
        var msec = Math.round(self.duration * percent / 100.0);
        self._setPlaying(true);
        if (self.trackDict.free) {
            FLYFI.audioPlayer.setPosition(msec);
        } else {
            if (self.playerControls.videoPlayer) {
                self.playerControls.videoPlayer.setPosition(msec);
            } else {
                FLYFI.consoleLog_FireFox('not self.playerControls.videoPlayer in FLYFI.playingtrack._playAtPercent');
            }
        }
    };
    
    self.pause = function() {
        // stop the playback and clearing the Queue
        FLYFI.playingTrackCommandQueue.pause();
    };
        
    self._pause = function() {
        // the implementation for self.pause, used when the command is unqueued
        if (!self.haveTrack()) {
            return;
        }
        self.playerControls.setNotPlaying();
        if (self.trackDict.free) {
            FLYFI.audioPlayer.pause(self.trackDict.trackID);
        } else {
            if (self.playerControls.videoPlayer) {
                self.playerControls.videoPlayer.pause();
            } else {
                FLYFI.consoleLog_FireFox('not self.playerControls.videoPlayer in FLYFI.playingtrack._pause');
            }
        }
        self._setPlaying(false);
    };

    self.stop = function() {
        // stop the playback, using the Queue to ensure there are no concurrency problems
        FLYFI.playingTrackCommandQueue.push('FLYFI.playingTrack._stop()');
    };
        
    self._stop = function() {
        // stop the current sound and unloads the track - use when another track is about to be loaded
        if (!self.haveTrack()) {
            return;
        }
        self._pause();
        if (self.trackDict.free) {
            FLYFI.audioPlayer.stop();
        } else {
            if (self.playerControls.videoPlayer) {
                self.playerControls.videoPlayer.stop();
            } else {
                FLYFI.consoleLog_FireFox('not self.playerControls.videoPlayer in FLYFI.playingtrack._stop');
            }
        }
    };

    self.resume = function() {
        // restart the playback, using the Queue to ensure there are no concurrency problems
        FLYFI.playingTrackCommandQueue.push('FLYFI.playingTrack._resume()');
    };
        
    self._resume = function() {
        if (!self.haveTrack()) {
            return;
        }
        if (self.trackDict.free) {
            FLYFI.audioPlayer.resume(self.trackDict.trackID);
        } else {
            if (self.playerControls.videoPlayer) {
                self.playerControls.videoPlayer.resume();
            } else {
                FLYFI.consoleLog_FireFox('not self.playerControls.videoPlayer in FLYFI.playingtrack._resume');
            }
        }
        self._setPlaying(true);
    };
    
    self._onSoundLoading = function() {
        if (!self.playerControls) { return; }
        
        var percent = self.percentLoaded();

        var loadProgressBar = self.playerControls.loadProgressBar;
        if (loadProgressBar.length > 0) {
            loadProgressBar.css('width', percent + '%');
        }
    };

    self._onTrackPlaying = function(evenIfScrubbing) {
        if (!self.playerControls)  { return; }

        if (evenIfScrubbing === null) {
            evenIfScrubbing = false;
        }
        var percent = self.percentPlayed();

        var progressBars = self.playerControls.progressBars;
        var playProgressBar = self.playerControls.playProgressBar;
        var scrubThumb = self.playerControls.scrubThumb;

        // the scrub_thumb has to stay within the bar, so it needs to be positioned separately
        // do this work in pixels rather than percent, so we can adjust it
        var wholeWidth = $(progressBars).width();
        
        var newPlayWidth = Math.round(wholeWidth * percent / 100.0);
        if (newPlayWidth != playProgressBar.width()) {
            playProgressBar.css('width', newPlayWidth + 'px');
        }
        
        // adjust the thumb
        if ((evenIfScrubbing || !self.scrubbing) && scrubThumb && scrubThumb.css('display') != 'none') {
            var thumbWidth = Math.floor(scrubThumb.width() / 2);    // ensure there is room to show the thumb
            scrubThumb.css('left', (Math.round(wholeWidth * percent / 100.0) - thumbWidth) + 'px');
        }
    };

    self.startScrubbing = function(event, bar) {
        // return whether we could start scrubbing
        var percent = self._computePercentFromEvent(event, bar);
        if (percent === null) {
            return false;
        }
        self.scrubbing = true;
        self._positionThumb(percent);
        return true;
    };
    
    self.scrub = function(event, bar) {
        var percent = self._computePercentFromEvent(event, bar);
        if (percent === null) {    // if not in range, then revert to non-scrubbing display
            self._onTrackPlaying(true);
            return;
        }
        self._positionThumb(percent);
    };
    
    self.stopScrubbing = function(event, bar) {
        self.scrubbing = false;
        var percent = self._computePercentFromEvent(event, bar);
        if (percent === null) {
            return;
        }
        FLYFI.playingTrack.playAtPercent(percent);
    };
    
    self._positionThumb = function(percent) {
        // 'left: 73%' does not work, so compute it in pixels
        var progressBars = self.playerControls.progressBars;
        var wholeWidth = $(progressBars).width();
        
        var correctedPercent = Math.min(percent, self.percentLoaded());
        var pixels = Math.round(wholeWidth * correctedPercent / 100.0);

        self.playerControls.scrubThumb.css('left', pixels + 'px');
    };
    
    self._computePercentFromEvent = function(event, bar) {
        // return the percentage represented by the x position of the event in the bar, or null if out-of-bounds
        var SLOP = 12;

        var barX =  FLYFI.findPosX(bar);
        var barY =  FLYFI.findPosY(bar);
        var x = event.pageX - barX;
        var y = event.pageY - barY;

        var width = $(bar).width();
        var height = $(bar).height();
        
        if (x < 0) {    // too far left?
            if (x < -SLOP) {
                return null; 
            } else {
                x = 0;
            }
        }
        if (width < x) {    // too far right?
            if (width + SLOP < x) {
                return null; 
            } else {
                x = width - 1;
            }
        }
        
        if ((y < -SLOP) || (height + SLOP < y)) {
            return null; 
        }
        
        return self._computePercent(x, width);
    };
    
    self._computePercent = function(x, width) {
        // return the percentage represented
        var percent = Math.round((x * 100.0) / width);
        if (percent < 0) {
            return 0;
        }
        if (percent > 100) {
            return 100;
        }
        return percent;
    };
    
    self.notifyFinished = function(id) {
        // the track with the supplied id has finished.
        //  if the same id is called again in a short period of time, don't notify again -
        //  in particular, the YouTube player was calling us twice and we would skip
        //  the following track
        var date = new Date();
        var time = date.getTime();
        
        // ignore repeat calls for the same track
        if (self.lastFinished && self.lastFinished.trackID == id && (time - self.lastFinished.time < self.NOTIFYFINISHED_MSEC_MIN))  { return; }
        
        self.lastFinished = {'id': id, 'time': time};
        
        self.playerControls.onSoundFinished();
        self._setPlaying(false); // NOTE: MUST come after call to onSoundFinished to enable video voting for the track that finished.
    };
        
    // Video
    
    self.cueVideo = function(playerControls) {
        // cue up a Video, using the Queue to ensure there are no concurrency problems
        var which = FLYFI.playingTrack_data_cueVideo.push(playerControls) - 1;
        FLYFI.playingTrackCommandQueue.push('FLYFI.playingTrack_popQueue_cueVideo(' + which + ')');
    };

    self._cueVideo = function(playerControls) {
        self.playerControls = playerControls;
        if (self.playerControls.videoPlayer) {
            self.playerControls.videoPlayer.clear();
        } else {
            FLYFI.consoleLog_FireFox('not self.playerControls.videoPlayer in FLYFI.playingtrack._cueVideo, clear');
        }
        
        self.trackDict = self.playerControls.trackDict;
        if (self.trackDict && self.trackDict.videoYouTubeIDs && self.trackDict.videoYouTubeIDs.length > 0) {
            if (self.playerControls.videoPlayer) {
                self.playerControls.videoPlayer.cue(self.playerControls);
            } else {
                FLYFI.consoleLog_FireFox('not self.playerControls.videoPlayer in FLYFI.playingtrack._cueVideo, cue');
            }
        } else {
            FLYFI.server_getVideo(self.playerControls, false);
        }
    };

    self.playNextVideo = function(badVideo, previous) {
        if (self.trackDict && self.trackDict.videoYouTubeIDs && self.trackDict.videoYouTubeIDs.length > 0) {
            self.pause();
            var videoIndex = (self.trackDict.videoIndex ? self.trackDict.videoIndex : 0);
            if (badVideo) {
                if (badVideo == FLYFI.BADVIDEO_USER_VOTE) {
                    FLYFI.post_NoResponse('/youtube/track/' + self.trackDict.trackID + '/badvideo/' + self.trackDict.videoYouTubeIDs[videoIndex] + '/json/', {});
                }
                if (self.trackDict.videoYouTubeIDs.length == 1) {
                    FLYFI.setUnavailable_doNext(self.playerControls, self.trackDict.trackID, true);
                    return;
                } else {
                    // if (badVideo == FLYFI.BADVIDEO_USER_VOTE) {
                        self.trackDict.videoYouTubeIDs.splice(videoIndex, 1);
                    // } else {
                    //    videoIndex += 1;
                    // } remove video even if it was a load failure - if there are only bad videos left in the list it stalls the player
                    if (videoIndex >= self.trackDict.videoYouTubeIDs.length) {
                        videoIndex = 0;
                    }
                }
            } else {
                if (previous) {
                    videoIndex -= 1;
                    if (videoIndex < 0) {
                        videoIndex = self.trackDict.videoYouTubeIDs.length - 1;
                    }
                } else {
                    videoIndex += 1;
                    if (videoIndex >= self.trackDict.videoYouTubeIDs.length) {
                        videoIndex = 0;
                    }
                }
            }
            self.trackDict.videoIndex = videoIndex;
            // start the playback of a new video for the track, using the Queue to ensure there are no concurrency problems
            // NOTE: Need to blank out current values to bypass "resume" processing in play method.
            self.playerControls.videoPlayer.youTubeVideoPlayer.videoLoaded_trackID = null;
            self.playerControls.videoPlayer.youTubeVideoPlayer.videoLoaded_youTubeID = null;
            self.play(self.playerControls);
            // NYI: Notify server which video the user is playing
        }
    };
};

// The one and only PlayingTrack
FLYFI.playingTrack = new FLYFI.PlayingTrack();

// PlayingTrackCommandQueue

FLYFI.PlayingTrackCommandQueue = function() {
    this.commands = [];
    this.blocked = false;   // only allow on command to execute at a time - it MUST call this.unblock() when complete
    
    var self = this;
    
    this.push = function (command) {
        // pause the player and execute the supplied command string, in serial order
        // command is a string of JavaScript commands (like that passed to Javascript's setTimeout) 
        FLYFI.playingTrack._pause();
        self.commands.push(command);
    };
    
    this.popAndBlock = function() {
        // remove and return the first command string or null.  If a command is returned, it blocks the return
        //  of the next command until 'unblock' is called
        if (self.blocked || self.commands.length === 0) { return null; }
        
        self.blocked = true;
        var command = self.commands[0];
        self.commands = self.commands.slice(1);
        return command;
    };
    
    this.unblock = function() { 
        self.blocked = false;
    };
    
    this.pause = function() {
        this.commands = [];
        FLYFI.playingTrack._pause();
    };
};
FLYFI.playingTrackCommandQueue = new FLYFI.PlayingTrackCommandQueue();

FLYFI.onInterval_PlayingTrack = function() {
    FLYFI.playingTrack._updateDisplay();
    
    var command = FLYFI.playingTrackCommandQueue.popAndBlock();
    if (command) {
        if (FLYFI.playingTrack.playing) {
            FLYFI.playingTrack._pause(); // stop the playback so we can do the next command
        }
        setTimeout('FLYFI._executeCommand("' + command + '")', 50);
    }
};
FLYFI._executeCommand = function(command) {
    try {
        eval(command);
    } finally {
        FLYFI.playingTrackCommandQueue.unblock();
    }
};

FLYFI.onClickThickBox_PlayingTrack = function(event) {
    // Thick boxes cause the video to stop without correcting our controls, so keep
    //  track of the video state, if any.  When we leave the thickbox we will restore the video state.
    if (FLYFI.playingTrack.haveVideoTrack()) {
//        FLYFI.playingTrack.inThickBox = (FLYFI.playingTrack.playing) ? "playing" : "cued";
//        $('body').addClass('thickbox_open');     // done in ThickBox code, too, but do here to prevent timing issues
    }
};

$(document).ready(function() {
    FLYFI.playingTrack.init();

    $('.thickbox').click(FLYFI.onClickThickBox_PlayingTrack);
});

